mirror of
https://github.com/opf/openproject.git
synced 2026-06-13 19:20:00 +00:00
Clean up remaining Backlogs dead code
Restore the minimal admin settings blankslate so the admin menu route remains valid after the sprint-based cleanup. Remove the remaining settings-driven story/task classification code, dead models and services, and the obsolete filter and spec setup that depended on it.
This commit is contained in:
@@ -551,12 +551,6 @@ en:
|
||||
sidebar_arrow: "Use the return arrow in the top left corner to return to the project’s <b>main menu</b>."
|
||||
welcome: "Take a three-minute introduction tour to learn the most <b>important features</b>. <br> We recommend completing the steps until the end. You can restart the tour any time."
|
||||
wiki: "Within the <b>wiki</b> you can document and share knowledge together with your team."
|
||||
backlogs:
|
||||
overview: "Manage your work in the <b>backlogs</b> view."
|
||||
sprints: "On the right you have the product backlog and the bug backlog, on the left you have the respective sprints. Here you can create <b>epics, user stories, and bugs</b>, prioritize via drag & drop and add them to a sprint."
|
||||
task_board_arrow: "To see your <b>task board</b>, open the sprint drop-down..."
|
||||
task_board_select: "...and select the <b>task board</b> entry."
|
||||
task_board: "The task board visualizes the <b>progress for this sprint</b>. Click on the plus (+) icon next to a user story to add new tasks or impediments. <br> The status can be updated by drag and drop."
|
||||
boards:
|
||||
overview: "Select <b>boards</b> to shift the view and manage your project using the agile boards view."
|
||||
lists_kanban: "Here you can create multiple lists (columns) within your board. This feature allows you to create a <b>Kanban board</b>, for example."
|
||||
|
||||
@@ -37,8 +37,7 @@ module API
|
||||
|
||||
cached_representer key_parts: %i[project type],
|
||||
dependencies: -> {
|
||||
all_permissions_granted_to_user_under_project + [Setting.work_package_done_ratio,
|
||||
Setting.plugin_openproject_backlogs]
|
||||
all_permissions_granted_to_user_under_project + [Setting.work_package_done_ratio]
|
||||
}
|
||||
|
||||
custom_field_injector type: :schema_representer
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
<%# -- 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.
|
||||
|
||||
++# %>
|
||||
|
||||
<%= component_wrapper(tag: :section) do %>
|
||||
<%= render(Primer::Beta::BorderBox.new(**@system_arguments)) do |border_box| %>
|
||||
<% border_box.with_header(id: dom_target(backlog, :header)) do %>
|
||||
<%= render(Backlogs::BacklogHeaderComponent.new(backlog:, project: @project, folded: folded?)) %>
|
||||
<% end %>
|
||||
<% if backlog.stories.empty? %>
|
||||
<% border_box.with_row(data: { empty_list_item: true }) do %>
|
||||
<%=
|
||||
render Primer::Beta::Blankslate.new(role: "status", aria: { live: "polite" }) do |blankslate|
|
||||
blankslate.with_heading(tag: :h4).with_content(t(".blankslate_title", name: sprint.name))
|
||||
blankslate.with_description_content(t(".blankslate_description"))
|
||||
end
|
||||
%>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% backlog.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"
|
||||
),
|
||||
tabindex: 0
|
||||
) do %>
|
||||
<%= render(Backlogs::StoryComponent.new(story:, project:, sprint:)) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -1,85 +0,0 @@
|
||||
# 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 Backlogs
|
||||
class BacklogComponent < ApplicationComponent
|
||||
include Primer::AttributesHelper
|
||||
include OpTurbo::Streamable
|
||||
include RbCommonHelper
|
||||
|
||||
attr_reader :backlog, :project, :current_user
|
||||
|
||||
delegate :sprint, :stories, to: :backlog
|
||||
|
||||
def initialize(backlog:, project:, current_user: User.current, **system_arguments)
|
||||
super()
|
||||
|
||||
@backlog = backlog
|
||||
@project = project
|
||||
@current_user = current_user
|
||||
|
||||
@system_arguments = system_arguments
|
||||
@system_arguments[:id] = dom_id(backlog)
|
||||
@system_arguments[:list_id] = "#{@system_arguments[:id]}-list"
|
||||
@system_arguments[:padding] = :condensed
|
||||
@system_arguments[:data] = merge_data(
|
||||
@system_arguments,
|
||||
{ data: drop_target_config }
|
||||
)
|
||||
end
|
||||
|
||||
def wrapper_uniq_by
|
||||
backlog.sprint_id
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def folded?
|
||||
current_user.backlogs_preference(:versions_default_fold_state) == "closed"
|
||||
end
|
||||
|
||||
def drop_target_config
|
||||
{
|
||||
generic_drag_and_drop_target: "container",
|
||||
target_container_accessor: ":scope > ul",
|
||||
target_id: "version:#{backlog.sprint_id}",
|
||||
target_allowed_drag_type: "story"
|
||||
}
|
||||
end
|
||||
|
||||
def draggable_item_config(story)
|
||||
{
|
||||
draggable_id: story.id,
|
||||
draggable_type: "story",
|
||||
drop_url: move_legacy_backlogs_project_sprint_story_path(project, sprint, story)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,90 +0,0 @@
|
||||
<%# -- 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.
|
||||
|
||||
++# %>
|
||||
|
||||
<%= component_wrapper(tag: :header) do %>
|
||||
<% if show? %>
|
||||
<%= grid_layout("op-backlogs-header", tag: :div) do |grid| %>
|
||||
<% grid.with_area(:collapsible) do %>
|
||||
<%=
|
||||
render(
|
||||
Primer::OpenProject::BorderBox::CollapsibleHeader.new(
|
||||
collapsible_id: "#{dom_id(backlog)}-list",
|
||||
collapsed:,
|
||||
multi_line: false
|
||||
)
|
||||
) do |collapsible|
|
||||
collapsible.with_title { sprint.name }
|
||||
collapsible.with_count(
|
||||
scheme: :default,
|
||||
count: story_count,
|
||||
round: true,
|
||||
aria: {
|
||||
label: t(".label_story_count", count: story_count),
|
||||
live: "polite"
|
||||
}
|
||||
)
|
||||
collapsible.with_description(role: "group") do
|
||||
format_date_range(date_range)
|
||||
end
|
||||
end
|
||||
%>
|
||||
<% end %>
|
||||
|
||||
<% grid.with_area(:points) do %>
|
||||
<%=
|
||||
render(
|
||||
Primer::Beta::Text.new(
|
||||
color: :subtle,
|
||||
classes: "velocity",
|
||||
aria: { live: "polite" }
|
||||
)
|
||||
) do
|
||||
%>
|
||||
<%= story_points %>
|
||||
<span class="op-backlogs-points-label"> <%= t(:"backlogs.points_label", count: story_points) %></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% grid.with_area(:menu) do %>
|
||||
<%= render(Backlogs::BacklogMenuComponent.new(backlog:, project: @project)) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%=
|
||||
primer_form_with(
|
||||
url: backlogs_project_sprint_path(project, sprint),
|
||||
model: sprint,
|
||||
method: :patch,
|
||||
class: "op-backlogs-header-form"
|
||||
) do |f|
|
||||
render(Backlogs::BacklogHeaderForm.new(f, cancel_path: show_name_backlogs_project_sprint_path(project, sprint)))
|
||||
end
|
||||
%>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -1,82 +0,0 @@
|
||||
# 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 Backlogs
|
||||
class BacklogHeaderComponent < ApplicationComponent
|
||||
include OpPrimer::ComponentHelpers
|
||||
include OpTurbo::Streamable
|
||||
include Primer::FetchOrFallbackHelper
|
||||
include Redmine::I18n
|
||||
include RbCommonHelper
|
||||
|
||||
STATE_DEFAULT = :show
|
||||
STATE_OPTIONS = [STATE_DEFAULT, :edit].freeze
|
||||
|
||||
attr_reader :backlog, :project, :state, :collapsed, :current_user
|
||||
|
||||
delegate :sprint, :stories, to: :backlog
|
||||
delegate :name, to: :sprint, prefix: :sprint
|
||||
delegate :edit?, :show?, to: :state
|
||||
|
||||
def initialize(
|
||||
backlog:,
|
||||
project:,
|
||||
state: STATE_DEFAULT,
|
||||
folded: false,
|
||||
current_user: User.current
|
||||
)
|
||||
super()
|
||||
|
||||
@backlog = backlog
|
||||
@project = project
|
||||
@state = ActiveSupport::StringInquirer.new(fetch_or_fallback(STATE_OPTIONS, state, STATE_DEFAULT).to_s)
|
||||
@collapsed = folded
|
||||
@current_user = current_user
|
||||
end
|
||||
|
||||
def wrapper_uniq_by
|
||||
backlog.sprint_id
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def story_points
|
||||
@story_points ||= stories.sum { |story| story.story_points || 0 }
|
||||
end
|
||||
|
||||
def story_count
|
||||
@story_count ||= stories.size
|
||||
end
|
||||
|
||||
def date_range
|
||||
[sprint.start_date, sprint.effective_date]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,121 +0,0 @@
|
||||
<%# -- 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.
|
||||
|
||||
++# %>
|
||||
|
||||
<%=
|
||||
render(Primer::Alpha::ActionMenu.new(**@system_arguments)) do |menu|
|
||||
menu.with_show_button(
|
||||
scheme: :invisible,
|
||||
icon: :"kebab-horizontal",
|
||||
"aria-label": t(".label_actions"),
|
||||
tooltip_direction: :se
|
||||
)
|
||||
|
||||
if user_allowed?(:create_sprints)
|
||||
menu.with_item(
|
||||
id: dom_target(sprint, :menu, :edit_sprint),
|
||||
label: t(".action_menu.edit_sprint"),
|
||||
href: edit_name_backlogs_project_sprint_path(project, sprint),
|
||||
content_arguments: { data: { turbo_stream: true } }
|
||||
) do |item|
|
||||
item.with_leading_visual_icon(icon: :pencil)
|
||||
end
|
||||
end
|
||||
|
||||
if user_allowed?(:add_work_packages) && user_allowed?(:assign_versions)
|
||||
menu.with_item(
|
||||
id: dom_target(sprint, :menu, :new_story),
|
||||
label: t(".action_menu.new_story"),
|
||||
href: new_project_work_packages_dialog_path(
|
||||
project,
|
||||
version_id: sprint.id,
|
||||
type_id: available_story_types.first
|
||||
),
|
||||
content_arguments: { data: { turbo_stream: true } }
|
||||
) do |item|
|
||||
item.with_leading_visual_icon(icon: :compose)
|
||||
end
|
||||
end
|
||||
|
||||
if user_allowed?(:create_sprints) || user_allowed?(:manage_sprint_items)
|
||||
menu.with_divider
|
||||
end
|
||||
|
||||
menu.with_item(
|
||||
id: dom_target(sprint, :menu, :stories_tasks),
|
||||
label: t(".action_menu.stories_tasks"),
|
||||
tag: :a,
|
||||
href: backlogs_project_sprint_query_path(project, sprint)
|
||||
) do |item|
|
||||
item.with_leading_visual_icon(icon: :"op-view-list")
|
||||
end
|
||||
|
||||
if backlog.sprint_backlog?
|
||||
menu.with_item(
|
||||
id: dom_target(sprint, :menu, :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
|
||||
|
||||
menu.with_item(
|
||||
id: dom_target(sprint, :menu, :burndown_chart),
|
||||
label: t("backlogs.label_burndown_chart"),
|
||||
tag: :a,
|
||||
href: backlogs_project_sprint_burndown_chart_path(project, sprint),
|
||||
disabled: !sprint.has_burndown?
|
||||
) do |item|
|
||||
item.with_leading_visual_icon(icon: :graph)
|
||||
end
|
||||
|
||||
if project.module_enabled? "wiki"
|
||||
menu.with_item(
|
||||
id: dom_target(sprint, :menu, :wiki),
|
||||
label: t(".action_menu.wiki"),
|
||||
tag: :a,
|
||||
href: edit_backlogs_project_sprint_wiki_path(project, sprint)
|
||||
) do |item|
|
||||
item.with_leading_visual_icon(icon: :book)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if user_allowed?(:create_sprints)
|
||||
menu.with_item(
|
||||
id: dom_target(sprint, :menu, :properties),
|
||||
label: t(".action_menu.properties"),
|
||||
tag: :a,
|
||||
href: edit_version_path(sprint, back_url: backlogs_project_backlogs_path(project), project_id: project)
|
||||
) do |item|
|
||||
item.with_leading_visual_icon(icon: :gear)
|
||||
end
|
||||
end
|
||||
end
|
||||
%>
|
||||
@@ -1,65 +0,0 @@
|
||||
# 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 Backlogs
|
||||
class BacklogMenuComponent < ApplicationComponent
|
||||
include RbCommonHelper
|
||||
|
||||
attr_reader :backlog, :project, :current_user
|
||||
|
||||
delegate :sprint, :stories, to: :backlog
|
||||
|
||||
def initialize(backlog:, project:, current_user: User.current, **system_arguments)
|
||||
super()
|
||||
|
||||
@backlog = backlog
|
||||
@project = project
|
||||
@current_user = current_user
|
||||
|
||||
@system_arguments = system_arguments
|
||||
@system_arguments[:menu_id] = dom_target(backlog, :menu)
|
||||
@system_arguments[:anchor_align] = :end
|
||||
@system_arguments[:classes] = class_names(
|
||||
@system_arguments[:classes],
|
||||
"hide-when-print"
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_allowed?(permission)
|
||||
current_user.allowed_in_project?(permission, project)
|
||||
end
|
||||
|
||||
def available_story_types
|
||||
@available_story_types ||= story_types & project.types
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -56,8 +56,7 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
label: t(".action_menu.add_work_package"),
|
||||
href: new_project_work_packages_dialog_path(
|
||||
project,
|
||||
sprint_id: sprint.id,
|
||||
type_id: available_story_types.first
|
||||
sprint_id: sprint.id
|
||||
),
|
||||
content_arguments: { data: { turbo_stream: true } }
|
||||
) do |item|
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
module Backlogs
|
||||
class SprintMenuComponent < ApplicationComponent
|
||||
include OpPrimer::ComponentHelpers
|
||||
include RbCommonHelper
|
||||
|
||||
attr_reader :sprint, :project, :current_user
|
||||
|
||||
@@ -51,10 +50,6 @@ module Backlogs
|
||||
)
|
||||
end
|
||||
|
||||
def stories
|
||||
@sprint.work_packages
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def show_task_board_link?
|
||||
@@ -68,9 +63,5 @@ module Backlogs
|
||||
def user_allowed?(permission)
|
||||
current_user.allowed_in_project?(permission, project)
|
||||
end
|
||||
|
||||
def available_story_types
|
||||
@available_story_types ||= story_types & project.types
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -53,11 +53,7 @@ module Backlogs
|
||||
private
|
||||
|
||||
def date_range
|
||||
if @sprint.is_a?(Agile::Sprint)
|
||||
[@sprint.start_date, @sprint.finish_date]
|
||||
else
|
||||
[@sprint.start_date, @sprint.effective_date]
|
||||
end
|
||||
[@sprint.start_date, @sprint.finish_date]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -51,20 +51,22 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
<% end %>
|
||||
|
||||
<% grid.with_area(:menu) do %>
|
||||
<%= render(
|
||||
Primer::Alpha::ActionMenu.new(
|
||||
menu_id: dom_target(story, :menu),
|
||||
src: menu_backlogs_project_sprint_story_path(project, sprint, story),
|
||||
anchor_align: :end,
|
||||
classes: "hide-when-print"
|
||||
)
|
||||
) do |menu| %>
|
||||
<% menu.with_show_button(
|
||||
scheme: :invisible,
|
||||
icon: :"kebab-horizontal",
|
||||
"aria-label": t(".label_actions"),
|
||||
tooltip_direction: :se
|
||||
) %>
|
||||
<% if menu_src.present? %>
|
||||
<%= render(
|
||||
Primer::Alpha::ActionMenu.new(
|
||||
menu_id: dom_target(story, :menu),
|
||||
src: menu_src,
|
||||
anchor_align: :end,
|
||||
classes: "hide-when-print"
|
||||
)
|
||||
) do |menu| %>
|
||||
<% menu.with_show_button(
|
||||
scheme: :invisible,
|
||||
icon: :"kebab-horizontal",
|
||||
"aria-label": t(".label_actions"),
|
||||
tooltip_direction: :se
|
||||
) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
|
||||
@@ -32,15 +32,17 @@ module Backlogs
|
||||
class StoryComponent < ApplicationComponent
|
||||
include OpPrimer::ComponentHelpers
|
||||
|
||||
attr_reader :story, :sprint, :project, :current_user
|
||||
attr_reader :story, :sprint, :project, :current_user, :show_actions, :show_drag_handle
|
||||
|
||||
def initialize(story:, sprint:, project:, current_user: User.current)
|
||||
def initialize(story:, sprint:, project:, current_user: User.current, show_actions: true, show_drag_handle: true)
|
||||
super()
|
||||
|
||||
@story = story
|
||||
@sprint = sprint
|
||||
@project = project
|
||||
@current_user = current_user
|
||||
@show_actions = show_actions
|
||||
@show_drag_handle = show_drag_handle
|
||||
end
|
||||
|
||||
private
|
||||
@@ -50,7 +52,13 @@ module Backlogs
|
||||
end
|
||||
|
||||
def draggable?
|
||||
current_user.allowed_in_project?(:manage_sprint_items, project)
|
||||
show_drag_handle && current_user.allowed_in_project?(:manage_sprint_items, project)
|
||||
end
|
||||
|
||||
def menu_src
|
||||
return unless show_actions
|
||||
|
||||
menu_project_sprint_story_path(project, sprint, story)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -77,7 +77,7 @@ module Backlogs
|
||||
id: dom_target(story, :menu, direction),
|
||||
label:,
|
||||
tag: :button,
|
||||
href: reorder_backlogs_project_sprint_story_path(project, sprint, story),
|
||||
href: reorder_project_sprint_story_path(project, sprint, story),
|
||||
form_arguments: { method: :post, inputs: [{ name: "direction", value: direction }] }
|
||||
) do |item|
|
||||
item.with_leading_visual_icon(icon:)
|
||||
|
||||
@@ -34,19 +34,5 @@ class BacklogsSettingsController < ApplicationController
|
||||
|
||||
before_action :require_admin
|
||||
|
||||
def show
|
||||
@settings = Admin::Settings::BacklogsSettingsModel.new(Setting.plugin_openproject_backlogs)
|
||||
end
|
||||
|
||||
def update # rubocop:disable Metrics/AbcSize
|
||||
@settings = Admin::Settings::BacklogsSettingsModel.new(permitted_params.backlogs_admin_settings)
|
||||
if @settings.valid?
|
||||
Setting.plugin_openproject_backlogs = @settings.to_h
|
||||
flash[:notice] = I18n.t(:notice_successful_update)
|
||||
redirect_to action: :show
|
||||
else
|
||||
flash.now[:error] = I18n.t(:notice_unsuccessful_update_with_reason, reason: @settings.errors.full_messages.to_sentence)
|
||||
render :show, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
def show; end
|
||||
end
|
||||
|
||||
@@ -53,7 +53,6 @@ class RbApplicationController < ApplicationController
|
||||
@sprint_id = params.delete(:sprint_id)
|
||||
return unless @sprint_id
|
||||
|
||||
@sprint = Agile::Sprint.for_project(@project).visible.find_by(id: @sprint_id) ||
|
||||
Sprint.visible.apply_to(@project).find(@sprint_id)
|
||||
@sprint = Agile::Sprint.for_project(@project).visible.find(@sprint_id)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -32,11 +32,7 @@ class RbBurndownChartsController < RbApplicationController
|
||||
helper :burndown_charts
|
||||
|
||||
def show
|
||||
@burndown = if @sprint.is_a?(Agile::Sprint)
|
||||
Burndown.new(@sprint, @project)
|
||||
else
|
||||
@sprint.burndown(@project)
|
||||
end
|
||||
@burndown = Burndown.new(@sprint, @project) if @sprint.date_range_set?
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render layout: true }
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
# 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 RbImpedimentsController < RbApplicationController
|
||||
def create
|
||||
call = Impediments::CreateService
|
||||
.new(user: current_user)
|
||||
.call(attributes: impediment_params(Impediment.new).merge(project: @project))
|
||||
|
||||
respond_with_impediment call
|
||||
end
|
||||
|
||||
def update
|
||||
@impediment = Impediment.find(params[:id])
|
||||
|
||||
call = Impediments::UpdateService
|
||||
.new(user: current_user, impediment: @impediment)
|
||||
.call(attributes: impediment_params(@impediment))
|
||||
|
||||
respond_with_impediment call
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def respond_with_impediment(call)
|
||||
status = call.success? ? 200 : 400
|
||||
@impediment = call.result
|
||||
|
||||
@include_meta = true
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render partial: "impediment", object: @impediment, status:, locals: { errors: call.errors } }
|
||||
end
|
||||
end
|
||||
|
||||
def impediment_params(instance)
|
||||
# We do not need project_id, since ApplicationController will take care of
|
||||
# fetching the record.
|
||||
params.delete(:project_id)
|
||||
|
||||
hash = params
|
||||
.permit(:version_id, :status_id, :id, :sprint_id,
|
||||
:assigned_to_id, :remaining_hours, :subject, :blocks_ids)
|
||||
.to_h
|
||||
.symbolize_keys
|
||||
|
||||
# We block block_ids only when user is not allowed to create or update the
|
||||
# instance passed.
|
||||
unless instance && ((instance.new_record? && User.current.allowed_in_project?(:add_work_packages,
|
||||
@project)) || User.current.allowed_in_any_work_package?(
|
||||
:edit_work_packages, in_project: @project
|
||||
))
|
||||
hash.delete(:block_ids)
|
||||
end
|
||||
|
||||
hash
|
||||
end
|
||||
end
|
||||
@@ -70,7 +70,6 @@ class RbMasterBacklogsController < RbApplicationController
|
||||
end
|
||||
|
||||
def load_backlogs
|
||||
@owner_backlogs = Backlog.owner_backlogs(@project)
|
||||
@sprints = Agile::Sprint.for_project(@project).not_completed.order_by_date
|
||||
@stories_by_sprint_id = WorkPackage
|
||||
.where(sprint: @sprints, project: @project)
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
# 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 RbQueriesController < RbApplicationController
|
||||
include WorkPackagesFilterHelper
|
||||
|
||||
def show
|
||||
filters = []
|
||||
if @sprint_id
|
||||
filters.push(filter_object("status_id", "*"))
|
||||
filters.push(filter_object("version_id", "=", [@sprint_id]))
|
||||
# Note: We need a filter for backlogs_work_package_type but currently it's not possible for plugins to introduce new filter types
|
||||
else
|
||||
filters.push(filter_object("status_id", "o"))
|
||||
filters.push(filter_object("version_id", "!*", [@sprint_id]))
|
||||
# Same as above
|
||||
end
|
||||
|
||||
query = {
|
||||
f: filters,
|
||||
c: ["type", "status", "priority", "subject", "assigned_to", "updated_at", "position"],
|
||||
t: "position:desc"
|
||||
}
|
||||
|
||||
redirect_to project_work_packages_with_query_path(@project, query)
|
||||
end
|
||||
end
|
||||
@@ -135,53 +135,8 @@ class RbSprintsController < RbApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def edit_name
|
||||
update_header_component_via_turbo_stream(state: :edit)
|
||||
respond_with_turbo_streams
|
||||
end
|
||||
|
||||
def show_name
|
||||
update_header_component_via_turbo_stream(state: :show)
|
||||
respond_with_turbo_streams
|
||||
end
|
||||
|
||||
def update
|
||||
call = Versions::UpdateService
|
||||
.new(user: current_user, model: @sprint)
|
||||
.call(attributes: sprint_params)
|
||||
|
||||
if call.success?
|
||||
status = 200
|
||||
state = :show
|
||||
@sprint = call.result
|
||||
render_success_flash_message_via_turbo_stream(message: I18n.t(:notice_successful_update))
|
||||
else
|
||||
status = 422
|
||||
state = :edit
|
||||
render_error_flash_message_via_turbo_stream(
|
||||
message: I18n.t(:notice_unsuccessful_update_with_reason, reason: call.message)
|
||||
)
|
||||
end
|
||||
|
||||
update_header_component_via_turbo_stream(state:)
|
||||
respond_with_turbo_streams(status:)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_header_component_via_turbo_stream(state: :show)
|
||||
@backlog = Backlog.for(sprint: @sprint, project: @project)
|
||||
|
||||
update_via_turbo_stream(
|
||||
component: Backlogs::BacklogHeaderComponent.new(
|
||||
backlog: @backlog,
|
||||
project: @project,
|
||||
state:
|
||||
),
|
||||
method: :morph
|
||||
)
|
||||
end
|
||||
|
||||
def update_sprint_header_component_via_turbo_stream(sprint:)
|
||||
update_via_turbo_stream(
|
||||
component: Backlogs::SprintHeaderComponent.new(sprint:, project: @project),
|
||||
@@ -213,15 +168,7 @@ class RbSprintsController < RbApplicationController
|
||||
def load_sprint_and_project
|
||||
load_project
|
||||
|
||||
@sprint = if (NEW_SPRINT_ACTIONS + SPRINT_STATE_ACTIONS).include?(action_name.to_sym)
|
||||
Agile::Sprint.for_project(@project).visible.find(params[:id])
|
||||
else
|
||||
Sprint.visible.find(params[:id])
|
||||
end
|
||||
end
|
||||
|
||||
def sprint_params
|
||||
params.expect(sprint: %i[name start_date effective_date])
|
||||
@sprint = Agile::Sprint.for_project(@project).visible.find(params[:id])
|
||||
end
|
||||
|
||||
def agile_sprint_params
|
||||
|
||||
@@ -47,26 +47,6 @@ class RbStoriesController < RbApplicationController
|
||||
layout: false)
|
||||
end
|
||||
|
||||
# Move a story from a Sprint to another Sprint or an Agile::Sprint.
|
||||
def move_legacy
|
||||
# The update service reloads the story internally (via #move_after),
|
||||
# so we memoize the previous version_id before the call.
|
||||
version_id_was = @story.version_id
|
||||
|
||||
move_attributes = infer_attributes_from_target
|
||||
unless move_story(move_attributes).success?
|
||||
return respond_with_turbo_streams(status: :unprocessable_entity)
|
||||
end
|
||||
|
||||
if target_sprint?(move_attributes)
|
||||
moved_to_sprint
|
||||
elsif target_version?(move_attributes) && @story.version_id != version_id_was
|
||||
moved_to_version
|
||||
end
|
||||
|
||||
respond_with_turbo_streams
|
||||
end
|
||||
|
||||
# Move a story from an Agile::Sprint to another Agile::Sprint, or the Inbox.
|
||||
def move
|
||||
# The update service reloads the story internally (via #move_after),
|
||||
@@ -80,8 +60,6 @@ class RbStoriesController < RbApplicationController
|
||||
|
||||
if target_inbox?(move_attributes)
|
||||
moved_to_inbox
|
||||
elsif target_version?(move_attributes)
|
||||
moved_to_version
|
||||
elsif target_sprint?(move_attributes) && @story.sprint_id != sprint_id_was
|
||||
moved_to_sprint
|
||||
end
|
||||
@@ -101,7 +79,7 @@ class RbStoriesController < RbApplicationController
|
||||
return respond_with_turbo_streams(status: :unprocessable_entity)
|
||||
end
|
||||
|
||||
replace_typed_component_via_turbo_stream(sprint: @sprint)
|
||||
replace_sprint_component_via_turbo_stream(sprint: @sprint)
|
||||
|
||||
respond_with_turbo_streams
|
||||
end
|
||||
@@ -113,7 +91,7 @@ class RbStoriesController < RbApplicationController
|
||||
|
||||
if call.success?
|
||||
# Update source component so that the moved story disappears
|
||||
replace_typed_component_via_turbo_stream(sprint: @sprint)
|
||||
replace_sprint_component_via_turbo_stream(sprint: @sprint)
|
||||
else
|
||||
render_error_flash_message_via_turbo_stream(
|
||||
message: I18n.t(:notice_unsuccessful_update_with_reason, reason: call.message)
|
||||
@@ -129,14 +107,6 @@ class RbStoriesController < RbApplicationController
|
||||
.call(attributes:, **position_attributes)
|
||||
end
|
||||
|
||||
def replace_typed_component_via_turbo_stream(sprint:)
|
||||
if sprint.is_a?(Agile::Sprint)
|
||||
replace_sprint_component_via_turbo_stream(sprint:)
|
||||
else
|
||||
replace_backlog_component_via_turbo_stream(sprint:)
|
||||
end
|
||||
end
|
||||
|
||||
def moved_to_inbox
|
||||
render_success_flash_message_via_turbo_stream(
|
||||
message: I18n.t(:notice_successful_move, from: @sprint.name, to: I18n.t(:label_inbox))
|
||||
@@ -148,12 +118,8 @@ class RbStoriesController < RbApplicationController
|
||||
)
|
||||
end
|
||||
|
||||
def moved_to_version
|
||||
moved_to(new_sprint: @story.version.becomes(Sprint))
|
||||
end
|
||||
|
||||
def moved_to_sprint
|
||||
moved_to(new_sprint: @story.sprint.becomes(Agile::Sprint))
|
||||
moved_to(new_sprint: @story.sprint)
|
||||
end
|
||||
|
||||
def moved_to(new_sprint:)
|
||||
@@ -162,51 +128,28 @@ class RbStoriesController < RbApplicationController
|
||||
)
|
||||
|
||||
# Update the target component so that the moved story shows up
|
||||
replace_typed_component_via_turbo_stream(sprint: new_sprint)
|
||||
replace_sprint_component_via_turbo_stream(sprint: new_sprint)
|
||||
end
|
||||
|
||||
def infer_attributes_from_target
|
||||
target_type, target_id = move_params[:target_id].split(":")
|
||||
|
||||
case target_type
|
||||
when "version"
|
||||
{ version_id: target_id, sprint_id: nil }
|
||||
when "sprint"
|
||||
# If the story is assigned to a version, we will only nullify the version
|
||||
# if it is used as a backlog. We will keep a "regular" version reference.
|
||||
# Otherwise, moving a story to a sprint would delete it from any version it is
|
||||
# assigned to.
|
||||
if @story.version&.used_as_backlog?
|
||||
{ version_id: nil, sprint_id: target_id }
|
||||
else
|
||||
{ sprint_id: target_id }
|
||||
end
|
||||
{ sprint_id: target_id }
|
||||
when "inbox"
|
||||
{ sprint_id: nil }
|
||||
else
|
||||
raise ArgumentError, "target_type must include one of: version, sprint, inbox."
|
||||
raise ArgumentError, "target_type must include one of: sprint, inbox."
|
||||
end
|
||||
end
|
||||
|
||||
def target_version?(move_attributes)
|
||||
move_attributes[:version_id].present?
|
||||
end
|
||||
|
||||
def target_sprint?(move_attributes)
|
||||
move_attributes[:sprint_id].present?
|
||||
end
|
||||
|
||||
def target_inbox?(move_attributes)
|
||||
move_attributes.key?(:sprint_id) && move_attributes[:sprint_id].nil? &&
|
||||
!move_attributes.key?(:version_id)
|
||||
end
|
||||
|
||||
def replace_backlog_component_via_turbo_stream(sprint:)
|
||||
@backlog = Backlog.for(sprint:, project: @project)
|
||||
replace_via_turbo_stream(
|
||||
component: Backlogs::BacklogComponent.new(backlog: @backlog, project: @project),
|
||||
method: :morph
|
||||
)
|
||||
move_attributes.key?(:sprint_id) && move_attributes[:sprint_id].nil?
|
||||
end
|
||||
|
||||
def replace_sprint_component_via_turbo_stream(sprint:)
|
||||
@@ -215,12 +158,7 @@ class RbStoriesController < RbApplicationController
|
||||
end
|
||||
|
||||
def load_story
|
||||
@allowed_stories =
|
||||
if @sprint.is_a?(Agile::Sprint)
|
||||
WorkPackage.visible.where(sprint: @sprint, project: @project)
|
||||
else
|
||||
Story.visible.where(Story.condition(@project, @sprint))
|
||||
end
|
||||
@allowed_stories = WorkPackage.visible.where(sprint: @sprint, project: @project)
|
||||
@story = @allowed_stories.find(params[:id])
|
||||
end
|
||||
|
||||
|
||||
@@ -31,32 +31,11 @@
|
||||
class RbTaskboardsController < RbApplicationController
|
||||
menu_item :backlogs
|
||||
|
||||
helper :taskboards
|
||||
|
||||
def show
|
||||
if @sprint.is_a?(Agile::Sprint)
|
||||
@board = @sprint.task_board_for(@project)
|
||||
@board = @sprint.task_board_for(@project)
|
||||
|
||||
return redirect_to(project_work_package_board_path(@project, @board)) if @board
|
||||
return redirect_to(project_work_package_board_path(@project, @board)) if @board
|
||||
|
||||
render_404
|
||||
else
|
||||
@statuses = Type.find(Task.type).statuses
|
||||
@story_ids = @sprint.stories(@project).map(&:id)
|
||||
@last_updated = Task.children_of(@story_ids)
|
||||
.order(Arel.sql("updated_at DESC"))
|
||||
.first
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_sprint_and_project
|
||||
@project = Project.visible.find(params[:project_id])
|
||||
|
||||
return unless (@sprint_id = params.delete(:sprint_id))
|
||||
|
||||
@sprint = Agile::Sprint.for_project(@project).visible.find_by(id: @sprint_id) ||
|
||||
Sprint.visible.apply_to(@project).find(@sprint_id)
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
# 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 RbTasksController < RbApplicationController
|
||||
# This is a constant here because we will recruit it elsewhere to whitelist
|
||||
# attributes. This is necessary for now as we still directly use `attributes=`
|
||||
# in non-controller code.
|
||||
PERMITTED_PARAMS = ["id", "subject", "assigned_to_id", "remaining_hours", "parent_id",
|
||||
"estimated_hours", "status_id", "sprint_id"].freeze
|
||||
|
||||
def create
|
||||
call = ::Tasks::CreateService
|
||||
.new(user: current_user)
|
||||
.call(attributes: task_params.merge(project: @project), prev_id: params[:prev])
|
||||
|
||||
respond_with_task call
|
||||
end
|
||||
|
||||
def update
|
||||
task = Task.find(task_params[:id])
|
||||
|
||||
call = ::Tasks::UpdateService
|
||||
.new(user: current_user, task:)
|
||||
.call(attributes: task_params, prev_id: params[:prev])
|
||||
|
||||
respond_with_task call
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def respond_with_task(call)
|
||||
status = call.success? ? 200 : 400
|
||||
@task = call.result
|
||||
|
||||
@include_meta = true
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render partial: "task", object: @task, status:, locals: { errors: call.errors } }
|
||||
end
|
||||
end
|
||||
|
||||
def task_params
|
||||
params.permit(PERMITTED_PARAMS).to_h.symbolize_keys
|
||||
end
|
||||
end
|
||||
@@ -1,43 +0,0 @@
|
||||
# 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 RbWikisController < RbApplicationController
|
||||
# NOTE: The methods #show and #edit are public (see init.rb). We will let
|
||||
# OpenProject's WikiController#index take care of authorization
|
||||
#
|
||||
# NOTE: The methods #show and #edit create a template page when called.
|
||||
def show
|
||||
redirect_to controller: "/wiki", action: "index", project_id: @project, id: @sprint.wiki_page
|
||||
end
|
||||
|
||||
def edit
|
||||
redirect_to controller: "/wiki", action: "edit", project_id: @project, id: @sprint.wiki_page
|
||||
end
|
||||
end
|
||||
@@ -1,124 +0,0 @@
|
||||
# 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 Admin
|
||||
module Settings
|
||||
class BacklogsSettingsForm < ApplicationForm
|
||||
include ::Settings::FormHelper
|
||||
|
||||
form do |f|
|
||||
f.autocompleter(
|
||||
name: :story_types,
|
||||
label: I18n.t(:backlogs_story_type),
|
||||
caption: setting_caption(:plugin_openproject_backlogs, :story_types),
|
||||
autocomplete_options: {
|
||||
multiple: true,
|
||||
closeOnSelect: false,
|
||||
clearable: false,
|
||||
decorated: true,
|
||||
data: {
|
||||
admin__backlogs_settings_target: "storyTypes",
|
||||
test_selector: "story_type_autocomplete"
|
||||
}
|
||||
}
|
||||
) do |list|
|
||||
available_types.each do |label, value|
|
||||
active = value.in?(Story.types)
|
||||
in_use = Task.type == value
|
||||
|
||||
list.option(
|
||||
label:,
|
||||
value:,
|
||||
selected: active,
|
||||
disabled: in_use
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
f.autocompleter(
|
||||
name: :task_type,
|
||||
label: I18n.t(:backlogs_task_type),
|
||||
caption: setting_caption(:plugin_openproject_backlogs, :task_type),
|
||||
input_width: :small,
|
||||
autocomplete_options: {
|
||||
multiple: false,
|
||||
closeOnSelect: true,
|
||||
clearable: false,
|
||||
decorated: true,
|
||||
data: {
|
||||
admin__backlogs_settings_target: "taskType",
|
||||
test_selector: "task_type_autocomplete"
|
||||
}
|
||||
}
|
||||
) do |list|
|
||||
available_types.each do |label, value|
|
||||
active = Task.type == value
|
||||
in_use = value.in?(Story.types)
|
||||
|
||||
list.option(
|
||||
label:,
|
||||
value:,
|
||||
selected: active,
|
||||
disabled: in_use
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
f.radio_button_group(
|
||||
name: :points_burn_direction,
|
||||
label: I18n.t(:backlogs_points_burn_direction)
|
||||
) do |group|
|
||||
group.radio_button(
|
||||
label: I18n.t(:label_points_burn_up),
|
||||
value: "up"
|
||||
)
|
||||
group.radio_button(
|
||||
label: I18n.t(:label_points_burn_down),
|
||||
value: "down"
|
||||
)
|
||||
end
|
||||
|
||||
f.text_field(
|
||||
name: :wiki_template,
|
||||
label: I18n.t(:backlogs_wiki_template),
|
||||
input_width: :medium
|
||||
)
|
||||
|
||||
f.submit(scheme: :primary, name: :apply, label: I18n.t(:button_save))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def available_types
|
||||
Type.pluck(:name, :id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,60 +0,0 @@
|
||||
# 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 Admin
|
||||
module Settings
|
||||
class BacklogsSettingsModel
|
||||
include ActiveModel::Model
|
||||
include ActiveModel::Attributes
|
||||
|
||||
attribute :story_types, array: true, default: []
|
||||
attribute :task_type, :integer
|
||||
attribute :points_burn_direction, :string
|
||||
attribute :wiki_template, :string
|
||||
|
||||
validates :task_type, exclusion: {
|
||||
in: ->(setting) { setting.story_types }, message: :cannot_be_story_type
|
||||
}
|
||||
|
||||
def story_types=(value)
|
||||
super(Array(value).map(&:to_i))
|
||||
end
|
||||
|
||||
def to_h
|
||||
{
|
||||
story_types:,
|
||||
task_type:,
|
||||
points_burn_direction:,
|
||||
wiki_template:
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,85 +0,0 @@
|
||||
# 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 Backlogs
|
||||
class BacklogHeaderForm < ApplicationForm
|
||||
attr_reader :cancel_path
|
||||
|
||||
form do |f|
|
||||
f.text_field(
|
||||
name: :name,
|
||||
label: attribute_name(:name),
|
||||
placeholder: attribute_name(:name),
|
||||
visually_hide_label: true,
|
||||
autofocus: true,
|
||||
autocomplete: "off"
|
||||
)
|
||||
|
||||
f.group(layout: :horizontal) do |dates|
|
||||
dates.single_date_picker(
|
||||
name: :start_date,
|
||||
input_width: :xsmall,
|
||||
full_width: false,
|
||||
label: attribute_name(:start_date),
|
||||
placeholder: attribute_name(:start_date),
|
||||
visually_hide_label: true,
|
||||
leading_visual: { icon: :calendar }
|
||||
)
|
||||
dates.single_date_picker(
|
||||
name: :effective_date,
|
||||
input_width: :xsmall,
|
||||
full_width: false,
|
||||
label: attribute_name(:effective_date),
|
||||
placeholder: attribute_name(:effective_date),
|
||||
visually_hide_label: true,
|
||||
leading_visual: { icon: :calendar }
|
||||
)
|
||||
end
|
||||
|
||||
f.group(layout: :horizontal) do |buttons|
|
||||
buttons.submit(scheme: :primary, name: :submit, label: I18n.t(:button_save))
|
||||
buttons.button(
|
||||
scheme: :secondary,
|
||||
name: :cancel,
|
||||
label: I18n.t(:button_cancel),
|
||||
tag: :a,
|
||||
data: { turbo_stream: true },
|
||||
href: cancel_path
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(cancel_path:)
|
||||
super()
|
||||
|
||||
@cancel_path = cancel_path
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -34,104 +34,6 @@ module RbCommonHelper
|
||||
safe_join([from, "–", to], " ") # – and
|
||||
end
|
||||
|
||||
def assignee_id_or_empty(story)
|
||||
story.assigned_to_id.to_s
|
||||
end
|
||||
|
||||
def assignee_name_or_empty(story)
|
||||
story.blank? || story.assigned_to.blank? ? "" : "#{story.assigned_to.firstname} #{story.assigned_to.lastname}"
|
||||
end
|
||||
|
||||
def blocks_ids(ids)
|
||||
ids.sort.join(",")
|
||||
end
|
||||
|
||||
def build_inline_style(task)
|
||||
is_assigned_task?(task) ? color_style(task) : ""
|
||||
end
|
||||
|
||||
def color_style(task)
|
||||
background_color = get_backlogs_preference(task.assigned_to, :task_color)
|
||||
|
||||
"style=\"background-color:#{background_color};\"".html_safe
|
||||
end
|
||||
|
||||
def color_contrast_class(task)
|
||||
if is_assigned_task?(task)
|
||||
color_contrast(background_color_hex(task)) ? "light" : "dark"
|
||||
else
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
def color_contrast(color)
|
||||
_, bright = find_color_diff 0x000000, color
|
||||
(bright > 128)
|
||||
end
|
||||
|
||||
# Return the contrast and brightness difference between two RGB values
|
||||
def find_color_diff(c1, c2)
|
||||
r1, g1, b1 = break_color c1
|
||||
r2, g2, b2 = break_color c2
|
||||
cont_diff = (r1 - r2).abs + (g1 - g2).abs + (b1 - b2).abs # Color contrast
|
||||
bright1 = ((r1 * 299) + (g1 * 587) + (b1 * 114)) / 1000
|
||||
bright2 = ((r2 * 299) + (g2 * 587) + (b2 * 114)) / 1000
|
||||
brt_diff = (bright1 - bright2).abs # Color brightness diff
|
||||
[cont_diff, brt_diff]
|
||||
end
|
||||
|
||||
# Break a color into the R, G and B components
|
||||
def break_color(rgb)
|
||||
r = (rgb & 0xff0000) >> 16
|
||||
g = (rgb & 0x00ff00) >> 8
|
||||
b = rgb & 0x0000ff
|
||||
[r, g, b]
|
||||
end
|
||||
|
||||
def is_assigned_task?(task)
|
||||
!(task.blank? || task.assigned_to.blank?)
|
||||
end
|
||||
|
||||
def background_color_hex(task)
|
||||
background_color = get_backlogs_preference(task.assigned_to, :task_color)
|
||||
background_color.sub("#", "0x").hex
|
||||
end
|
||||
|
||||
def id_or_empty(item)
|
||||
item.id.to_s
|
||||
end
|
||||
|
||||
def work_package_link_or_empty(work_package)
|
||||
modal_link_to_work_package(work_package.id, work_package, class: "prevent_edit") unless work_package.new_record?
|
||||
end
|
||||
|
||||
def modal_link_to_work_package(title, work_package, options = {})
|
||||
modal_link_to(title, work_package_path(work_package), options)
|
||||
end
|
||||
|
||||
def modal_link_to(title, path, options = {})
|
||||
html_id = "modal_work_package_#{SecureRandom.hex(10)}"
|
||||
link_to(title, path, options.merge(id: html_id, target: "_blank"))
|
||||
end
|
||||
|
||||
def mark_if_closed(story)
|
||||
!story.new_record? && work_package_status_for_id(story.status_id).is_closed? ? "closed" : ""
|
||||
end
|
||||
|
||||
def story_html_id_or_empty(story)
|
||||
story.id.nil? ? "" : "story_#{story.id}"
|
||||
end
|
||||
|
||||
def date_string_with_milliseconds(d, add = 0)
|
||||
return "" if d.blank?
|
||||
|
||||
d.strftime("%B %d, %Y %H:%M:%S") + "." + ((d.to_f % 1) + add).to_s.split(".")[1]
|
||||
end
|
||||
|
||||
def remaining_hours(item)
|
||||
item.remaining_hours.blank? || item.remaining_hours == 0 ? "" : item.remaining_hours
|
||||
end
|
||||
|
||||
def allow_sprint_creation?(project)
|
||||
current_user.allowed_in_project?(:create_sprints, project) &&
|
||||
!project.receive_shared_sprints?
|
||||
@@ -140,35 +42,4 @@ module RbCommonHelper
|
||||
def show_all_backlog
|
||||
ActiveRecord::Type::Boolean.new.cast(params[:all]) || false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def work_package_status_for_id(id)
|
||||
@all_work_package_status_by_id ||= all_work_package_status.inject({}) do |mem, status|
|
||||
mem[status.id] = status
|
||||
mem
|
||||
end
|
||||
|
||||
@all_work_package_status_by_id[id]
|
||||
end
|
||||
|
||||
def all_work_package_status
|
||||
@all_work_package_status ||= Status.order(Arel.sql("position ASC"))
|
||||
end
|
||||
|
||||
def backlogs_types
|
||||
[]
|
||||
end
|
||||
|
||||
def story_types
|
||||
[]
|
||||
end
|
||||
|
||||
def get_backlogs_preference(assignee, attr)
|
||||
assignee.is_a?(User) ? assignee.backlogs_preference(attr) : "#24B3E7"
|
||||
end
|
||||
|
||||
def sprint_board_label
|
||||
t("backlogs.label_sprint_board")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
#-- 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 TaskboardsHelper
|
||||
def impediments_by_position_for_status(sprint, project, status)
|
||||
@impediments_by_position_for_status ||= sprint.impediments(project).group_by(&:status_id)
|
||||
|
||||
(@impediments_by_position_for_status[status.id] || [])
|
||||
.sort_by { |i| i.position.presence || 0 }
|
||||
end
|
||||
end
|
||||
@@ -33,34 +33,13 @@ class Backlog
|
||||
|
||||
delegate :id, to: :sprint, prefix: true
|
||||
|
||||
def self.for(sprint:, project:)
|
||||
owner_backlog = sprint.settings(project)&.display == VersionSetting::DISPLAY_RIGHT
|
||||
new(sprint:, stories: sprint.stories(project), owner_backlog:)
|
||||
end
|
||||
|
||||
def self.inbox_for(project:)
|
||||
WorkPackage
|
||||
.visible
|
||||
.with_status_open
|
||||
.where(project:, sprint_id: nil)
|
||||
.includes(:type)
|
||||
.order(Arel.sql(Story::ORDER))
|
||||
end
|
||||
|
||||
def self.owner_backlogs(project)
|
||||
backlogs = Sprint.apply_to(project).with_status_open.displayed_right(project).order(:name)
|
||||
|
||||
stories_by_sprints = Story.backlogs(project.id, backlogs.map(&:id))
|
||||
|
||||
backlogs.map { |sprint| new(stories: stories_by_sprints[sprint.id], owner_backlog: true, sprint:) }
|
||||
end
|
||||
|
||||
def self.sprint_backlogs(project)
|
||||
sprints = Sprint.apply_to(project).with_status_open.displayed_left(project).order_by_date
|
||||
|
||||
stories_by_sprints = Story.backlogs(project.id, sprints.map(&:id))
|
||||
|
||||
sprints.map { |sprint| new(stories: stories_by_sprints[sprint.id], sprint:) }
|
||||
.order(WorkPackage.arel_table[:position].asc.nulls_last, WorkPackage.arel_table[:id].asc)
|
||||
end
|
||||
|
||||
def initialize(sprint:, stories:, owner_backlog: false)
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
#-- 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 Impediment < Task
|
||||
extend OpenProject::Backlogs::Mixins::PreventIssueSti
|
||||
|
||||
before_save :update_blocks_list
|
||||
|
||||
validate :validate_blocks_list
|
||||
|
||||
def self.default_scope
|
||||
roots
|
||||
.where(type_id: type)
|
||||
end
|
||||
|
||||
def blocks_ids=(ids)
|
||||
@blocks_ids = [ids] if ids.is_a?(Integer)
|
||||
@blocks_ids = ids.split(/\D+/).map(&:to_i) if ids.is_a?(String)
|
||||
@blocks_ids = ids.map(&:to_i) if ids.is_a?(Array)
|
||||
end
|
||||
|
||||
def blocks_ids
|
||||
@blocks_ids ||= blocks_relations.map(&:to_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_blocks_list
|
||||
mark_blocks_to_destroy
|
||||
|
||||
build_new_blocks
|
||||
end
|
||||
|
||||
def validate_blocks_list
|
||||
if blocks_ids.empty?
|
||||
errors.add :blocks_ids, :must_block_at_least_one_work_package
|
||||
else
|
||||
other_version_ids = WorkPackage.where(id: blocks_ids).pluck(:version_id).uniq
|
||||
if other_version_ids.size != 1 || other_version_ids[0] != version_id
|
||||
errors.add :blocks_ids,
|
||||
:can_only_contain_work_packages_of_current_sprint
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def mark_blocks_to_destroy
|
||||
blocks_relations.reject { |relation| blocks_ids.include?(relation.to_id) }.each(&:mark_for_destruction)
|
||||
end
|
||||
|
||||
def build_new_blocks
|
||||
(blocks_ids - blocks_relations.select { |relation| blocks_ids.include?(relation.to_id) }.map(&:to_id)).each do |id|
|
||||
blocks_relations.build(to_id: id)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -94,7 +94,7 @@ class Sprint < Version
|
||||
return false if wiki_page_title.blank?
|
||||
|
||||
page = project.wiki.find_page(wiki_page_title)
|
||||
return false if !page
|
||||
return false unless page
|
||||
|
||||
template = project.wiki.find_page(Setting.plugin_openproject_backlogs["wiki_template"])
|
||||
return false if template && page.text == template.text
|
||||
@@ -150,12 +150,6 @@ class Sprint < Version
|
||||
Version.where(conditions).each(&:burndown)
|
||||
end
|
||||
|
||||
def impediments(project)
|
||||
# for reasons beyond me,
|
||||
# the default_scope needs to be explicitly applied.
|
||||
Impediment.default_scope.where(version_id: self, project_id: project)
|
||||
end
|
||||
|
||||
def settings(project)
|
||||
version_settings.find { it.project_id == project.id || it.project_id.nil? }
|
||||
end
|
||||
|
||||
@@ -29,44 +29,6 @@
|
||||
class Story < WorkPackage
|
||||
extend OpenProject::Backlogs::Mixins::PreventIssueSti
|
||||
|
||||
def self.backlogs(project_id, sprint_ids, options = {}) # rubocop:disable Metrics/AbcSize
|
||||
options.reverse_merge!(order: Story::ORDER,
|
||||
conditions: Story.condition(project_id, sprint_ids))
|
||||
|
||||
candidates = Story.where(options[:conditions])
|
||||
.includes(:status, :type)
|
||||
.order(Arel.sql(options[:order]))
|
||||
|
||||
stories_by_version = Hash.new do |hash, sprint_id|
|
||||
hash[sprint_id] = []
|
||||
end
|
||||
|
||||
candidates.each do |story|
|
||||
last_rank = if stories_by_version[story.version_id].size > 0
|
||||
stories_by_version[story.version_id].last.rank
|
||||
else
|
||||
0
|
||||
end
|
||||
|
||||
story.rank = last_rank + 1
|
||||
stories_by_version[story.version_id] << story
|
||||
end
|
||||
|
||||
stories_by_version
|
||||
end
|
||||
|
||||
def self.sprint_backlog(project, sprint, options = {})
|
||||
Story.backlogs(project.id, [sprint.id], options)[sprint.id]
|
||||
end
|
||||
|
||||
def self.at_rank(project_id, sprint_id, rank)
|
||||
Story.where(Story.condition(project_id, sprint_id))
|
||||
.joins(:status)
|
||||
.order(Arel.sql(Story::ORDER))
|
||||
.offset(rank - 1)
|
||||
.first
|
||||
end
|
||||
|
||||
def self.types
|
||||
types = Setting.plugin_openproject_backlogs["story_types"]
|
||||
return [] if types.blank?
|
||||
@@ -75,19 +37,7 @@ class Story < WorkPackage
|
||||
end
|
||||
|
||||
def tasks
|
||||
Task.tasks_for(id)
|
||||
end
|
||||
|
||||
def tasks_and_subtasks
|
||||
return [] unless Task.type
|
||||
|
||||
descendants.where(type_id: Task.type)
|
||||
end
|
||||
|
||||
def direct_tasks_and_subtasks
|
||||
return [] unless Task.type
|
||||
|
||||
children.where(type_id: Task.type).map { |t| [t] + t.descendants }.flatten
|
||||
Task.children_of(id).order(:position)
|
||||
end
|
||||
|
||||
def set_points(p)
|
||||
@@ -109,58 +59,4 @@ class Story < WorkPackage
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Refactor and add tests
|
||||
#
|
||||
# groups = tasks.partition(&:closed?)
|
||||
# {:open => tasks.last.size, :closed => tasks.first.size}
|
||||
#
|
||||
def task_status
|
||||
closed = 0
|
||||
open = 0
|
||||
|
||||
tasks.each do |task|
|
||||
if task.closed?
|
||||
closed += 1
|
||||
else
|
||||
open += 1
|
||||
end
|
||||
end
|
||||
|
||||
{ open:, closed: }
|
||||
end
|
||||
|
||||
def rank=(r)
|
||||
@rank = r
|
||||
end
|
||||
|
||||
def rank
|
||||
if position.blank?
|
||||
extras = [
|
||||
"and ((#{WorkPackage.table_name}.position is NULL and #{WorkPackage.table_name}.id <= ?) or not #{WorkPackage.table_name}.position is NULL)", id
|
||||
]
|
||||
else
|
||||
extras = ["and not #{WorkPackage.table_name}.position is NULL and #{WorkPackage.table_name}.position <= ?", position]
|
||||
end
|
||||
|
||||
@rank ||= WorkPackage.where(Story.condition(project.id, version_id, extras))
|
||||
.joins(:status)
|
||||
.count
|
||||
@rank
|
||||
end
|
||||
|
||||
def self.condition(project_id, sprint_ids, extras = [])
|
||||
c = ["project_id = ? AND type_id in (?) AND version_id in (?)",
|
||||
project_id, Story.types, sprint_ids]
|
||||
|
||||
if extras.size > 0
|
||||
c[0] += " " + extras.shift
|
||||
c += extras
|
||||
end
|
||||
|
||||
c
|
||||
end
|
||||
|
||||
# This forces NULLS-LAST ordering
|
||||
ORDER = "CASE WHEN #{WorkPackage.table_name}.position IS NULL THEN 1 ELSE 0 END ASC, CASE WHEN #{WorkPackage.table_name}.position IS NULL THEN #{WorkPackage.table_name}.id ELSE #{WorkPackage.table_name}.position END ASC"
|
||||
end
|
||||
|
||||
@@ -30,32 +30,4 @@ require "date"
|
||||
|
||||
class Task < WorkPackage
|
||||
extend OpenProject::Backlogs::Mixins::PreventIssueSti
|
||||
|
||||
def self.type
|
||||
task_type = Setting.plugin_openproject_backlogs["task_type"]
|
||||
task_type.blank? ? nil : task_type.to_i
|
||||
end
|
||||
|
||||
# This method is used by Backlogs::List. It ensures, that tasks and stories
|
||||
# follow a similar interface
|
||||
def self.types
|
||||
[type]
|
||||
end
|
||||
|
||||
def self.tasks_for(story_id)
|
||||
Task.children_of(story_id).order(:position).each_with_index do |task, i|
|
||||
task.rank = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
def status_id=(id)
|
||||
super
|
||||
self.remaining_hours = 0 if Status.find(id).is_closed?
|
||||
end
|
||||
|
||||
def rank=(r)
|
||||
@rank = r
|
||||
end
|
||||
|
||||
attr_reader :rank
|
||||
end
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
#-- 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 BasicData
|
||||
module Backlogs
|
||||
class SettingSeeder < ::Seeder
|
||||
self.needs = [
|
||||
BasicData::TypeSeeder
|
||||
]
|
||||
|
||||
BACKLOGS_SETTINGS_KEYS = %w[
|
||||
story_types
|
||||
task_type
|
||||
points_burn_direction
|
||||
wiki_template
|
||||
].freeze
|
||||
|
||||
def seed_data!
|
||||
configure_backlogs_settings
|
||||
end
|
||||
|
||||
def applicable?
|
||||
not backlogs_configured?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def configure_backlogs_settings
|
||||
Setting.plugin_openproject_backlogs = current_backlogs_settings.merge(missing_backlogs_settings)
|
||||
end
|
||||
|
||||
def backlogs_configured?
|
||||
BACKLOGS_SETTINGS_KEYS.all? { configured?(it) }
|
||||
end
|
||||
|
||||
def configured?(key)
|
||||
current_backlogs_settings[key] != nil
|
||||
end
|
||||
|
||||
def current_backlogs_settings
|
||||
Hash(Setting.plugin_openproject_backlogs)
|
||||
end
|
||||
|
||||
def missing_backlogs_settings
|
||||
BACKLOGS_SETTINGS_KEYS
|
||||
.reject { |key| configured?(key) }
|
||||
.index_with { |key| setting_value(key) }
|
||||
.compact
|
||||
end
|
||||
|
||||
def setting_value(setting_key)
|
||||
case setting_key
|
||||
when "story_types"
|
||||
backlogs_story_types.map(&:id)
|
||||
when "task_type"
|
||||
backlogs_task_type.try(:id)
|
||||
when "points_burn_direction"
|
||||
"up"
|
||||
when "wiki_template"
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
def backlogs_story_types
|
||||
type_references = %i[
|
||||
default_type_feature
|
||||
default_type_epic
|
||||
default_type_user_story
|
||||
default_type_bug
|
||||
]
|
||||
seed_data.find_references(type_references, default: nil).compact
|
||||
end
|
||||
|
||||
def backlogs_task_type
|
||||
seed_data.find_reference(:default_type_task, default: nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,43 +0,0 @@
|
||||
#-- 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 Impediments::CreateService
|
||||
attr_accessor :user
|
||||
|
||||
def initialize(user:)
|
||||
self.user = user
|
||||
end
|
||||
|
||||
def call(attributes: {})
|
||||
attributes[:type_id] = Impediment.type
|
||||
|
||||
WorkPackages::CreateService
|
||||
.new(user:)
|
||||
.call(**attributes.merge(work_package: Impediment.new).symbolize_keys)
|
||||
end
|
||||
end
|
||||
@@ -1,43 +0,0 @@
|
||||
#-- 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 Impediments::UpdateService
|
||||
attr_accessor :user, :impediment
|
||||
|
||||
def initialize(user:, impediment:)
|
||||
self.user = user
|
||||
self.impediment = impediment
|
||||
end
|
||||
|
||||
def call(attributes: {})
|
||||
WorkPackages::UpdateService
|
||||
.new(user:,
|
||||
model: impediment)
|
||||
.call(**attributes)
|
||||
end
|
||||
end
|
||||
@@ -1,49 +0,0 @@
|
||||
#-- 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 Tasks::CreateService
|
||||
attr_accessor :user
|
||||
|
||||
def initialize(user:)
|
||||
self.user = user
|
||||
end
|
||||
|
||||
def call(attributes: {}, prev_id: "")
|
||||
attributes[:type_id] = Task.type
|
||||
|
||||
create_call = WorkPackages::CreateService
|
||||
.new(user:)
|
||||
.call(**attributes)
|
||||
|
||||
if create_call.success?
|
||||
create_call.result.move_after prev_id:
|
||||
end
|
||||
|
||||
create_call
|
||||
end
|
||||
end
|
||||
@@ -1,49 +0,0 @@
|
||||
#-- 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 Tasks::UpdateService
|
||||
attr_accessor :user, :task
|
||||
|
||||
def initialize(user:, task:)
|
||||
self.user = user
|
||||
self.task = task
|
||||
end
|
||||
|
||||
def call(attributes: {}, prev_id: "")
|
||||
create_call = WorkPackages::UpdateService
|
||||
.new(user:,
|
||||
model: task)
|
||||
.call(**attributes)
|
||||
|
||||
if create_call.success?
|
||||
create_call.result.move_after prev_id:
|
||||
end
|
||||
|
||||
create_call
|
||||
end
|
||||
end
|
||||
@@ -39,11 +39,11 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
tag: :a,
|
||||
href: backlogs_project_sprint_taskboard_path(@project, @sprint),
|
||||
mobile_icon: :"op-view-cards",
|
||||
mobile_label: sprint_board_label,
|
||||
aria: { label: sprint_board_label }
|
||||
mobile_label: t("backlogs.label_sprint_board"),
|
||||
aria: { label: t("backlogs.label_sprint_board") }
|
||||
) do |button|
|
||||
button.with_leading_visual_icon(icon: :"op-view-cards")
|
||||
sprint_board_label
|
||||
t("backlogs.label_sprint_board")
|
||||
end
|
||||
end
|
||||
%>
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
<%#-- 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.
|
||||
|
||||
++#%>
|
||||
|
||||
<%
|
||||
project ||= impediment.project
|
||||
prevent_edit = if User.current.allowed_in_project?(:edit_work_packages, project)
|
||||
""
|
||||
else
|
||||
"prevent_edit"
|
||||
end
|
||||
%>
|
||||
<div class="model work_package impediment <%= color_contrast_class(impediment) %> <%= prevent_edit %> <%= mark_if_closed(impediment) %><%= color_contrast_class(impediment) %>"
|
||||
id="work_package_<%= impediment.id %>"
|
||||
<%= build_inline_style(impediment) %>>
|
||||
<div class="id">
|
||||
<div class="t"><%= work_package_link_or_empty(impediment) %></div>
|
||||
<div class="v"><%= id_or_empty(impediment) %></div>
|
||||
</div>
|
||||
<div class="subject editable"
|
||||
fieldtype="textarea"
|
||||
fieldname="subject"
|
||||
fieldlabel="<%= WorkPackage.human_attribute_name(:subject) %>"
|
||||
field_id="<%= impediment.id %>"><%= impediment.subject %></div>
|
||||
<div class="blocks editable"
|
||||
fieldname="blocks_ids"
|
||||
fieldlabel="<%= t(:label_blocks_ids) %>"
|
||||
field_id="<%= impediment.id %>"><%= blocks_ids(impediment.blocks_ids) %></div>
|
||||
<div class="assigned_to_id editable"
|
||||
fieldtype="select"
|
||||
fieldname="assigned_to_id"
|
||||
fieldlabel="<%= WorkPackage.human_attribute_name(:assigned_to) %>"
|
||||
field_id="<%= impediment.id %>">
|
||||
<div class="t"><%= assignee_name_or_empty(impediment) %></div>
|
||||
<div class="v"><%= assignee_id_or_empty(impediment) %></div>
|
||||
</div>
|
||||
<div class="remaining_hours editable<%= " empty" if remaining_hours(impediment).blank? %>"
|
||||
fieldname="remaining_hours"
|
||||
fieldlabel="<%= WorkPackage.human_attribute_name(:remaining_hours) %>"
|
||||
field_id="<%= impediment.id %>"><%= remaining_hours(impediment) %></div>
|
||||
<div class="indicator"> </div>
|
||||
<div class="meta">
|
||||
<div class="story_id"><%= impediment.parent_id %></div>
|
||||
<div class="status_id"><%= impediment.status_id %></div>
|
||||
<%= render(partial: "shared/model_errors", object: errors) if defined?(errors) && !errors.empty? %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,52 +0,0 @@
|
||||
<%# -- 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.
|
||||
|
||||
++# %>
|
||||
|
||||
<%= turbo_frame_tag :backlogs_container, refresh: :morph do %>
|
||||
<% if @owner_backlogs.empty? && @sprint_backlogs.empty? %>
|
||||
<%=
|
||||
render(Primer::Beta::Blankslate.new(border: true, spacious: true)) do |blankslate|
|
||||
blankslate.with_visual_icon(icon: :versions)
|
||||
blankslate.with_heading(tag: :h2).with_content(t(:backlogs_empty_title))
|
||||
|
||||
if current_user.allowed_in_project?(:create_sprints, @project)
|
||||
blankslate.with_description_content(t(:backlogs_empty_action_text))
|
||||
end
|
||||
end
|
||||
%>
|
||||
<% else %>
|
||||
<div class="op-backlogs-container" data-controller="generic-drag-and-drop">
|
||||
<div id="sprint_backlogs_container" class="op-backlogs-lists">
|
||||
<%= render(Backlogs::BacklogComponent.with_collection(@sprint_backlogs, project: @project)) %>
|
||||
</div>
|
||||
<div id="owner_backlogs_container" class="op-backlogs-lists">
|
||||
<%= render(Backlogs::BacklogComponent.with_collection(@owner_backlogs, project: @project)) %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -1,66 +0,0 @@
|
||||
<%#-- 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.
|
||||
|
||||
++#%>
|
||||
|
||||
<% html_title t(:label_backlogs) %>
|
||||
|
||||
<% content_controller "backlogs" %>
|
||||
|
||||
<% content_for :content_header do %>
|
||||
<%=
|
||||
render Primer::OpenProject::PageHeader.new do |header|
|
||||
header.with_title { t(:label_backlogs) }
|
||||
header.with_breadcrumbs(
|
||||
[{ href: project_overview_path(@project), text: @project.name },
|
||||
t(:label_backlogs)]
|
||||
)
|
||||
end
|
||||
%>
|
||||
|
||||
<%=
|
||||
render(Primer::OpenProject::SubHeader.new) do |subheader|
|
||||
subheader.with_action_button(
|
||||
scheme: :primary,
|
||||
leading_icon: :plus,
|
||||
label: I18n.t(:label_version_new),
|
||||
tag: :a,
|
||||
href: new_project_version_path(@project)
|
||||
) do
|
||||
Version.human_model_name
|
||||
end
|
||||
end
|
||||
%>
|
||||
<% end %>
|
||||
|
||||
<% content_for :content_body do %>
|
||||
<%= turbo_frame_tag :backlogs_container, refresh: :morph, src: backlogs_project_backlogs_path(@project), class: "op-backlogs-page" %>
|
||||
<% end %>
|
||||
|
||||
<% content_for :content_body_right do %>
|
||||
<%= render(split_view_instance) if render_work_package_split_view? %>
|
||||
<% end %>
|
||||
@@ -1,172 +0,0 @@
|
||||
<%#-- 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.
|
||||
|
||||
++#%>
|
||||
|
||||
<% content_for :additional_js_dom_ready do %>
|
||||
<%= render(partial: "shared/server_variables", formats: [:js]) %>
|
||||
<% end %>
|
||||
|
||||
<% content_controller "backlogs--taskboard-legacy" %>
|
||||
|
||||
<% html_title @sprint.name %>
|
||||
|
||||
<%=
|
||||
render(Backlogs::SprintPageHeaderComponent.new(sprint: @sprint, project: @project)) do |header|
|
||||
header.with_action_button(
|
||||
tag: :a,
|
||||
href: backlogs_project_sprint_burndown_chart_path(@project, @sprint),
|
||||
mobile_icon: :graph,
|
||||
mobile_label: t(:"backlogs.show_burndown_chart"),
|
||||
aria: { label: t(:"backlogs.show_burndown_chart") },
|
||||
disabled: !@sprint.has_burndown?
|
||||
) do |button|
|
||||
button.with_leading_visual_icon(icon: :graph)
|
||||
t(:"backlogs.show_burndown_chart")
|
||||
end
|
||||
end
|
||||
%>
|
||||
|
||||
<%= render(Primer::OpenProject::SubHeader.new) do |component| %>
|
||||
<% component.with_filter_component(id: "col_width") do %>
|
||||
<%=
|
||||
render(
|
||||
Primer::Alpha::TextField.new(
|
||||
name: :col_width_input,
|
||||
type: :number,
|
||||
label: t(:"backlogs.column_width"),
|
||||
placeholder: t(:"backlogs.column_width"),
|
||||
visually_hide_label: true,
|
||||
leading_visual: { icon: :"zoom-in" },
|
||||
step: 1,
|
||||
input_width: :xsmall,
|
||||
autocomplete: "off"
|
||||
)
|
||||
)
|
||||
%>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<div id='rb'>
|
||||
<div id="taskboard">
|
||||
<table id="board_header" cellspacing="0">
|
||||
<tr>
|
||||
<td><%= t(:backlogs_story) %></td>
|
||||
<% @statuses.each do |status| %>
|
||||
<td class="swimlane"><%= status.name %></td>
|
||||
<% end %>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table id="impediments" class="board" cellspacing="0">
|
||||
<tr>
|
||||
<td><div class="label_sprint_impediments"><%= t(:label_sprint_impediments) %></div></td>
|
||||
<% if User.current.allowed_in_project?(:add_work_packages, @project) %>
|
||||
<td class="add_new clickable">+</td>
|
||||
<% else %>
|
||||
<td class="add_new"></td>
|
||||
<% end %>
|
||||
<% @statuses.each do |status| %>
|
||||
<td class="swimlane list <%= status.is_closed? ? "closed" : "" %>" id="impcell_<%= status.id %>">
|
||||
<%= render partial: "rb_impediments/impediment",
|
||||
collection: impediments_by_position_for_status(@sprint, @project, status) %>
|
||||
</td>
|
||||
<% end %>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table id="tasks" class="board" cellspacing="0">
|
||||
<% @sprint.stories(@project).each do |story| %>
|
||||
<% tasks_by_status_id = story.tasks.group_by(&:status_id) %>
|
||||
|
||||
<tr class="<%= story_html_id_or_empty(story) %>">
|
||||
<td>
|
||||
<div class="story <%= mark_if_closed(story) %>" id="<%= story_html_id_or_empty(story) %>">
|
||||
<div class='story-bar'>
|
||||
<div class="status">
|
||||
<%= story.status.name %>
|
||||
</div>
|
||||
<div class="id">
|
||||
<%= work_package_link_or_empty(story) %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="subject"><%= story.subject %></div>
|
||||
<div class='story-footer'>
|
||||
<div class="assigned_to_id">
|
||||
<% if story.assigned_to.present? %>
|
||||
<%= link_to_user(story.assigned_to) %>
|
||||
<% else %>
|
||||
<em><%= t("backlogs.unassigned") %></em>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="story-points">
|
||||
<%= story.story_points %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<% if User.current.allowed_in_project?(:add_work_packages, @project) %>
|
||||
<td class="add_new clickable">+</td>
|
||||
<% else %>
|
||||
<td class="add_new"></td>
|
||||
<% end %>
|
||||
<% @statuses.each do |status| %>
|
||||
<td class="swimlane list <%= status.is_closed? ? "closed" : "" %>" id="<%= story.id %>_<%= status.id %>">
|
||||
<%= render partial: "rb_tasks/task",
|
||||
collection: tasks_by_status_id[status.id] %>
|
||||
</td>
|
||||
<% end %>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="helpers">
|
||||
<select class="assigned_to_id template" id="assigned_to_id_options">
|
||||
<option value=""> </option>
|
||||
<% Principal.possible_assignee(@project).each do |user| %>
|
||||
<option value="<%= user.id %>" color="<%= get_backlogs_preference(user, :task_color) %>">
|
||||
<%= user.name %>
|
||||
</option>
|
||||
<% end %>
|
||||
</select>
|
||||
<div id="task_template">
|
||||
<%= render partial: "rb_tasks/task", object: Task.new, locals: { project: @project } %>
|
||||
</div>
|
||||
<div id="impediment_template">
|
||||
<%= render partial: "rb_impediments/impediment", object: Impediment.new, locals: { project: @project } %>
|
||||
</div>
|
||||
|
||||
<div id="work_package_editor"> </div>
|
||||
<div class="meta" id="last_updated"><%= date_string_with_milliseconds((@last_updated.blank? ? Time.zone.now : @last_updated.updated_at)) %></div>
|
||||
<div id="charts"> </div>
|
||||
<div id="preloader">
|
||||
<div id="spinner"> </div>
|
||||
<div id="warning"> </div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,65 +0,0 @@
|
||||
<%#-- 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.
|
||||
|
||||
++#%>
|
||||
|
||||
<%
|
||||
project ||= task.project
|
||||
prevent_edit = if User.current.allowed_in_project?(:edit_work_packages, project)
|
||||
""
|
||||
else
|
||||
"prevent_edit"
|
||||
end
|
||||
%>
|
||||
<div class="model work_package task <%= color_contrast_class(task) %> <%= prevent_edit %> <%= mark_if_closed(task) %>" id="work_package_<%= task.id %>" <%= build_inline_style(task) %>>
|
||||
<div class="id">
|
||||
<div class="t"><%= work_package_link_or_empty(task) %></div>
|
||||
<div class="v"><%= id_or_empty(task) %></div>
|
||||
</div>
|
||||
<div class="subject editable"
|
||||
fieldtype="textarea"
|
||||
fieldname="subject"
|
||||
fieldlabel="<%= WorkPackage.human_attribute_name(:subject) %>"
|
||||
field_id="<%= task.id %>"><%= task.subject %></div>
|
||||
<div class="assigned_to_id editable"
|
||||
fieldtype="select"
|
||||
fieldname="assigned_to_id"
|
||||
fieldlabel="<%= WorkPackage.human_attribute_name(:assigned_to) %>"
|
||||
field_id="<%= task.id %>">
|
||||
<div class="t"><%= assignee_name_or_empty(task) %></div>
|
||||
<div class="v"><%= assignee_id_or_empty(task) %></div>
|
||||
</div>
|
||||
<div class="remaining_hours editable<%= " empty" if remaining_hours(task).blank? %>"
|
||||
fieldname="remaining_hours"
|
||||
fieldlabel="<%= WorkPackage.human_attribute_name(:remaining_hours) %>"><%= remaining_hours(task) %></div>
|
||||
<div class="indicator"> </div>
|
||||
<div class="meta">
|
||||
<div class="story_id"><%= task.parent_id %></div>
|
||||
<div class="status_id"><%= task.status_id %></div>
|
||||
<%= render(partial: "shared/model_errors", object: errors) if defined?(errors) && !errors.empty? %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,36 +0,0 @@
|
||||
<%#-- 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.
|
||||
|
||||
++#%>
|
||||
|
||||
<div>
|
||||
<div class="meta" id="last_updated"><%= date_string_with_milliseconds((@last_updated.blank? ? Time.zone.parse(params[:after]) : @last_updated.updated_at), 0.001) %></div>
|
||||
<%= render partial: "task", collection: @tasks, locals: { include_meta: @include_meta } %>
|
||||
<%- if @impediments %>
|
||||
<%= render partial: "impediment", collection: @impediments, locals: { include_meta: @include_meta } %>
|
||||
<%- end %>
|
||||
</div>
|
||||
@@ -1,34 +0,0 @@
|
||||
<%#-- 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.
|
||||
|
||||
++#%>
|
||||
|
||||
<div class="errors">
|
||||
<% model_errors.full_messages.each do |err| %>
|
||||
<div><%= err %></div>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -1,67 +0,0 @@
|
||||
<%#-- 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.
|
||||
|
||||
++#%>
|
||||
|
||||
if (window.RB === null || window.RB === undefined) {
|
||||
window.RB = {};
|
||||
}
|
||||
|
||||
RB.constants = {
|
||||
project_id: <%= @project.id %>,
|
||||
sprint_id: <%= @sprint ? @sprint.id : "null" %>
|
||||
};
|
||||
|
||||
RB.urlFor = (function () {
|
||||
const routes = {
|
||||
update_sprint: '<%= backlogs_project_sprint_path(project_id: @project, id: ":id") %>',
|
||||
|
||||
create_task: '<%= backlogs_project_sprint_tasks_path(project_id: @project, sprint_id: ":sprint_id") %>',
|
||||
update_task: '<%= backlogs_project_sprint_task_path(project_id: @project, sprint_id: ":sprint_id", id: ":id") %>',
|
||||
|
||||
create_impediment: '<%= backlogs_project_sprint_impediments_path(project_id: @project, sprint_id: ":sprint_id") %>',
|
||||
update_impediment: '<%= backlogs_project_sprint_impediment_path(project_id: @project, sprint_id: ":sprint_id", id: ":id") %>'
|
||||
};
|
||||
|
||||
return function (routeName, options) {
|
||||
let route = routes[routeName];
|
||||
|
||||
if (options) {
|
||||
if (options.id) {
|
||||
route = route.replace(":id", options.id);
|
||||
}
|
||||
if (options.project_id) {
|
||||
route = route.replace(":project_id", options.project_id);
|
||||
}
|
||||
if (options.sprint_id) {
|
||||
route = route.replace(":sprint_id", options.sprint_id);
|
||||
}
|
||||
}
|
||||
|
||||
return route;
|
||||
};
|
||||
}());
|
||||
@@ -1,55 +0,0 @@
|
||||
<%# -- 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.
|
||||
|
||||
++# %>
|
||||
|
||||
<% html_title t(:label_backlogs) %>
|
||||
|
||||
<% content_for :content_header do %>
|
||||
<%=
|
||||
render Primer::OpenProject::PageHeader.new do |header|
|
||||
header.with_title { t(:label_backlogs) }
|
||||
header.with_breadcrumbs(
|
||||
[{ href: project_overview_path(@project), text: @project.name },
|
||||
t(:label_backlogs)]
|
||||
)
|
||||
end
|
||||
%>
|
||||
<% end %>
|
||||
|
||||
<% content_for :content_body do %>
|
||||
<%=
|
||||
render(Primer::Beta::Blankslate.new(border: true, spacious: true)) do |blankslate|
|
||||
blankslate.with_visual_icon(icon: :"op-backlogs")
|
||||
blankslate.with_heading(tag: :h2).with_content(t(:backlogs_not_configured_title))
|
||||
blankslate.with_description_content(t(:backlogs_not_configured_description))
|
||||
blankslate.with_secondary_action(href: admin_backlogs_settings_path, scheme: :default) do
|
||||
t(:backlogs_not_configured_action_text)
|
||||
end
|
||||
end
|
||||
%>
|
||||
<% end %>
|
||||
@@ -104,8 +104,6 @@ en:
|
||||
done_status: "Done status"
|
||||
sharing_description: "This project can either share its own sprints, receive shared sprints or handle sprints independently (no sharing)."
|
||||
sharing: "Sharing"
|
||||
impediment: "Impediment"
|
||||
label_versions_default_fold_state: "Show versions folded"
|
||||
label_burndown_chart: "Burndown chart"
|
||||
label_sprint_board: "Sprint board"
|
||||
work_package_is_closed: "Work package is done, when"
|
||||
@@ -122,32 +120,20 @@ en:
|
||||
story_points:
|
||||
one: "%{count} story point"
|
||||
other: "%{count} story points"
|
||||
task_color: "Task color"
|
||||
unassigned: "Unassigned"
|
||||
|
||||
administration_blankslate:
|
||||
title: "Backlog admin settings are evolving"
|
||||
text: "We are currently redesigning the Backlogs module. Admin settings for sprints and backlogs will be visible here in the near future. Project-level settings remain available."
|
||||
|
||||
user_preference:
|
||||
header_backlogs: "Backlogs module"
|
||||
button_update_backlogs: "Update backlogs module"
|
||||
|
||||
backlog_component:
|
||||
blankslate_title: "%{name} is empty"
|
||||
blankslate_description: "No items planned yet. Drag items here to add them."
|
||||
administration_blankslate:
|
||||
title: "Backlog admin settings are evolving"
|
||||
text: "We are currently redesigning the Backlogs module. Admin settings for sprints and backlogs will be visible here in the near future. Project-level settings remain available."
|
||||
|
||||
sprint_component:
|
||||
blankslate_title: "%{name} is empty"
|
||||
blankslate_description: "No items planned yet. Drag items here to add them."
|
||||
|
||||
backlog_header_component:
|
||||
label_toggle_backlog: "Collapse/Expand %{name}"
|
||||
label_story_count:
|
||||
zero: "No stories in backlog"
|
||||
one: "%{count} story in backlog"
|
||||
other: "%{count} stories in backlog"
|
||||
|
||||
inbox_component:
|
||||
blankslate_title: "Backlog inbox is empty"
|
||||
blankslate_description: "All open work packages in this project will automatically appear here."
|
||||
@@ -192,16 +178,6 @@ en:
|
||||
one: "%{count} story in sprint"
|
||||
other: "%{count} stories in sprint"
|
||||
|
||||
backlog_menu_component:
|
||||
label_actions: "Backlog actions"
|
||||
action_menu:
|
||||
edit_sprint: "Edit sprint"
|
||||
new_story: "New story"
|
||||
stories_tasks: "Stories/Tasks"
|
||||
task_board: "Task board"
|
||||
wiki: "Wiki"
|
||||
properties: "Properties"
|
||||
|
||||
finish_sprint_dialog_component:
|
||||
title: "There are work in progress items"
|
||||
body: "%{message} What would you like to do with these?"
|
||||
@@ -229,34 +205,18 @@ en:
|
||||
copy_work_package_id: "Copy work package ID"
|
||||
move_menu: "Move"
|
||||
|
||||
backlogs_points_burn_direction: "Points burn up/down"
|
||||
backlogs_story_type: "Story types"
|
||||
backlogs_task_type: "Task type"
|
||||
backlogs_wiki_template: "Template for sprint wiki page"
|
||||
|
||||
backlogs_not_configured_title: "Backlogs not configured"
|
||||
backlogs_not_configured_description: "Story and task types need to be set before using this module."
|
||||
backlogs_not_configured_action_text: "Configure Backlogs"
|
||||
burndown:
|
||||
story_points: "Story points"
|
||||
story_points_ideal: "Story points (ideal)"
|
||||
|
||||
errors:
|
||||
attributes:
|
||||
task_type:
|
||||
cannot_be_story_type: "can not also be a story type"
|
||||
|
||||
label_backlog: "Backlog"
|
||||
label_inbox: "Inbox"
|
||||
label_backlogs: "Backlogs"
|
||||
label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool."
|
||||
label_burndown_chart: "Burndown chart"
|
||||
label_column_in_backlog: "Column in backlog"
|
||||
label_sprint_board: "Sprint board"
|
||||
label_points_burn_down: "Down"
|
||||
label_points_burn_up: "Up"
|
||||
label_sprint_edit: "Edit sprint"
|
||||
label_sprint_impediments: "Sprint Impediments"
|
||||
label_sprint_new: "New sprint"
|
||||
label_backlog_and_sprints: "Backlog and sprints"
|
||||
label_task_board: "Task board"
|
||||
@@ -303,13 +263,3 @@ en:
|
||||
blankslate_description: "Set start and end date for the sprint to generate a burndown chart."
|
||||
|
||||
remaining_hours: "remaining work"
|
||||
|
||||
version_settings_display_label: "Column in backlog"
|
||||
version_settings_display_option_left: "left"
|
||||
version_settings_display_option_none: "none"
|
||||
version_settings_display_option_right: "right"
|
||||
|
||||
setting_plugin_openproject_backlogs_story_types_caption: |
|
||||
Types treated as backlog stories (e.g., Feature, User story). Must differ from task type.
|
||||
setting_plugin_openproject_backlogs_task_type_caption: |
|
||||
Type used for story tasks. Must differ from story types.
|
||||
|
||||
@@ -29,6 +29,10 @@
|
||||
#++
|
||||
|
||||
Rails.application.routes.draw do
|
||||
scope "admin" do
|
||||
resource :backlogs, only: :show, controller: :backlogs_settings, as: "admin_backlogs_settings"
|
||||
end
|
||||
|
||||
# Routes for the new Agile::Sprint
|
||||
# Scoped under projects for permissions:
|
||||
resources :projects, only: [] do
|
||||
@@ -49,6 +53,7 @@ Rails.application.routes.draw do
|
||||
member do
|
||||
get :menu
|
||||
put :move
|
||||
post :reorder
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -84,34 +89,10 @@ Rails.application.routes.draw do
|
||||
end
|
||||
end
|
||||
|
||||
resources :sprints, controller: :rb_sprints, only: %i[update] do
|
||||
resource :query, controller: :rb_queries, only: :show
|
||||
|
||||
resources :sprints, controller: :rb_sprints, only: [] do
|
||||
resource :taskboard, controller: :rb_taskboards, only: :show
|
||||
|
||||
resource :wiki, controller: :rb_wikis, only: %i[show edit]
|
||||
|
||||
resource :burndown_chart, controller: :rb_burndown_charts, only: :show
|
||||
|
||||
resources :impediments, controller: :rb_impediments, only: %i[create update]
|
||||
|
||||
resources :tasks, controller: :rb_tasks, only: %i[create update]
|
||||
|
||||
resources :stories, controller: :rb_stories, only: [] do
|
||||
member do
|
||||
get :menu
|
||||
put :move_legacy
|
||||
post :reorder
|
||||
end
|
||||
end
|
||||
|
||||
member do
|
||||
get :edit_name
|
||||
get :show_name
|
||||
end
|
||||
end
|
||||
|
||||
resource :query, controller: :rb_queries, only: :show
|
||||
end
|
||||
end
|
||||
|
||||
@@ -124,11 +105,4 @@ Rails.application.routes.draw do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
scope "admin" do
|
||||
resource :backlogs,
|
||||
only: %i[show update],
|
||||
controller: :backlogs_settings,
|
||||
as: "admin_backlogs_settings"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -91,7 +91,6 @@ module OpenProject::Backlogs::Burndown
|
||||
AND journals.data_type = '#{Journal::WorkPackageJournal.name}'
|
||||
AND #{container_query}
|
||||
AND #{project_id_query}
|
||||
AND #{type_id_query}
|
||||
#{and_status_query}
|
||||
JOIN
|
||||
(#{day_query.to_sql}) days
|
||||
@@ -129,14 +128,6 @@ module OpenProject::Backlogs::Burndown
|
||||
"(#{Journal::WorkPackageJournal.table_name}.project_id = #{project.id})"
|
||||
end
|
||||
|
||||
def type_id_query
|
||||
if sprint.is_a?(Agile::Sprint)
|
||||
"1 = 1"
|
||||
else
|
||||
"(#{Journal::WorkPackageJournal.table_name}.type_id in (#{collected_types.join(',')}))"
|
||||
end
|
||||
end
|
||||
|
||||
def day_query
|
||||
lower_bound = sprint.start_date
|
||||
upper_date = sprint.is_a?(Agile::Sprint) ? sprint.finish_date : sprint.effective_date
|
||||
@@ -146,9 +137,5 @@ module OpenProject::Backlogs::Burndown
|
||||
|
||||
Day.working.from_range(from: lower_bound, to: upper_bound)
|
||||
end
|
||||
|
||||
def collected_types
|
||||
Story.types << Task.type
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -54,30 +54,14 @@ module OpenProject::Backlogs
|
||||
author_url: "https://www.openproject.org",
|
||||
bundled: true,
|
||||
settings:) do
|
||||
Rails.application.reloader.to_prepare do
|
||||
OpenProject::AccessControl.permission(:add_work_packages).tap do |add|
|
||||
add.controller_actions << "rb_tasks/create"
|
||||
add.controller_actions << "rb_impediments/create"
|
||||
end
|
||||
|
||||
OpenProject::AccessControl.permission(:edit_work_packages).tap do |edit|
|
||||
edit.controller_actions << "rb_tasks/update"
|
||||
edit.controller_actions << "rb_impediments/update"
|
||||
end
|
||||
end
|
||||
|
||||
project_module :backlogs, dependencies: :work_package_tracking do
|
||||
permission :view_sprints,
|
||||
{ rb_master_backlogs: %i[index backlog details],
|
||||
rb_sprints: %i[index show show_name],
|
||||
rb_wikis: :show,
|
||||
rb_sprints: %i[index show],
|
||||
rb_stories: %i[index show menu],
|
||||
inbox: :menu,
|
||||
rb_queries: :show,
|
||||
rb_burndown_charts: :show,
|
||||
rb_taskboards: :show,
|
||||
rb_tasks: %i[index show],
|
||||
rb_impediments: %i[index show] },
|
||||
rb_taskboards: :show },
|
||||
permissible_on: :project,
|
||||
dependencies: %i[view_work_packages show_board_views]
|
||||
|
||||
@@ -89,8 +73,7 @@ module OpenProject::Backlogs
|
||||
require: :member
|
||||
|
||||
permission :create_sprints,
|
||||
{ rb_sprints: %i[new_dialog refresh_form create edit_name update edit_dialog update_agile_sprint],
|
||||
rb_wikis: %i[edit update] },
|
||||
{ rb_sprints: %i[new_dialog refresh_form create edit_dialog update_agile_sprint] },
|
||||
permissible_on: :project,
|
||||
require: :member,
|
||||
dependencies: :view_sprints
|
||||
@@ -102,7 +85,7 @@ module OpenProject::Backlogs
|
||||
dependencies: %i[view_sprints manage_board_views manage_sprint_items]
|
||||
|
||||
permission :manage_sprint_items,
|
||||
{ rb_stories: %i[move move_legacy reorder],
|
||||
{ rb_stories: %i[move reorder],
|
||||
inbox: %i[move reorder move_to_sprint_dialog] },
|
||||
permissible_on: :project,
|
||||
require: :member,
|
||||
@@ -143,7 +126,6 @@ module OpenProject::Backlogs
|
||||
patches %i[PermittedParams
|
||||
WorkPackage
|
||||
Status
|
||||
Type
|
||||
Project
|
||||
User
|
||||
Version]
|
||||
@@ -153,7 +135,6 @@ module OpenProject::Backlogs
|
||||
patch_with_namespace :WorkPackages, :SetAttributesService
|
||||
patch_with_namespace :WorkPackages, :BaseContract
|
||||
patch_with_namespace :WorkPackages, :UpdateContract
|
||||
patch_with_namespace :Versions, :RowComponent
|
||||
patch_with_namespace :API, :V3, :WorkPackages, :EagerLoading, :Checksum
|
||||
|
||||
config.to_prepare do
|
||||
@@ -229,12 +210,8 @@ module OpenProject::Backlogs
|
||||
end
|
||||
|
||||
config.to_prepare do
|
||||
enabled_backlogs_story = ->(type, project: nil) do
|
||||
if project.present?
|
||||
project.backlogs_enabled?
|
||||
else
|
||||
true
|
||||
end
|
||||
enabled_backlogs_story = ->(_type, project: nil) do
|
||||
project.nil? || project.backlogs_enabled?
|
||||
end
|
||||
|
||||
story_and_sprint_permission = ->(_type, project: nil) do
|
||||
|
||||
@@ -32,51 +32,17 @@ module OpenProject::Backlogs::List
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
acts_as_list touch_on_update: false
|
||||
acts_as_list touch_on_update: false, scope: %i[project_id sprint_id]
|
||||
|
||||
# acts as list adds a before destroy hook which messes
|
||||
# with the parent_id_was value
|
||||
skip_callback(:destroy, :before, :reload)
|
||||
|
||||
private
|
||||
|
||||
# Used by acts_list to limit the list to a certain subset within
|
||||
# the table.
|
||||
def scope_condition
|
||||
{ project_id:, sprint_id: }
|
||||
end
|
||||
|
||||
# acts_as_list needs to know when a work package moved between backlog/sprint scopes
|
||||
# so it can reorder both the source and target lists correctly.
|
||||
def scope_changed?
|
||||
(scope_condition.keys & changed.map(&:to_sym)).any?
|
||||
end
|
||||
|
||||
# Copied from acts_as_list to support our custom hash-based scope condition.
|
||||
def destroyed_via_scope?
|
||||
return false unless destroyed_by_association
|
||||
|
||||
foreign_key = destroyed_by_association.foreign_key
|
||||
if foreign_key.is_a?(Array)
|
||||
(scope_condition.keys & foreign_key.map(&:to_sym)).any?
|
||||
else
|
||||
scope_condition.keys.include?(foreign_key.to_sym)
|
||||
end
|
||||
end
|
||||
|
||||
include InstanceMethods
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
def move_after(position: nil, prev_id: nil)
|
||||
if acts_as_list_list.all?(position: nil)
|
||||
# If no items have a position, create an order on position
|
||||
# silently. This can happen when sorting inside a version for the first
|
||||
# time after backlogs was activated and there have already been items
|
||||
# inside the version at the time of backlogs activation
|
||||
set_default_prev_positions_silently(acts_as_list_list.last)
|
||||
end
|
||||
|
||||
# Remove so the potential 'prev' has a correct position
|
||||
remove_from_list
|
||||
reload
|
||||
@@ -101,13 +67,5 @@ module OpenProject::Backlogs::List
|
||||
def set_list_position(new_position, _raise_exception_if_save_fails = false) # rubocop:disable Style/OptionalBooleanParameter
|
||||
update_columns(position: new_position)
|
||||
end
|
||||
|
||||
def set_default_prev_positions_silently(prev)
|
||||
return if prev.nil?
|
||||
|
||||
WorkPackages::RebuildPositionsService.new(project: prev.project).call
|
||||
|
||||
prev.reload.position
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -40,12 +40,6 @@ module OpenProject::Backlogs::Patches::PermittedParamsPatch
|
||||
|
||||
permitted_params
|
||||
end
|
||||
|
||||
def backlogs_admin_settings
|
||||
params
|
||||
.require(:settings)
|
||||
.permit(:task_type, :points_burn_direction, :wiki_template, story_types: [])
|
||||
end
|
||||
end
|
||||
end
|
||||
PermittedParams.include OpenProject::Backlogs::Patches::PermittedParamsPatch
|
||||
|
||||
@@ -37,13 +37,6 @@ module OpenProject::Backlogs::Patches::ProjectPatch
|
||||
has_many :sprints, class_name: "Agile::Sprint", dependent: :destroy
|
||||
end
|
||||
|
||||
def rebuild_positions
|
||||
return unless backlogs_enabled?
|
||||
|
||||
shared_versions.each { |v| v.rebuild_story_positions(self) }
|
||||
nil
|
||||
end
|
||||
|
||||
def backlogs_enabled?
|
||||
module_enabled? "backlogs"
|
||||
end
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
#-- 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 OpenProject::Backlogs::Patches::TypePatch
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
include InstanceMethods
|
||||
extend ClassMethods
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
def story?
|
||||
false
|
||||
end
|
||||
|
||||
def task?
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
# 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 OpenProject::Backlogs::Patches::Versions::RowComponentPatch
|
||||
def button_links
|
||||
(super + [backlogs_edit_link]).compact
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def backlogs_edit_link
|
||||
return if version.project == table.project || !table.project.module_enabled?("backlogs")
|
||||
|
||||
helpers.link_to_if_authorized "",
|
||||
{ controller: "/versions", action: "edit", id: version, project_id: table.project.id },
|
||||
class: "icon icon-edit",
|
||||
title: t(:button_edit)
|
||||
end
|
||||
end
|
||||
@@ -59,21 +59,6 @@ module OpenProject::Backlogs::Patches::WorkPackagePatch
|
||||
project.done_statuses.to_a.include?(status)
|
||||
end
|
||||
|
||||
def story
|
||||
if Story.types.include?(type_id)
|
||||
Story.find(id)
|
||||
elsif Task.type.present? && type_id == Task.type
|
||||
ancestors.where(type_id: Story.types).first
|
||||
end
|
||||
end
|
||||
|
||||
def blocks
|
||||
# return work_packages that I block that aren't closed
|
||||
return [] if closed?
|
||||
|
||||
blocks_relations.includes(:to).merge(WorkPackage.with_status_open).map(&:to)
|
||||
end
|
||||
|
||||
def backlogs_enabled?
|
||||
project&.backlogs_enabled?
|
||||
end
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
# 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.
|
||||
#++
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe Backlogs::BacklogComponent, type: :component do
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
shared_let(:type_feature) { create(:type_feature) }
|
||||
shared_let(:type_task) { create(:type_task) }
|
||||
shared_let(:default_status) { create(:default_status) }
|
||||
shared_let(:default_priority) { create(:default_priority) }
|
||||
shared_let(:user) { create(:admin) }
|
||||
current_user { user }
|
||||
|
||||
let(:project) { create(:project, types: [type_feature, type_task]) }
|
||||
let(:sprint) { create(:sprint, project:, name: "Sprint 1", start_date: Date.yesterday, effective_date: Date.tomorrow) }
|
||||
let(:stories) { [] }
|
||||
let(:backlog) { Backlog.new(sprint:, stories:) }
|
||||
|
||||
before do
|
||||
allow(Setting)
|
||||
.to receive(:plugin_openproject_backlogs)
|
||||
.and_return("story_types" => [type_feature.id.to_s], "task_type" => type_task.id.to_s)
|
||||
|
||||
allow(user).to receive(:backlogs_preference).with(:versions_default_fold_state).and_return("open")
|
||||
end
|
||||
|
||||
def render_component
|
||||
render_inline(described_class.new(backlog:, project:, current_user: user))
|
||||
end
|
||||
|
||||
describe "rendering" do
|
||||
context "with stories" do
|
||||
let(:story1) do
|
||||
create(:story,
|
||||
project:,
|
||||
type: type_feature,
|
||||
status: default_status,
|
||||
priority: default_priority,
|
||||
story_points: 5,
|
||||
position: 1,
|
||||
version: sprint)
|
||||
end
|
||||
let(:story2) do
|
||||
create(:story,
|
||||
project:,
|
||||
type: type_feature,
|
||||
status: default_status,
|
||||
priority: default_priority,
|
||||
story_points: 3,
|
||||
position: 2,
|
||||
version: sprint)
|
||||
end
|
||||
let(:stories) { [story1, story2] }
|
||||
|
||||
it "renders a Primer::Beta::BorderBox" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_css(".Box")
|
||||
end
|
||||
|
||||
it "has the sprint ID in the DOM id" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_css(".Box#backlog_#{sprint.id}")
|
||||
end
|
||||
|
||||
it "renders BacklogHeaderComponent in header" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_css(".Box-header h3", text: "Sprint 1")
|
||||
end
|
||||
|
||||
it "renders a stable id on the backlog header" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_element(:div, class: "Box-header", id: /\Abacklog_#{sprint.id}_header\z/)
|
||||
end
|
||||
|
||||
it "renders StoryComponent for each story" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_css(".Box-row", count: 2) # 2 stories
|
||||
expect(page).to have_text(story1.subject)
|
||||
expect(page).to have_text(story2.subject)
|
||||
end
|
||||
|
||||
it "has drop target data attributes" do
|
||||
render_component
|
||||
|
||||
box = page.find(".Box")
|
||||
expect(box["data-generic-drag-and-drop-target"]).to eq("container")
|
||||
expect(box["data-target-container-accessor"]).to eq(":scope > ul")
|
||||
expect(box["data-target-id"]).to eq("version:#{sprint.id}")
|
||||
expect(box["data-target-allowed-drag-type"]).to eq("story")
|
||||
end
|
||||
|
||||
it "has draggable data attributes on story rows" do
|
||||
render_component
|
||||
|
||||
story_row = page.find(".Box-row[id='story_#{story1.id}']")
|
||||
expect(story_row["data-draggable-id"]).to eq(story1.id.to_s)
|
||||
expect(story_row["data-draggable-type"]).to eq("story")
|
||||
expect(story_row["data-drop-url"]).to end_with(move_legacy_backlogs_project_sprint_story_path(project, sprint, story1))
|
||||
end
|
||||
|
||||
it "renders story rows with proper classes" do
|
||||
render_component
|
||||
|
||||
story_row = page.find(".Box-row[id='story_#{story1.id}']")
|
||||
expect(story_row[:class]).to include("Box-row--hover-blue")
|
||||
expect(story_row[:class]).to include("Box-row--focus-gray")
|
||||
expect(story_row[:class]).to include("Box-row--clickable")
|
||||
end
|
||||
end
|
||||
|
||||
context "without stories" do
|
||||
let(:stories) { [] }
|
||||
let(:rendered_component) { render_component }
|
||||
|
||||
it_behaves_like "rendering Blank Slate", heading: "Sprint 1 is empty"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,221 +0,0 @@
|
||||
# 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.
|
||||
#++
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe Backlogs::BacklogHeaderComponent, type: :component do
|
||||
shared_let(:type_feature) { create(:type_feature) }
|
||||
shared_let(:type_task) { create(:type_task) }
|
||||
shared_let(:default_status) { create(:default_status) }
|
||||
shared_let(:default_priority) { create(:default_priority) }
|
||||
shared_let(:user) { create(:admin) }
|
||||
current_user { user }
|
||||
|
||||
let(:project) { create(:project, types: [type_feature, type_task]) }
|
||||
let(:start_date) { Date.new(2024, 1, 15) }
|
||||
let(:effective_date) { Date.new(2024, 1, 29) }
|
||||
let(:sprint) { create(:sprint, project:, name: "Sprint 1", start_date:, effective_date:) }
|
||||
let(:stories) { [] }
|
||||
let(:backlog) { Backlog.new(sprint:, stories:) }
|
||||
let(:state) { :show }
|
||||
let(:folded) { false }
|
||||
|
||||
before do
|
||||
allow(Setting)
|
||||
.to receive(:plugin_openproject_backlogs)
|
||||
.and_return("story_types" => [type_feature.id.to_s], "task_type" => type_task.id.to_s)
|
||||
end
|
||||
|
||||
def render_component(state: :show, folded: false)
|
||||
render_inline(described_class.new(backlog:, project:, state:, folded:, current_user: user))
|
||||
end
|
||||
|
||||
describe "show state (default)" do
|
||||
context "with stories" do
|
||||
let(:story1) do
|
||||
create(:story,
|
||||
project:,
|
||||
type: type_feature,
|
||||
status: default_status,
|
||||
priority: default_priority,
|
||||
story_points: 5,
|
||||
version: sprint)
|
||||
end
|
||||
let(:story2) do
|
||||
create(:story,
|
||||
project:,
|
||||
type: type_feature,
|
||||
status: default_status,
|
||||
priority: default_priority,
|
||||
story_points: 3,
|
||||
version: sprint)
|
||||
end
|
||||
let(:story_with_nil_points) do
|
||||
create(:story,
|
||||
project:,
|
||||
type: type_feature,
|
||||
status: default_status,
|
||||
priority: default_priority,
|
||||
story_points: nil,
|
||||
version: sprint)
|
||||
end
|
||||
let(:stories) { [story1, story2, story_with_nil_points] }
|
||||
|
||||
it "displays sprint name in h4" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_css("h3", text: "Sprint 1")
|
||||
end
|
||||
|
||||
it "shows story count via Primer::Beta::Counter" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_css(".Counter", text: "3")
|
||||
end
|
||||
|
||||
it "shows formatted date range with time tags" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_css("time[datetime='2024-01-15']")
|
||||
expect(page).to have_css("time[datetime='2024-01-29']")
|
||||
end
|
||||
|
||||
it "shows story points total (nil treated as 0)" do
|
||||
render_component
|
||||
|
||||
# 5 + 3 + 0 = 8 points
|
||||
expect(page).to have_text("8 points", normalize_ws: true)
|
||||
end
|
||||
|
||||
it "renders collapse/expand chevrons" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_octicon(:"chevron-up", visible: :all)
|
||||
expect(page).to have_octicon(:"chevron-down", visible: :all)
|
||||
end
|
||||
|
||||
it "renders BacklogMenuComponent" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_css("action-menu")
|
||||
end
|
||||
end
|
||||
|
||||
context "with no stories" do
|
||||
let(:stories) { [] }
|
||||
|
||||
it "shows 0 story count" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_css(".Counter", text: "0")
|
||||
end
|
||||
|
||||
it "shows 0 points" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_text("0 points", normalize_ws: true)
|
||||
end
|
||||
end
|
||||
|
||||
context "when sprint has no dates" do
|
||||
let(:sprint) { create(:sprint, project:, name: "Sprint 1", start_date: nil, effective_date: nil) }
|
||||
|
||||
it "renders without date range" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_no_css("time")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "folded state" do
|
||||
context "when folded is true" do
|
||||
it "renders chevron-up hidden and chevron-down visible" do
|
||||
render_component(folded: true)
|
||||
|
||||
# When folded, chevron-up is hidden (has hidden attribute on svg)
|
||||
# and chevron-down is visible (for expanding)
|
||||
expect(page).to have_css("svg[hidden][data-target='collapsible-header.arrowUp']", visible: :hidden)
|
||||
expect(page).to have_css("svg[data-target='collapsible-header.arrowDown']:not([hidden])", visible: :all)
|
||||
end
|
||||
end
|
||||
|
||||
context "when folded is false" do
|
||||
it "renders chevron-down hidden and chevron-up visible" do
|
||||
render_component(folded: false)
|
||||
|
||||
# When expanded, chevron-down is hidden (has hidden attribute)
|
||||
# and chevron-up is visible (for collapsing)
|
||||
expect(page).to have_css("svg[hidden][data-target='collapsible-header.arrowDown']", visible: :hidden)
|
||||
expect(page).to have_css("svg[data-target='collapsible-header.arrowUp']:not([hidden])", visible: :all)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "edit state" do
|
||||
it "renders a form" do
|
||||
render_component(state: :edit)
|
||||
|
||||
expect(page).to have_css("form")
|
||||
end
|
||||
|
||||
it "renders text field for name" do
|
||||
render_component(state: :edit)
|
||||
|
||||
expect(page).to have_field(Sprint.human_attribute_name(:name), with: "Sprint 1")
|
||||
end
|
||||
|
||||
it "renders date picker components" do
|
||||
render_component(state: :edit)
|
||||
|
||||
# Date pickers have calendar icons as leading visuals
|
||||
expect(page).to have_octicon(:calendar, count: 2)
|
||||
end
|
||||
|
||||
it "shows Save button" do
|
||||
render_component(state: :edit)
|
||||
|
||||
expect(page).to have_button(I18n.t(:button_save))
|
||||
end
|
||||
|
||||
it "shows Cancel button" do
|
||||
render_component(state: :edit)
|
||||
|
||||
expect(page).to have_link(I18n.t(:button_cancel))
|
||||
end
|
||||
end
|
||||
|
||||
describe "state validation" do
|
||||
it "raises an InvalidValueError for invalid state values" do
|
||||
expect { render_component(state: :invalid) }
|
||||
.to raise_error(Primer::FetchOrFallbackHelper::InvalidValueError)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,340 +0,0 @@
|
||||
# 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.
|
||||
#++
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe Backlogs::BacklogMenuComponent, type: :component do
|
||||
shared_let(:type_feature) { create(:type_feature) }
|
||||
shared_let(:type_task) { create(:type_task) }
|
||||
|
||||
let(:project) { create(:project, types: [type_feature, type_task]) }
|
||||
let(:sprint) { create(:sprint, project:, name: "Sprint 1", start_date: Date.yesterday, effective_date: Date.tomorrow) }
|
||||
let(:stories) { [] }
|
||||
let(:backlog) { Backlog.new(sprint:, stories:, owner_backlog:) }
|
||||
let(:owner_backlog) { true }
|
||||
let(:user) { create(:user) }
|
||||
let(:permissions) { [] }
|
||||
|
||||
before do
|
||||
allow(Setting)
|
||||
.to receive(:plugin_openproject_backlogs)
|
||||
.and_return("story_types" => [type_feature.id.to_s], "task_type" => type_task.id.to_s)
|
||||
|
||||
mock_permissions_for user do |mock|
|
||||
mock.allow_in_project(*permissions, project:)
|
||||
end
|
||||
end
|
||||
|
||||
def render_component
|
||||
render_inline(described_class.new(backlog:, project:, current_user: user))
|
||||
end
|
||||
|
||||
it "renders a stable id on the action menu and stories/tasks item" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_element(:button, id: /\Abacklog_#{sprint.id}_menu-button\z/)
|
||||
expect(page).to have_element(:ul, id: /\Abacklog_#{sprint.id}_menu-list\z/)
|
||||
expect(page).to have_element(:a, id: /\Asprint_#{sprint.id}_menu_stories_tasks\z/)
|
||||
end
|
||||
|
||||
context "for a product owner backlog" do
|
||||
let(:owner_backlog) { true }
|
||||
|
||||
describe "permission-based items" do
|
||||
context "with :add_work_packages and :assign_versions permission" do
|
||||
let(:permissions) { %i[view_sprints add_work_packages assign_versions] }
|
||||
|
||||
it "shows Add new story item with compose icon" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.new_story"))
|
||||
expect(page).to have_octicon(:compose)
|
||||
end
|
||||
end
|
||||
|
||||
context "with :add_work_packages but without :assign_versions permission" do
|
||||
let(:permissions) { %i[view_sprints add_work_packages] }
|
||||
|
||||
it "does not show Add new story item" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_no_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.new_story"))
|
||||
end
|
||||
end
|
||||
|
||||
context "with :assign_versions but without :add_work_packages permission" do
|
||||
let(:permissions) { %i[view_sprints assign_versions] }
|
||||
|
||||
it "does not show Add new story item" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_no_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.new_story"))
|
||||
end
|
||||
end
|
||||
|
||||
context "without :assign_versions but with :add_work_packages and :manage_sprint_items permission" do
|
||||
let(:permissions) { %i[view_sprints add_work_packages manage_sprint_items] }
|
||||
|
||||
it "does not show Add new story item" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_no_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.new_story"))
|
||||
end
|
||||
end
|
||||
|
||||
context "with :create_sprints permission" do
|
||||
let(:permissions) { %i[view_sprints create_sprints] }
|
||||
|
||||
it "shows Properties item with gear icon" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.properties"))
|
||||
expect(page).to have_octicon(:gear)
|
||||
end
|
||||
|
||||
it "shows Edit item with pencil icon" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_css("action-menu")
|
||||
expect(page).to have_text(I18n.t("backlogs.backlog_menu_component.action_menu.edit_sprint"))
|
||||
expect(page).to have_octicon(:pencil)
|
||||
end
|
||||
end
|
||||
|
||||
context "without :create_sprints permission" do
|
||||
let(:permissions) { [:view_sprints] }
|
||||
|
||||
it "does not show Properties item" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_no_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.properties"))
|
||||
end
|
||||
|
||||
it "does not show Edit item" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_no_text(I18n.t("backlogs.backlog_menu_component.action_menu.edit_sprint"))
|
||||
end
|
||||
end
|
||||
|
||||
context "with :view_sprints permission" do
|
||||
let(:permissions) { %i[view_sprints] }
|
||||
|
||||
it "does not show Task board item" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_no_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.task_board"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "permission independent items" do
|
||||
let(:permissions) { [:view_sprints] }
|
||||
|
||||
it "shows Stories/Tasks link" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.stories_tasks"))
|
||||
end
|
||||
|
||||
it "shows no Burndown chart link" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_no_text(I18n.t(:"backlogs.label_burndown_chart"))
|
||||
end
|
||||
end
|
||||
|
||||
describe "module-based items" do
|
||||
context "when wiki module is enabled" do
|
||||
let(:permissions) { [:view_sprints] }
|
||||
let(:project) { create(:project, types: [type_feature, type_task], enabled_module_names: %w[backlogs wiki]) }
|
||||
|
||||
it "does not show a Wiki item" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_no_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.wiki"))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "for a sprint backlog" do
|
||||
let(:owner_backlog) { false }
|
||||
|
||||
describe "permission-based items" do
|
||||
context "with :add_work_packages and :assign_versions permission" do
|
||||
let(:permissions) { %i[view_sprints add_work_packages assign_versions] }
|
||||
|
||||
it "shows Add new story item with compose icon" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.new_story"))
|
||||
expect(page).to have_octicon(:compose)
|
||||
end
|
||||
end
|
||||
|
||||
context "with :add_work_packages but without :assign_versions permission" do
|
||||
let(:permissions) { %i[view_sprints add_work_packages] }
|
||||
|
||||
it "does not show Add new story item" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_no_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.new_story"))
|
||||
end
|
||||
end
|
||||
|
||||
context "with :assign_versions but without :add_work_packages permission" do
|
||||
let(:permissions) { %i[view_sprints assign_versions] }
|
||||
|
||||
it "does not show Add new story item" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_no_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.new_story"))
|
||||
end
|
||||
end
|
||||
|
||||
context "without :assign_versions but with :add_work_packages and :manage_sprint_items permission" do
|
||||
let(:permissions) { %i[view_sprints add_work_packages manage_sprint_items] }
|
||||
|
||||
it "does not show Add new story item" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_no_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.new_story"))
|
||||
end
|
||||
end
|
||||
|
||||
context "with :create_sprints permission" do
|
||||
let(:permissions) { %i[view_sprints create_sprints] }
|
||||
|
||||
it "shows Properties item with gear icon" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.properties"))
|
||||
expect(page).to have_octicon(:gear)
|
||||
end
|
||||
|
||||
it "shows Edit item with pencil icon" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_css("action-menu")
|
||||
expect(page).to have_text(I18n.t("backlogs.backlog_menu_component.action_menu.edit_sprint"))
|
||||
expect(page).to have_octicon(:pencil)
|
||||
end
|
||||
end
|
||||
|
||||
context "without :create_sprints permission" do
|
||||
let(:permissions) { [:view_sprints] }
|
||||
|
||||
it "does not show Properties item" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_no_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.properties"))
|
||||
end
|
||||
|
||||
it "does not show Edit item" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_no_text(I18n.t("backlogs.backlog_menu_component.action_menu.edit_sprint"))
|
||||
end
|
||||
end
|
||||
|
||||
context "with :view_sprints permission" do
|
||||
let(:permissions) { %i[view_sprints] }
|
||||
|
||||
it "shows Task board item" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.task_board"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "permission independent items" do
|
||||
let(:permissions) { [:view_sprints] }
|
||||
|
||||
it "shows Stories/Tasks link" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.stories_tasks"))
|
||||
end
|
||||
|
||||
it "shows Burndown chart link" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_css("li", text: I18n.t(:"backlogs.label_burndown_chart"))
|
||||
end
|
||||
|
||||
context "when sprint has no burndown (no dates)" do
|
||||
let(:sprint) { create(:sprint, project:, name: "Sprint 1", start_date: nil, effective_date: nil) }
|
||||
|
||||
it "shows Burndown chart link as disabled" do
|
||||
render_component
|
||||
|
||||
burndown_item = page.find("li", text: I18n.t(:"backlogs.label_burndown_chart"))
|
||||
expect(burndown_item[:class]).to include("ActionListItem--disabled")
|
||||
end
|
||||
end
|
||||
|
||||
context "when sprint has burndown" do
|
||||
it "shows Burndown chart link as enabled" do
|
||||
render_component
|
||||
|
||||
burndown_item = page.find("li", text: I18n.t(:"backlogs.label_burndown_chart"))
|
||||
expect(burndown_item[:class]).not_to include("ActionListItem--disabled")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "module-based items" do
|
||||
context "when wiki module is enabled" do
|
||||
let(:permissions) { [:view_sprints] }
|
||||
let(:project) { create(:project, types: [type_feature, type_task], enabled_module_names: %w[backlogs wiki]) }
|
||||
|
||||
it "shows Wiki item" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.wiki"))
|
||||
expect(page).to have_octicon(:book)
|
||||
end
|
||||
end
|
||||
|
||||
context "when wiki module is disabled" do
|
||||
let(:permissions) { [:view_sprints] }
|
||||
let(:project) { create(:project, types: [type_feature, type_task], enabled_module_names: %w[backlogs]) }
|
||||
|
||||
it "does not show Wiki item" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_no_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.wiki"))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -49,7 +49,7 @@ RSpec.describe Backlogs::InboxItemComponent, type: :component do
|
||||
priority: default_priority,
|
||||
position: 1)
|
||||
end
|
||||
let(:work_packages) { WorkPackage.where(id: work_package.id).order(Arel.sql(Story::ORDER)) }
|
||||
let(:work_packages) { WorkPackage.where(id: work_package.id).order(:position, :id) }
|
||||
|
||||
before do
|
||||
render_inline(
|
||||
|
||||
@@ -44,10 +44,6 @@ RSpec.describe Backlogs::SprintComponent, type: :component do
|
||||
let(:sprint) { create(:agile_sprint, project:, name: "Sprint 1", start_date: Date.yesterday, finish_date: Date.tomorrow) }
|
||||
|
||||
before do
|
||||
allow(Setting)
|
||||
.to receive(:plugin_openproject_backlogs)
|
||||
.and_return("story_types" => [type_feature.id.to_s], "task_type" => type_task.id.to_s)
|
||||
|
||||
allow(user).to receive(:backlogs_preference).with(:versions_default_fold_state).and_return("open")
|
||||
end
|
||||
|
||||
@@ -90,7 +86,7 @@ RSpec.describe Backlogs::SprintComponent, type: :component do
|
||||
expect(page).to have_css(".Box#agile_sprint_#{sprint.id}")
|
||||
end
|
||||
|
||||
it "renders BacklogHeaderComponent in header" do
|
||||
it "renders SprintHeaderComponent in header" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_css(".Box-header h3", text: "Sprint 1")
|
||||
|
||||
@@ -45,12 +45,6 @@ RSpec.describe Backlogs::SprintHeaderComponent, type: :component do
|
||||
let(:state) { :show }
|
||||
let(:folded) { false }
|
||||
|
||||
before do
|
||||
allow(Setting)
|
||||
.to receive(:plugin_openproject_backlogs)
|
||||
.and_return("story_types" => [type_feature.id.to_s], "task_type" => type_task.id.to_s)
|
||||
end
|
||||
|
||||
def render_component(folded: false, active_sprint_ids: nil)
|
||||
render_inline(described_class.new(sprint:, project:, folded:, current_user: user, active_sprint_ids:))
|
||||
end
|
||||
@@ -118,7 +112,7 @@ RSpec.describe Backlogs::SprintHeaderComponent, type: :component do
|
||||
expect(page).to have_octicon(:"chevron-down", visible: :all)
|
||||
end
|
||||
|
||||
it "renders BacklogMenuComponent" do
|
||||
it "renders SprintMenuComponent" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_css("action-menu")
|
||||
|
||||
@@ -40,10 +40,6 @@ RSpec.describe Backlogs::SprintMenuComponent, type: :component do
|
||||
let(:permissions) { [] }
|
||||
|
||||
before do
|
||||
allow(Setting)
|
||||
.to receive(:plugin_openproject_backlogs)
|
||||
.and_return("story_types" => [type_feature.id.to_s], "task_type" => type_task.id.to_s)
|
||||
|
||||
create(:member,
|
||||
project:,
|
||||
principal: user,
|
||||
@@ -64,10 +60,11 @@ RSpec.describe Backlogs::SprintMenuComponent, type: :component do
|
||||
let(:permissions) { %i[view_sprints manage_sprint_items] }
|
||||
|
||||
it "shows Add new work package item with plus icon" do
|
||||
render_component
|
||||
rendered_component = render_component
|
||||
|
||||
expect(page).to have_text(I18n.t(:"backlogs.sprint_menu_component.action_menu.add_work_package"))
|
||||
expect(page).to have_octicon(:plus)
|
||||
expect(rendered_component.to_s).to include("sprint_id=#{sprint.id}")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -33,8 +33,8 @@ require "rails_helper"
|
||||
RSpec.describe Backlogs::SprintPageHeaderComponent, type: :component do
|
||||
let(:project) { create(:project, name: "Test Project") }
|
||||
let(:start_date) { Date.new(2024, 1, 15) }
|
||||
let(:effective_date) { Date.new(2024, 1, 29) }
|
||||
let(:sprint) { create(:sprint, project:, name: "Sprint 1", start_date:, effective_date:) }
|
||||
let(:finish_date) { Date.new(2024, 1, 29) }
|
||||
let(:sprint) { create(:agile_sprint, project:, name: "Sprint 1", start_date:, finish_date:) }
|
||||
|
||||
def render_component
|
||||
render_inline(described_class.new(sprint:, project:))
|
||||
@@ -89,7 +89,7 @@ RSpec.describe Backlogs::SprintPageHeaderComponent, type: :component do
|
||||
|
||||
describe "date handling" do
|
||||
context "when sprint has only start_date" do
|
||||
let(:sprint) { create(:sprint, project:, name: "Sprint 1", start_date:, effective_date: nil) }
|
||||
let(:sprint) { create(:agile_sprint, project:, name: "Sprint 1", start_date:, finish_date: nil) }
|
||||
|
||||
it "renders only start date" do
|
||||
render_component
|
||||
@@ -99,10 +99,10 @@ RSpec.describe Backlogs::SprintPageHeaderComponent, type: :component do
|
||||
end
|
||||
end
|
||||
|
||||
context "when sprint has only effective_date" do
|
||||
let(:sprint) { create(:sprint, project:, name: "Sprint 1", start_date: nil, effective_date:) }
|
||||
context "when sprint has only finish_date" do
|
||||
let(:sprint) { create(:agile_sprint, project:, name: "Sprint 1", start_date: nil, finish_date:) }
|
||||
|
||||
it "renders only effective date" do
|
||||
it "renders only finish date" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_no_css("time[datetime='2024-01-15']")
|
||||
@@ -111,7 +111,7 @@ RSpec.describe Backlogs::SprintPageHeaderComponent, type: :component do
|
||||
end
|
||||
|
||||
context "when sprint has no dates" do
|
||||
let(:sprint) { create(:sprint, project:, name: "Sprint 1", start_date: nil, effective_date: nil) }
|
||||
let(:sprint) { create(:agile_sprint, project:, name: "Sprint 1", start_date: nil, finish_date: nil) }
|
||||
|
||||
it "renders no time elements" do
|
||||
render_component
|
||||
|
||||
@@ -39,10 +39,10 @@ RSpec.describe Backlogs::StoryComponent, type: :component do
|
||||
current_user { user }
|
||||
|
||||
let(:project) { create(:project, types: [type_feature, type_task]) }
|
||||
let(:sprint) { create(:sprint, project:, name: "Sprint 1", start_date: Date.yesterday, effective_date: Date.tomorrow) }
|
||||
let(:sprint) { create(:agile_sprint, project:, name: "Sprint 1", start_date: Date.yesterday, finish_date: Date.tomorrow) }
|
||||
let(:story_points) { 5 }
|
||||
let(:story) do
|
||||
create(:story,
|
||||
create(:work_package,
|
||||
subject: "Test Story Subject",
|
||||
project:,
|
||||
type: type_feature,
|
||||
@@ -50,15 +50,11 @@ RSpec.describe Backlogs::StoryComponent, type: :component do
|
||||
priority: default_priority,
|
||||
story_points:,
|
||||
position: 1,
|
||||
version: sprint)
|
||||
sprint: sprint)
|
||||
end
|
||||
let(:permissions) { %i[manage_sprint_items] }
|
||||
|
||||
before do
|
||||
allow(Setting)
|
||||
.to receive(:plugin_openproject_backlogs)
|
||||
.and_return("story_types" => [type_feature.id.to_s], "task_type" => type_task.id.to_s)
|
||||
|
||||
mock_permissions_for(current_user) do |mock|
|
||||
mock.allow_in_project(*permissions, project:)
|
||||
end
|
||||
@@ -93,7 +89,7 @@ RSpec.describe Backlogs::StoryComponent, type: :component do
|
||||
|
||||
expect(page).to have_css("action-menu")
|
||||
expect(page).to have_css(%(include-fragment[src*="menu"]))
|
||||
expect(page).to have_element(:button, id: /\Astory_#{story.id}_menu-button\z/)
|
||||
expect(page).to have_element(:button, id: /\Awork_package_#{story.id}_menu-button\z/)
|
||||
end
|
||||
|
||||
describe "drag handle behaviour" do
|
||||
|
||||
@@ -39,11 +39,11 @@ RSpec.describe Backlogs::StoryMenuListComponent, type: :component do
|
||||
current_user { user }
|
||||
|
||||
let(:project) { create(:project, types: [type_feature, type_task]) }
|
||||
let(:sprint) { create(:sprint, project:, name: "Sprint 1", start_date: Date.yesterday, effective_date: Date.tomorrow) }
|
||||
let(:sprint) { create(:agile_sprint, project:, name: "Sprint 1", start_date: Date.yesterday, finish_date: Date.tomorrow) }
|
||||
let(:position) { 2 }
|
||||
let(:max_position) { 3 }
|
||||
let(:story) do
|
||||
create(:story,
|
||||
create(:work_package,
|
||||
subject: "Test Story",
|
||||
project:,
|
||||
type: type_feature,
|
||||
@@ -51,13 +51,7 @@ RSpec.describe Backlogs::StoryMenuListComponent, type: :component do
|
||||
priority: default_priority,
|
||||
story_points: 5,
|
||||
position:,
|
||||
version: sprint)
|
||||
end
|
||||
|
||||
before do
|
||||
allow(Setting)
|
||||
.to receive(:plugin_openproject_backlogs)
|
||||
.and_return("story_types" => [type_feature.id.to_s], "task_type" => type_task.id.to_s)
|
||||
sprint: sprint)
|
||||
end
|
||||
|
||||
def render_component(position: 2, max_position: 3)
|
||||
@@ -69,11 +63,11 @@ RSpec.describe Backlogs::StoryMenuListComponent, type: :component do
|
||||
it "renders stable ids for the list and primary actions" do
|
||||
render_component
|
||||
|
||||
expect(page).to have_element(:ul, id: /\Astory_#{story.id}_menu-list\z/)
|
||||
expect(page).to have_element(:a, id: /\Astory_#{story.id}_menu_open_details\z/)
|
||||
expect(page).to have_element(:a, id: /\Astory_#{story.id}_menu_open_fullscreen\z/)
|
||||
expect(page).to have_element(:"clipboard-copy", id: /\Astory_#{story.id}_menu_copy_url_to_clipboard\z/)
|
||||
expect(page).to have_element(:"clipboard-copy", id: /\Astory_#{story.id}_menu_copy_work_package_id\z/)
|
||||
expect(page).to have_element(:ul, id: /\Awork_package_#{story.id}_menu-list\z/)
|
||||
expect(page).to have_element(:a, id: /\Awork_package_#{story.id}_menu_open_details\z/)
|
||||
expect(page).to have_element(:a, id: /\Awork_package_#{story.id}_menu_open_fullscreen\z/)
|
||||
expect(page).to have_element(:"clipboard-copy", id: /\Awork_package_#{story.id}_menu_copy_url_to_clipboard\z/)
|
||||
expect(page).to have_element(:"clipboard-copy", id: /\Awork_package_#{story.id}_menu_copy_work_package_id\z/)
|
||||
end
|
||||
|
||||
it "shows Open details link (split view)" do
|
||||
@@ -104,7 +98,7 @@ RSpec.describe Backlogs::StoryMenuListComponent, type: :component do
|
||||
expect(page).to have_octicon(:copy)
|
||||
expect(page).to have_element(
|
||||
:"clipboard-copy",
|
||||
id: "story_#{story.id}_menu_copy_url_to_clipboard",
|
||||
id: "work_package_#{story.id}_menu_copy_url_to_clipboard",
|
||||
value: /\/work_packages\/#{story.id}\z/,
|
||||
text: "Copy URL to clipboard"
|
||||
)
|
||||
@@ -116,7 +110,7 @@ RSpec.describe Backlogs::StoryMenuListComponent, type: :component do
|
||||
expect(page).to have_octicon(:hash)
|
||||
expect(page).to have_element(
|
||||
:"clipboard-copy",
|
||||
id: "story_#{story.id}_menu_copy_work_package_id",
|
||||
id: "work_package_#{story.id}_menu_copy_work_package_id",
|
||||
value: story.id.to_s,
|
||||
text: "Copy work package ID"
|
||||
)
|
||||
|
||||
@@ -141,10 +141,6 @@ RSpec.describe WorkPackages::BaseContract, type: :model do
|
||||
.to receive(:relatable)
|
||||
.and_return(relatable_scope)
|
||||
|
||||
allow(Setting).to receive(:plugin_openproject_backlogs).and_return({ "points_burn_direction" => "down",
|
||||
"wiki_template" => "",
|
||||
"story_types" => [type_feature.id],
|
||||
"task_type" => type_task.id.to_s })
|
||||
end
|
||||
|
||||
describe "story_points" do
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
@@ -29,84 +31,22 @@
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe BacklogsSettingsController do
|
||||
current_user { build_stubbed(:admin) }
|
||||
let(:user) { create(:admin) }
|
||||
|
||||
before { login_as(user) }
|
||||
|
||||
describe "GET show" do
|
||||
it "performs that request" do
|
||||
it "renders successfully" do
|
||||
get :show
|
||||
expect(response).to be_successful
|
||||
expect(response).to render_template :show
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
|
||||
context "as regular user" do
|
||||
current_user { build_stubbed(:user) }
|
||||
context "when not an admin" do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
it "fails" do
|
||||
it "requires admin" do
|
||||
get :show
|
||||
expect(response).to have_http_status :forbidden
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "PUT update" do
|
||||
before do
|
||||
allow(Setting).to receive(:plugin_openproject_backlogs=)
|
||||
end
|
||||
|
||||
subject do
|
||||
put :update,
|
||||
params: {
|
||||
settings: {
|
||||
task_type:,
|
||||
story_types:
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
context "with invalid settings (Regression test #35157)" do
|
||||
let(:task_type) { "1234" }
|
||||
let(:story_types) { ["1234"] }
|
||||
|
||||
it "does not update the settings" do
|
||||
subject
|
||||
|
||||
expect(response).to render_template "show"
|
||||
expect(flash[:error]).to start_with I18n.t(:notice_unsuccessful_update_with_reason, reason: "")
|
||||
|
||||
expect(Setting).not_to have_received(:plugin_openproject_backlogs=).with(any_args)
|
||||
end
|
||||
end
|
||||
|
||||
context "with valid settings" do
|
||||
let(:task_type) { "1234" }
|
||||
let(:story_types) { ["5555"] }
|
||||
|
||||
it "does update the settings" do
|
||||
subject
|
||||
|
||||
expect(response).to redirect_to action: :show
|
||||
expect(flash[:notice]).to include I18n.t(:notice_successful_update)
|
||||
expect(flash[:error]).to be_nil
|
||||
|
||||
expect(Setting).to have_received(:plugin_openproject_backlogs=).with(
|
||||
points_burn_direction: nil,
|
||||
story_types: [5555],
|
||||
task_type: 1234,
|
||||
wiki_template: nil
|
||||
)
|
||||
end
|
||||
|
||||
context "with a non-admin" do
|
||||
current_user { build_stubbed(:user) }
|
||||
|
||||
it "does not update the settings" do
|
||||
subject
|
||||
|
||||
expect(response).not_to be_successful
|
||||
expect(response).to have_http_status :forbidden
|
||||
|
||||
expect(Setting).not_to have_received(:plugin_openproject_backlogs=).with(any_args)
|
||||
end
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
# 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.
|
||||
#++
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe RbSprintsController, "permissions" do
|
||||
let(:sprint_project) do
|
||||
create(:project, enabled_module_names: %w[work_package_tracking backlogs])
|
||||
end
|
||||
let(:sprint) { create(:sprint, project: sprint_project) }
|
||||
|
||||
let(:other_project) do
|
||||
create(:project, enabled_module_names: %w[work_package_tracking backlogs]).tap do |p|
|
||||
create(:member,
|
||||
user: current_user,
|
||||
roles: [create(:project_role, permissions: [:create_sprints])],
|
||||
project: p)
|
||||
end
|
||||
end
|
||||
|
||||
let(:current_user) { create(:user) }
|
||||
|
||||
before do
|
||||
allow(Setting).to receive(:plugin_openproject_backlogs)
|
||||
.and_return({ "story_types" => ["1"], "task_type" => "2" })
|
||||
login_as current_user
|
||||
end
|
||||
|
||||
describe "#update" do
|
||||
let(:original_name) { sprint.name }
|
||||
let(:new_name) { "a better name!" }
|
||||
|
||||
context "when the user has access to a different project but not the sprint's project" do
|
||||
it "does not allow updating the sprint via a foreign project_id" do
|
||||
original_name # memoize before request
|
||||
|
||||
patch :update,
|
||||
params: {
|
||||
project_id: other_project.id,
|
||||
id: sprint.id,
|
||||
sprint: {
|
||||
name: new_name
|
||||
}
|
||||
},
|
||||
format: :turbo_stream
|
||||
sprint.reload
|
||||
|
||||
expect(response).to have_http_status(:not_found)
|
||||
expect(sprint.name).to eq(original_name)
|
||||
end
|
||||
end
|
||||
|
||||
context "when the user has access to the sprint's own project" do
|
||||
before do
|
||||
create(:member,
|
||||
user: current_user,
|
||||
roles: [create(:project_role, permissions: %i[view_work_packages view_versions create_sprints])],
|
||||
project: sprint_project)
|
||||
end
|
||||
|
||||
it "allows updating the sprint" do
|
||||
skip "Incorrect permissions for updating Sprint"
|
||||
|
||||
patch :update,
|
||||
params: {
|
||||
project_id: sprint_project.id,
|
||||
id: sprint.id,
|
||||
sprint: {
|
||||
name: new_name
|
||||
}
|
||||
},
|
||||
format: :turbo_stream
|
||||
sprint.reload
|
||||
|
||||
expect(sprint.name).to eq(new_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -31,128 +31,6 @@
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe RbSprintsController do
|
||||
describe "inline name actions" do
|
||||
shared_let(:type_feature) { create(:type_feature) }
|
||||
shared_let(:type_task) { create(:type_task) }
|
||||
shared_let(:user) { create(:admin) }
|
||||
current_user { user }
|
||||
|
||||
let(:visible_projects_scope) { instance_double(ActiveRecord::Relation) }
|
||||
let(:visible_sprints_scope) { instance_double(ActiveRecord::Relation) }
|
||||
|
||||
before do
|
||||
allow(Setting)
|
||||
.to receive(:plugin_openproject_backlogs)
|
||||
.and_return({ "story_types" => [type_feature.id], "task_type" => type_task.id })
|
||||
|
||||
allow(Project)
|
||||
.to receive(:visible)
|
||||
.and_return(visible_projects_scope)
|
||||
|
||||
allow(visible_projects_scope)
|
||||
.to receive(:find)
|
||||
.with(project.identifier)
|
||||
.and_return(project)
|
||||
|
||||
allow(Sprint)
|
||||
.to receive(:visible)
|
||||
.and_return(visible_sprints_scope)
|
||||
|
||||
allow(visible_sprints_scope)
|
||||
.to receive(:find)
|
||||
.with(sprint.id.to_s)
|
||||
.and_return(sprint)
|
||||
end
|
||||
|
||||
describe "GET #edit_name" do
|
||||
let(:project) { build_stubbed(:project) }
|
||||
let(:sprint) { build_stubbed(:sprint) }
|
||||
|
||||
it "responds with success", :aggregate_failures do
|
||||
get :edit_name, params: { project_id: project.identifier, id: sprint.id }, format: :turbo_stream
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response).to have_http_status :ok
|
||||
expect(response).to have_turbo_stream action: "update", target: "backlogs-backlog-header-component-#{sprint.id}"
|
||||
assert_select %(turbo-stream[action="update"][target="backlogs-backlog-header-component-#{sprint.id}"][method="morph"])
|
||||
expect(assigns(:project)).to eq(project)
|
||||
expect(assigns(:sprint)).to eq(sprint)
|
||||
expect(assigns(:backlog)).to be_a(Backlog)
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET #show_name" do
|
||||
let(:project) { build_stubbed(:project) }
|
||||
let(:sprint) { build_stubbed(:sprint) }
|
||||
|
||||
it "responds with success", :aggregate_failures do
|
||||
get :show_name, params: { project_id: project.identifier, id: sprint.id }, format: :turbo_stream
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response).to have_http_status :ok
|
||||
expect(response).to have_turbo_stream action: "update", target: "backlogs-backlog-header-component-#{sprint.id}"
|
||||
assert_select %(turbo-stream[action="update"][target="backlogs-backlog-header-component-#{sprint.id}"][method="morph"])
|
||||
expect(assigns(:project)).to eq(project)
|
||||
expect(assigns(:sprint)).to eq(sprint)
|
||||
expect(assigns(:backlog)).to be_a(Backlog)
|
||||
end
|
||||
end
|
||||
|
||||
describe "PATCH #update" do
|
||||
let(:project) { build_stubbed(:project) }
|
||||
let(:sprint) { build_stubbed(:sprint) }
|
||||
|
||||
before do
|
||||
update_service = instance_double(Versions::UpdateService, call: service_result)
|
||||
|
||||
allow(Versions::UpdateService)
|
||||
.to receive(:new)
|
||||
.with(user:, model: sprint)
|
||||
.and_return(update_service)
|
||||
end
|
||||
|
||||
context "when service call succeeds" do
|
||||
let(:service_result) { ServiceResult.success(result: sprint) }
|
||||
|
||||
it "responds with success", :aggregate_failures do
|
||||
patch :update, params: { project_id: project.identifier, id: sprint.id, sprint: { name: "Updated Sprint" } },
|
||||
format: :turbo_stream
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response).to have_http_status :ok
|
||||
expect(response).to have_turbo_stream action: "update", target: "backlogs-backlog-header-component-#{sprint.id}"
|
||||
assert_select %(turbo-stream[action="update"][target="backlogs-backlog-header-component-#{sprint.id}"][method="morph"])
|
||||
expect(response).to have_turbo_stream action: "flash", target: "op-primer-flash-component"
|
||||
expect(assigns(:project)).to eq(project)
|
||||
expect(assigns(:sprint)).to eq(sprint)
|
||||
expect(assigns(:backlog)).to be_a(Backlog)
|
||||
end
|
||||
end
|
||||
|
||||
context "when service call fails" do
|
||||
let(:service_result) { ServiceResult.failure(result: sprint) }
|
||||
|
||||
before do
|
||||
project.name = ""
|
||||
end
|
||||
|
||||
it "responds with 422", :aggregate_failures do
|
||||
patch :update, params: { project_id: project.identifier, id: sprint.id, sprint: { name: "" } },
|
||||
format: :turbo_stream
|
||||
|
||||
expect(response).not_to be_successful
|
||||
expect(response).to have_http_status :unprocessable_entity
|
||||
expect(response).to have_turbo_stream action: "update", target: "backlogs-backlog-header-component-#{sprint.id}"
|
||||
assert_select %(turbo-stream[action="update"][target="backlogs-backlog-header-component-#{sprint.id}"][method="morph"])
|
||||
expect(response).to have_turbo_stream action: "flash", target: "op-primer-flash-component"
|
||||
expect(assigns(:project)).to eq(project)
|
||||
expect(assigns(:sprint)).to eq(sprint)
|
||||
expect(assigns(:backlog)).to be_a(Backlog)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "new actions" do
|
||||
shared_let(:type_feature) { create(:type_feature) }
|
||||
shared_let(:type_task) { create(:type_task) }
|
||||
@@ -166,13 +44,6 @@ RSpec.describe RbSprintsController do
|
||||
|
||||
current_user { user }
|
||||
|
||||
before do
|
||||
# Necessary to get the controller running due to check_if_plugin_is_configured
|
||||
allow(Setting)
|
||||
.to receive(:plugin_openproject_backlogs)
|
||||
.and_return({ "story_types" => [type_feature.id], "task_type" => type_task.id })
|
||||
end
|
||||
|
||||
describe "GET #new_dialog" do
|
||||
it "responds with success", :aggregate_failures do
|
||||
get :new_dialog, params: { project_id: project.id }, format: :turbo_stream
|
||||
@@ -301,156 +172,156 @@ RSpec.describe RbSprintsController do
|
||||
end
|
||||
|
||||
context "when the sprint is rendered in a receiving project" do
|
||||
let(:source_project) { create(:project, sprint_sharing: "share_all_projects") }
|
||||
let(:project) { create(:project, sprint_sharing: "receive_shared") }
|
||||
let!(:sprint) { create(:agile_sprint, project: source_project) }
|
||||
let(:source_permissions) { %i[view_sprints start_complete_sprint] }
|
||||
let!(:board) { create(:board_grid_with_query, project:, linked: sprint) }
|
||||
let(:source_project) { create(:project, sprint_sharing: "share_all_projects") }
|
||||
let(:project) { create(:project, sprint_sharing: "receive_shared") }
|
||||
let!(:sprint) { create(:agile_sprint, project: source_project) }
|
||||
let(:source_permissions) { %i[view_sprints start_complete_sprint] }
|
||||
let!(:board) { create(:board_grid_with_query, project:, linked: sprint) }
|
||||
|
||||
before do
|
||||
create(:member,
|
||||
project: source_project,
|
||||
principal: user,
|
||||
roles: [create(:project_role, permissions: source_permissions)])
|
||||
end
|
||||
|
||||
it "starts the sprint and redirects to the board", :aggregate_failures do
|
||||
post :start, format: :turbo_stream, params: request_params
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response).to have_turbo_stream(action: "redirect_to")
|
||||
expect(service).to have_received(:call)
|
||||
end
|
||||
|
||||
context "without source-project start permission" do
|
||||
let(:source_permissions) { %i[view_sprints] }
|
||||
|
||||
it "responds with forbidden and does not call the service", :aggregate_failures do
|
||||
post :start, params: request_params
|
||||
|
||||
expect(response).not_to be_successful
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
expect(service).not_to have_received(:call)
|
||||
end
|
||||
end
|
||||
|
||||
context "without rendered-project board access" do
|
||||
let(:permissions) { all_permissions - [:show_board_views] }
|
||||
|
||||
it "responds with forbidden and does not call the service", :aggregate_failures do
|
||||
post :start, params: request_params
|
||||
|
||||
expect(response).not_to be_successful
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
expect(service).not_to have_received(:call)
|
||||
end
|
||||
end
|
||||
before do
|
||||
create(:member,
|
||||
project: source_project,
|
||||
principal: user,
|
||||
roles: [create(:project_role, permissions: source_permissions)])
|
||||
end
|
||||
|
||||
context "when a board already exists" do
|
||||
let!(:existing_board) do
|
||||
create(:board_grid_with_query,
|
||||
project:,
|
||||
linked: sprint)
|
||||
end
|
||||
it "starts the sprint and redirects to the board", :aggregate_failures do
|
||||
post :start, format: :turbo_stream, params: request_params
|
||||
|
||||
it "starts the sprint and redirects to the board", :aggregate_failures do
|
||||
post :start, format: :turbo_stream, params: request_params
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response).to have_turbo_stream(action: "redirect_to")
|
||||
expect(service).to have_received(:call)
|
||||
end
|
||||
expect(response).to be_successful
|
||||
expect(response).to have_turbo_stream(action: "redirect_to")
|
||||
expect(service).to have_received(:call)
|
||||
end
|
||||
|
||||
context "when board creation succeeds" do
|
||||
let(:board) { create(:board_grid_with_query, project:, linked: sprint) }
|
||||
let(:service_result) do
|
||||
started_sprint = sprint.tap { it.status = "active" }
|
||||
allow(started_sprint).to receive(:task_board_for).with(project).and_return(board)
|
||||
context "without source-project start permission" do
|
||||
let(:source_permissions) { %i[view_sprints] }
|
||||
|
||||
ServiceResult.success(
|
||||
result: started_sprint
|
||||
)
|
||||
end
|
||||
|
||||
it "creates the board, starts the sprint, and redirects to the board", :aggregate_failures do
|
||||
post :start, format: :turbo_stream, params: request_params
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response).to have_turbo_stream(action: "redirect_to")
|
||||
expect(flash[:notice]).to eq(I18n.t(:notice_successful_start))
|
||||
expect(service).to have_received(:call)
|
||||
end
|
||||
end
|
||||
|
||||
context "when board creation fails" do
|
||||
let(:service_result) { ServiceResult.failure(message: "something went wrong") }
|
||||
|
||||
it "redirects back to the backlog and leaves the sprint in planning", :aggregate_failures do
|
||||
post :start, params: request_params
|
||||
|
||||
expect(response).to redirect_to(backlogs_project_backlogs_path(project))
|
||||
expect(flash[:alert]).to eq(
|
||||
I18n.t(:notice_unsuccessful_start_with_reason, reason: "something went wrong")
|
||||
)
|
||||
expect(sprint.reload).to be_in_planning
|
||||
end
|
||||
end
|
||||
|
||||
context "when sprint start fails without an explicit message" do
|
||||
let(:service_result) { ServiceResult.failure }
|
||||
|
||||
it "redirects back with the default start failure message", :aggregate_failures do
|
||||
post :start, params: request_params
|
||||
|
||||
expect(response).to redirect_to(backlogs_project_backlogs_path(project))
|
||||
expect(flash[:alert]).to eq(I18n.t(:notice_unsuccessful_start))
|
||||
expect(service).to have_received(:call)
|
||||
end
|
||||
end
|
||||
|
||||
context "when another sprint is already active" do
|
||||
let!(:active_sprint) { create(:agile_sprint, project:, status: "active") }
|
||||
let(:service_result) do
|
||||
ServiceResult.failure(
|
||||
result: sprint,
|
||||
message: sprint.errors.full_messages.to_sentence
|
||||
)
|
||||
end
|
||||
|
||||
it "redirects back to the backlog and leaves the sprint in planning", :aggregate_failures do
|
||||
post :start, params: request_params
|
||||
|
||||
expect(response).to redirect_to(backlogs_project_backlogs_path(project))
|
||||
expect(flash[:alert]).to eq(I18n.t(:notice_unsuccessful_start))
|
||||
expect(service).to have_received(:call)
|
||||
end
|
||||
end
|
||||
|
||||
context "without the 'start_complete_sprint' permission" do
|
||||
let(:permissions) { all_permissions - [:start_complete_sprint] }
|
||||
|
||||
it "responds with forbidden", :aggregate_failures do
|
||||
it "responds with forbidden and does not call the service", :aggregate_failures do
|
||||
post :start, params: request_params
|
||||
|
||||
expect(response).not_to be_successful
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
expect(service).not_to have_received(:call)
|
||||
end
|
||||
end
|
||||
|
||||
context "when the sprint is already active" do
|
||||
let!(:sprint) { create(:agile_sprint, project:, status: "active") }
|
||||
let(:service_result) { ServiceResult.failure }
|
||||
context "without rendered-project board access" do
|
||||
let(:permissions) { all_permissions - [:show_board_views] }
|
||||
|
||||
it "redirects back with the default start failure message", :aggregate_failures do
|
||||
it "responds with forbidden and does not call the service", :aggregate_failures do
|
||||
post :start, params: request_params
|
||||
|
||||
expect(response).to redirect_to(backlogs_project_backlogs_path(project))
|
||||
expect(flash[:alert]).to eq(I18n.t(:notice_unsuccessful_start))
|
||||
expect(service).to have_received(:call)
|
||||
expect(response).not_to be_successful
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
expect(service).not_to have_received(:call)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when a board already exists" do
|
||||
let!(:existing_board) do
|
||||
create(:board_grid_with_query,
|
||||
project:,
|
||||
linked: sprint)
|
||||
end
|
||||
|
||||
it "starts the sprint and redirects to the board", :aggregate_failures do
|
||||
post :start, format: :turbo_stream, params: request_params
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response).to have_turbo_stream(action: "redirect_to")
|
||||
expect(service).to have_received(:call)
|
||||
end
|
||||
end
|
||||
|
||||
context "when board creation succeeds" do
|
||||
let(:board) { create(:board_grid_with_query, project:, linked: sprint) }
|
||||
let(:service_result) do
|
||||
started_sprint = sprint.tap { it.status = "active" }
|
||||
allow(started_sprint).to receive(:task_board_for).with(project).and_return(board)
|
||||
|
||||
ServiceResult.success(
|
||||
result: started_sprint
|
||||
)
|
||||
end
|
||||
|
||||
it "creates the board, starts the sprint, and redirects to the board", :aggregate_failures do
|
||||
post :start, format: :turbo_stream, params: request_params
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response).to have_turbo_stream(action: "redirect_to")
|
||||
expect(flash[:notice]).to eq(I18n.t(:notice_successful_start))
|
||||
expect(service).to have_received(:call)
|
||||
end
|
||||
end
|
||||
|
||||
context "when board creation fails" do
|
||||
let(:service_result) { ServiceResult.failure(message: "something went wrong") }
|
||||
|
||||
it "redirects back to the backlog and leaves the sprint in planning", :aggregate_failures do
|
||||
post :start, params: request_params
|
||||
|
||||
expect(response).to redirect_to(backlogs_project_backlogs_path(project))
|
||||
expect(flash[:alert]).to eq(
|
||||
I18n.t(:notice_unsuccessful_start_with_reason, reason: "something went wrong")
|
||||
)
|
||||
expect(sprint.reload).to be_in_planning
|
||||
end
|
||||
end
|
||||
|
||||
context "when sprint start fails without an explicit message" do
|
||||
let(:service_result) { ServiceResult.failure }
|
||||
|
||||
it "redirects back with the default start failure message", :aggregate_failures do
|
||||
post :start, params: request_params
|
||||
|
||||
expect(response).to redirect_to(backlogs_project_backlogs_path(project))
|
||||
expect(flash[:alert]).to eq(I18n.t(:notice_unsuccessful_start))
|
||||
expect(service).to have_received(:call)
|
||||
end
|
||||
end
|
||||
|
||||
context "when another sprint is already active" do
|
||||
let!(:active_sprint) { create(:agile_sprint, project:, status: "active") }
|
||||
let(:service_result) do
|
||||
ServiceResult.failure(
|
||||
result: sprint,
|
||||
message: sprint.errors.full_messages.to_sentence
|
||||
)
|
||||
end
|
||||
|
||||
it "redirects back to the backlog and leaves the sprint in planning", :aggregate_failures do
|
||||
post :start, params: request_params
|
||||
|
||||
expect(response).to redirect_to(backlogs_project_backlogs_path(project))
|
||||
expect(flash[:alert]).to eq(I18n.t(:notice_unsuccessful_start))
|
||||
expect(service).to have_received(:call)
|
||||
end
|
||||
end
|
||||
|
||||
context "without the 'start_complete_sprint' permission" do
|
||||
let(:permissions) { all_permissions - [:start_complete_sprint] }
|
||||
|
||||
it "responds with forbidden", :aggregate_failures do
|
||||
post :start, params: request_params
|
||||
|
||||
expect(response).not_to be_successful
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context "when the sprint is already active" do
|
||||
let!(:sprint) { create(:agile_sprint, project:, status: "active") }
|
||||
let(:service_result) { ServiceResult.failure }
|
||||
|
||||
it "redirects back with the default start failure message", :aggregate_failures do
|
||||
post :start, params: request_params
|
||||
|
||||
expect(response).to redirect_to(backlogs_project_backlogs_path(project))
|
||||
expect(flash[:alert]).to eq(I18n.t(:notice_unsuccessful_start))
|
||||
expect(service).to have_received(:call)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST #finish" do
|
||||
@@ -471,43 +342,20 @@ RSpec.describe RbSprintsController do
|
||||
end
|
||||
|
||||
context "when the sprint is rendered in a receiving project" do
|
||||
let(:source_project) { create(:project, sprint_sharing: "share_all_projects") }
|
||||
let(:project) { create(:project, sprint_sharing: "receive_shared") }
|
||||
let!(:sprint) { create(:agile_sprint, project: source_project, status: "active") }
|
||||
let(:source_permissions) { %i[view_sprints start_complete_sprint] }
|
||||
let(:source_project) { create(:project, sprint_sharing: "share_all_projects") }
|
||||
let(:project) { create(:project, sprint_sharing: "receive_shared") }
|
||||
let!(:sprint) { create(:agile_sprint, project: source_project, status: "active") }
|
||||
let(:source_permissions) { %i[view_sprints start_complete_sprint] }
|
||||
|
||||
before do
|
||||
create(:member,
|
||||
project: source_project,
|
||||
principal: user,
|
||||
roles: [create(:project_role, permissions: source_permissions)])
|
||||
end
|
||||
|
||||
it "finishes the sprint and redirects to the backlog", :aggregate_failures do
|
||||
post :finish, params: request_params
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response.body).to include("action=\"redirect_to\"")
|
||||
expect(response.body).to include(backlogs_project_backlogs_path(project))
|
||||
expect(flash[:notice]).to eq(I18n.t(:notice_successful_finish))
|
||||
expect(service).to have_received(:call)
|
||||
end
|
||||
|
||||
context "without source-project start permission" do
|
||||
let(:source_permissions) { %i[view_sprints] }
|
||||
|
||||
it "responds with forbidden and does not call the service", :aggregate_failures do
|
||||
post :finish, params: request_params
|
||||
|
||||
expect(response).not_to be_successful
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
expect(service).not_to have_received(:call)
|
||||
end
|
||||
end
|
||||
before do
|
||||
create(:member,
|
||||
project: source_project,
|
||||
principal: user,
|
||||
roles: [create(:project_role, permissions: source_permissions)])
|
||||
end
|
||||
|
||||
it "finishes the sprint and redirects to the backlog via turbo stream", :aggregate_failures do
|
||||
post :finish, format: :turbo_stream, params: request_params
|
||||
it "finishes the sprint and redirects to the backlog", :aggregate_failures do
|
||||
post :finish, params: request_params
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response.body).to include("action=\"redirect_to\"")
|
||||
@@ -516,81 +364,104 @@ RSpec.describe RbSprintsController do
|
||||
expect(service).to have_received(:call)
|
||||
end
|
||||
|
||||
context "when finishing fails" do
|
||||
let(:service_result) { ServiceResult.failure(message: "something went wrong") }
|
||||
context "without source-project start permission" do
|
||||
let(:source_permissions) { %i[view_sprints] }
|
||||
|
||||
it "redirects back to the backlog", :aggregate_failures do
|
||||
post :finish, params: request_params
|
||||
|
||||
expect(response).to redirect_to(backlogs_project_backlogs_path(project))
|
||||
expect(flash[:alert]).to eq(
|
||||
I18n.t(:notice_unsuccessful_finish_with_reason, reason: "something went wrong")
|
||||
)
|
||||
expect(service).to have_received(:call)
|
||||
end
|
||||
end
|
||||
|
||||
context "when finishing fails without an explicit message" do
|
||||
let(:service_result) { ServiceResult.failure }
|
||||
|
||||
it "redirects back with the default finish failure message", :aggregate_failures do
|
||||
post :finish, params: request_params
|
||||
|
||||
expect(response).to redirect_to(backlogs_project_backlogs_path(project))
|
||||
expect(flash[:alert]).to eq(I18n.t(:notice_unsuccessful_finish))
|
||||
expect(service).to have_received(:call)
|
||||
end
|
||||
end
|
||||
|
||||
context "without the 'start_complete_sprint' permission" do
|
||||
let(:permissions) { all_permissions - [:start_complete_sprint] }
|
||||
|
||||
it "responds with forbidden", :aggregate_failures do
|
||||
it "responds with forbidden and does not call the service", :aggregate_failures do
|
||||
post :finish, params: request_params
|
||||
|
||||
expect(response).not_to be_successful
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
expect(service).not_to have_received(:call)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "finishes the sprint and redirects to the backlog via turbo stream", :aggregate_failures do
|
||||
post :finish, format: :turbo_stream, params: request_params
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response.body).to include("action=\"redirect_to\"")
|
||||
expect(response.body).to include(backlogs_project_backlogs_path(project))
|
||||
expect(flash[:notice]).to eq(I18n.t(:notice_successful_finish))
|
||||
expect(service).to have_received(:call)
|
||||
end
|
||||
|
||||
context "when finishing fails" do
|
||||
let(:service_result) { ServiceResult.failure(message: "something went wrong") }
|
||||
|
||||
it "redirects back to the backlog", :aggregate_failures do
|
||||
post :finish, params: request_params
|
||||
|
||||
expect(response).to redirect_to(backlogs_project_backlogs_path(project))
|
||||
expect(flash[:alert]).to eq(
|
||||
I18n.t(:notice_unsuccessful_finish_with_reason, reason: "something went wrong")
|
||||
)
|
||||
expect(service).to have_received(:call)
|
||||
end
|
||||
end
|
||||
|
||||
context "when finishing fails without an explicit message" do
|
||||
let(:service_result) { ServiceResult.failure }
|
||||
|
||||
it "redirects back with the default finish failure message", :aggregate_failures do
|
||||
post :finish, params: request_params
|
||||
|
||||
expect(response).to redirect_to(backlogs_project_backlogs_path(project))
|
||||
expect(flash[:alert]).to eq(I18n.t(:notice_unsuccessful_finish))
|
||||
expect(service).to have_received(:call)
|
||||
end
|
||||
end
|
||||
|
||||
context "without the 'start_complete_sprint' permission" do
|
||||
let(:permissions) { all_permissions - [:start_complete_sprint] }
|
||||
|
||||
it "responds with forbidden", :aggregate_failures do
|
||||
post :finish, params: request_params
|
||||
|
||||
expect(response).not_to be_successful
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context "when the sprint is already completed" do
|
||||
let!(:sprint) { create(:agile_sprint, project:, status: "completed") }
|
||||
let(:service_result) { ServiceResult.failure }
|
||||
let!(:sprint) { create(:agile_sprint, project:, status: "completed") }
|
||||
let(:service_result) { ServiceResult.failure }
|
||||
|
||||
it "redirects back with the default finish failure message", :aggregate_failures do
|
||||
post :finish, params: request_params
|
||||
it "redirects back with the default finish failure message", :aggregate_failures do
|
||||
post :finish, params: request_params
|
||||
|
||||
expect(response).to redirect_to(backlogs_project_backlogs_path(project))
|
||||
expect(flash[:alert]).to eq(I18n.t(:notice_unsuccessful_finish))
|
||||
expect(service).to have_received(:call)
|
||||
end
|
||||
expect(response).to redirect_to(backlogs_project_backlogs_path(project))
|
||||
expect(flash[:alert]).to eq(I18n.t(:notice_unsuccessful_finish))
|
||||
expect(service).to have_received(:call)
|
||||
end
|
||||
end
|
||||
|
||||
context "when moving to the top of the backlog" do
|
||||
let(:request_params) { { project_id: project.id, id: sprint.id, unfinished_action: "move_to_top_of_backlog" } }
|
||||
let(:request_params) { { project_id: project.id, id: sprint.id, unfinished_action: "move_to_top_of_backlog" } }
|
||||
|
||||
it "passes unfinished_action to the service and redirects via turbo stream", :aggregate_failures do
|
||||
post :finish, format: :turbo_stream, params: request_params
|
||||
it "passes unfinished_action to the service and redirects via turbo stream", :aggregate_failures do
|
||||
post :finish, format: :turbo_stream, params: request_params
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response.body).to include("action=\"redirect_to\"")
|
||||
expect(service).to have_received(:call)
|
||||
.with(hash_including(unfinished_action: "move_to_top_of_backlog"))
|
||||
end
|
||||
expect(response).to be_successful
|
||||
expect(response.body).to include("action=\"redirect_to\"")
|
||||
expect(service).to have_received(:call)
|
||||
.with(hash_including(unfinished_action: "move_to_top_of_backlog"))
|
||||
end
|
||||
end
|
||||
|
||||
context "when moving to the bottom of the backlog" do
|
||||
let(:request_params) { { project_id: project.id, id: sprint.id, unfinished_action: "move_to_bottom_of_backlog" } }
|
||||
let(:request_params) { { project_id: project.id, id: sprint.id, unfinished_action: "move_to_bottom_of_backlog" } }
|
||||
|
||||
it "passes unfinished_action to the service and redirects via turbo stream", :aggregate_failures do
|
||||
post :finish, format: :turbo_stream, params: request_params
|
||||
it "passes unfinished_action to the service and redirects via turbo stream", :aggregate_failures do
|
||||
post :finish, format: :turbo_stream, params: request_params
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response.body).to include("action=\"redirect_to\"")
|
||||
expect(service).to have_received(:call)
|
||||
.with(hash_including(unfinished_action: "move_to_bottom_of_backlog"))
|
||||
end
|
||||
expect(response).to be_successful
|
||||
expect(response.body).to include("action=\"redirect_to\"")
|
||||
expect(service).to have_received(:call)
|
||||
.with(hash_including(unfinished_action: "move_to_bottom_of_backlog"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET #refresh_form" do
|
||||
|
||||
@@ -39,17 +39,8 @@ RSpec.describe RbStoriesController do
|
||||
let(:user) { create(:admin) }
|
||||
let(:project) { create(:project) }
|
||||
let(:status) { create(:status, name: "status 1", is_default: true) }
|
||||
let(:version_sprint) { create(:sprint, project:) }
|
||||
let(:story) { create(:story, status:, version: version_sprint, project:) }
|
||||
|
||||
# Via this setting, version_sprint is used as backlog:
|
||||
let!(:version_setting) { create(:version_setting, version: version_sprint, project:, display: VersionSetting::DISPLAY_RIGHT) }
|
||||
|
||||
before do
|
||||
allow(Setting)
|
||||
.to receive(:plugin_openproject_backlogs)
|
||||
.and_return({ "story_types" => [type_feature.id], "task_type" => type_task.id })
|
||||
end
|
||||
let(:agile_sprint) { create(:agile_sprint, name: "Agile Sprint 1", project:) }
|
||||
let(:story) { create(:work_package, status:, sprint: agile_sprint, project:) }
|
||||
|
||||
describe "load_story" do
|
||||
subject do
|
||||
@@ -58,167 +49,38 @@ RSpec.describe RbStoriesController do
|
||||
format: :html
|
||||
end
|
||||
|
||||
context "when loading from a version sprint" do
|
||||
let(:load_story_id) { story.id }
|
||||
let(:requested_sprint) { version_sprint }
|
||||
let(:load_story_id) { story.id }
|
||||
|
||||
context "when the story is in the requested sprint" do
|
||||
it "assigns the visible story", :aggregate_failures do
|
||||
subject
|
||||
expect(response).to be_successful
|
||||
expect(response).to have_http_status :ok
|
||||
expect(assigns(:story)).to eq(story)
|
||||
end
|
||||
end
|
||||
|
||||
context "when the story is not in the requested sprint" do
|
||||
let(:requested_sprint) { create(:sprint, name: "Sprint load_story other", project:) }
|
||||
|
||||
it { is_expected.to have_http_status :not_found }
|
||||
end
|
||||
end
|
||||
|
||||
context "when loading from an agile sprint" do
|
||||
let(:agile_sprint) { create(:agile_sprint, name: "Agile Sprint load_story", project:) }
|
||||
let(:work_package_in_sprint) { create(:work_package, status:, sprint: agile_sprint, project:) }
|
||||
let(:load_story_id) { work_package_in_sprint.id }
|
||||
|
||||
context "when the work package is in the requested sprint" do
|
||||
let(:requested_sprint) { agile_sprint }
|
||||
|
||||
it "assigns the visible work package", :aggregate_failures do
|
||||
subject
|
||||
expect(response).to be_successful
|
||||
expect(response).to have_http_status :ok
|
||||
expect(assigns(:story)).to eq(work_package_in_sprint)
|
||||
end
|
||||
end
|
||||
|
||||
context "when the work package is not in the requested sprint" do
|
||||
let(:requested_sprint) { create(:agile_sprint, name: "Other Sprint load_story", project:) }
|
||||
|
||||
it { is_expected.to have_http_status :not_found }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "PUT #move_legacy" do
|
||||
context "with a user lacking project permission" do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
it "responds with 403" do
|
||||
put :move_legacy, params: {
|
||||
project_id: project.id,
|
||||
sprint_id: version_sprint.id,
|
||||
id: story.id,
|
||||
target_id: "foo",
|
||||
position: 1
|
||||
},
|
||||
format: :turbo_stream
|
||||
|
||||
expect(response).not_to be_successful
|
||||
expect(response).to have_http_status :not_found
|
||||
end
|
||||
end
|
||||
|
||||
context "with a version from the same project" do
|
||||
let(:other_version_sprint) { create(:sprint, name: "Sprint 2", project:) }
|
||||
|
||||
it "responds with success", :aggregate_failures do
|
||||
put :move_legacy, params: {
|
||||
project_id: project.id,
|
||||
sprint_id: version_sprint.id,
|
||||
id: story.id,
|
||||
target_id: "version:#{other_version_sprint.id}",
|
||||
position: 1
|
||||
},
|
||||
format: :turbo_stream
|
||||
context "when the work package is in the requested sprint" do
|
||||
let(:requested_sprint) { agile_sprint }
|
||||
|
||||
it "assigns the visible work package", :aggregate_failures do
|
||||
subject
|
||||
expect(response).to be_successful
|
||||
expect(response).to have_http_status :ok
|
||||
expect(response).to have_turbo_stream action: "replace", target: "backlogs-backlog-component-#{version_sprint.id}"
|
||||
expect(response).to have_turbo_stream action: "replace", target: "backlogs-backlog-component-#{other_version_sprint.id}"
|
||||
assert_select %(turbo-stream[action="replace"][target="backlogs-backlog-component-#{version_sprint.id}"][method="morph"])
|
||||
assert_select %(turbo-stream[action="replace"][target="backlogs-backlog-component-#{other_version_sprint.id}"][method="morph"]) # rubocop:disable Layout/LineLength
|
||||
expect(response).to have_turbo_stream action: "flash", target: "op-primer-flash-component"
|
||||
expect(assigns(:project)).to eq(project)
|
||||
expect(assigns(:sprint)).to eq(version_sprint)
|
||||
expect(assigns(:story)).to eq(story)
|
||||
expect(assigns(:backlog)).to be_a(Backlog)
|
||||
end
|
||||
end
|
||||
|
||||
context "with a version from another project" do
|
||||
let(:other_project) { create(:project) }
|
||||
let(:other_version_sprint) { create(:sprint, name: "Sprint 2", project: other_project, sharing: "system") }
|
||||
let(:story) { create(:story, status:, version: other_version_sprint, project:) }
|
||||
context "when the work package is not in the requested sprint" do
|
||||
let(:requested_sprint) { create(:agile_sprint, name: "Other Sprint load_story", project:) }
|
||||
|
||||
it "responds with success", :aggregate_failures do
|
||||
put :move_legacy, params: {
|
||||
project_id: project.id,
|
||||
sprint_id: other_version_sprint.id,
|
||||
id: story.id,
|
||||
target_id: "version:#{version_sprint.id}",
|
||||
position: 1
|
||||
},
|
||||
format: :turbo_stream
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response).to have_http_status :ok
|
||||
expect(response).to have_turbo_stream action: "replace", target: "backlogs-backlog-component-#{other_version_sprint.id}"
|
||||
expect(response).to have_turbo_stream action: "replace", target: "backlogs-backlog-component-#{version_sprint.id}"
|
||||
assert_select %(turbo-stream[action="replace"][target="backlogs-backlog-component-#{other_version_sprint.id}"][method="morph"]) # rubocop:disable Layout/LineLength
|
||||
assert_select %(turbo-stream[action="replace"][target="backlogs-backlog-component-#{version_sprint.id}"][method="morph"])
|
||||
expect(response).to have_turbo_stream action: "flash", target: "op-primer-flash-component"
|
||||
expect(assigns(:project)).to eq(project)
|
||||
expect(assigns(:sprint)).to eq(other_version_sprint)
|
||||
expect(assigns(:story)).to eq(story)
|
||||
expect(assigns(:backlog)).to be_a(Backlog)
|
||||
end
|
||||
end
|
||||
|
||||
context "when service call fails" do
|
||||
let(:other_version_sprint) { create(:sprint, name: "Sprint 2", project:) }
|
||||
let(:service_result) { ServiceResult.failure(message: "Something went wrong") }
|
||||
|
||||
before do
|
||||
update_service = instance_double(Stories::UpdateService, call: service_result)
|
||||
|
||||
allow(Stories::UpdateService)
|
||||
.to receive(:new)
|
||||
.and_return(update_service)
|
||||
end
|
||||
|
||||
it "renders an error flash with 422", :aggregate_failures do
|
||||
put :move_legacy, params: {
|
||||
project_id: project.id,
|
||||
sprint_id: version_sprint.id,
|
||||
id: story.id,
|
||||
target_id: "version:#{other_version_sprint.id}",
|
||||
position: 1
|
||||
},
|
||||
format: :turbo_stream
|
||||
|
||||
expect(response).to have_http_status :unprocessable_entity
|
||||
expect(response).to have_turbo_stream action: "flash", target: "op-primer-flash-component"
|
||||
expect(response).not_to have_turbo_stream action: "replace", target: "backlogs-backlog-component-#{version_sprint.id}"
|
||||
end
|
||||
it { is_expected.to have_http_status :not_found }
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST #reorder" do
|
||||
it "responds with success", :aggregate_failures do
|
||||
post :reorder, params: { project_id: project.id, sprint_id: version_sprint.id, id: story.id, direction: "highest" },
|
||||
post :reorder, params: { project_id: project.id, sprint_id: agile_sprint.id, id: story.id, direction: "highest" },
|
||||
format: :turbo_stream
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response).to have_http_status :ok
|
||||
expect(response).to have_turbo_stream action: "replace", target: "backlogs-backlog-component-#{version_sprint.id}"
|
||||
assert_select %(turbo-stream[action="replace"][target="backlogs-backlog-component-#{version_sprint.id}"][method="morph"])
|
||||
expect(response).to have_turbo_stream action: "replace", target: "backlogs-sprint-component-#{agile_sprint.id}"
|
||||
assert_select %(turbo-stream[action="replace"][target="backlogs-sprint-component-#{agile_sprint.id}"][method="morph"])
|
||||
expect(assigns(:project)).to eq(project)
|
||||
expect(assigns(:sprint)).to eq(version_sprint)
|
||||
expect(assigns(:sprint)).to eq(agile_sprint)
|
||||
expect(assigns(:story)).to eq(story)
|
||||
expect(assigns(:backlog)).to be_a(Backlog)
|
||||
end
|
||||
|
||||
context "when service call fails" do
|
||||
@@ -233,18 +95,17 @@ RSpec.describe RbStoriesController do
|
||||
end
|
||||
|
||||
it "renders an error flash with 422", :aggregate_failures do
|
||||
post :reorder, params: { project_id: project.id, sprint_id: version_sprint.id, id: story.id, direction: "highest" },
|
||||
post :reorder, params: { project_id: project.id, sprint_id: agile_sprint.id, id: story.id, direction: "highest" },
|
||||
format: :turbo_stream
|
||||
|
||||
expect(response).to have_http_status :unprocessable_entity
|
||||
expect(response).to have_turbo_stream action: "flash", target: "op-primer-flash-component"
|
||||
expect(response).not_to have_turbo_stream action: "replace", target: "backlogs-backlog-component-#{version_sprint.id}"
|
||||
expect(response).not_to have_turbo_stream action: "replace", target: "backlogs-sprint-component-#{agile_sprint.id}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "PUT #move" do
|
||||
let(:agile_sprint) { create(:agile_sprint, name: "Agile Sprint 1", project:) }
|
||||
let(:story_in_agile_sprint) { create(:work_package, status:, sprint: agile_sprint, project:) }
|
||||
|
||||
context "with another Agile::Sprint as target" do
|
||||
@@ -271,62 +132,6 @@ RSpec.describe RbStoriesController do
|
||||
expect(assigns(:sprint)).to eq(agile_sprint)
|
||||
expect(assigns(:story)).to eq(story_in_agile_sprint)
|
||||
end
|
||||
|
||||
context "when the story has a version that is not used as backlog" do
|
||||
let(:story_in_agile_sprint) { create(:work_package, status:, sprint: agile_sprint, version: version_sprint, project:) }
|
||||
# Via this setting, version_sprint is NOT used as backlog:
|
||||
let!(:version_setting) { create(:version_setting, version: version_sprint, project:, display: VersionSetting::DISPLAY_NONE) }
|
||||
|
||||
it "responds with success and moves story to Agile::Sprint, keeping the version", :aggregate_failures do
|
||||
put :move, params: {
|
||||
project_id: project.id,
|
||||
sprint_id: agile_sprint.id,
|
||||
id: story_in_agile_sprint.id,
|
||||
target_id: "sprint:#{other_agile_sprint.id}",
|
||||
prev_id: nil
|
||||
},
|
||||
format: :turbo_stream
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response).to have_http_status :ok
|
||||
expect(response).to have_turbo_stream action: "replace", target: "backlogs-sprint-component-#{agile_sprint.id}"
|
||||
expect(response).to have_turbo_stream action: "replace", target: "backlogs-sprint-component-#{other_agile_sprint.id}"
|
||||
assert_select %(turbo-stream[action="replace"][target="backlogs-sprint-component-#{agile_sprint.id}"])
|
||||
assert_select %(turbo-stream[action="replace"][target="backlogs-sprint-component-#{other_agile_sprint.id}"])
|
||||
expect(response).to have_turbo_stream action: "flash", target: "op-primer-flash-component"
|
||||
expect(assigns(:project)).to eq(project)
|
||||
expect(assigns(:sprint)).to eq(agile_sprint)
|
||||
expect(assigns(:story)).to eq(story_in_agile_sprint)
|
||||
|
||||
# It will preserve the version since it is not used as backlog/sprint.
|
||||
expect(story_in_agile_sprint.reload.version).to eq(version_sprint)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with a Sprint (Version) as target" do
|
||||
it "responds with success and moves story to Sprint", :aggregate_failures do
|
||||
put :move, params: {
|
||||
project_id: project.id,
|
||||
sprint_id: agile_sprint.id,
|
||||
id: story_in_agile_sprint.id,
|
||||
target_id: "version:#{version_sprint.id}",
|
||||
prev_id: nil
|
||||
},
|
||||
format: :turbo_stream
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response).to have_http_status :ok
|
||||
expect(response).to have_turbo_stream action: "replace", target: "backlogs-sprint-component-#{agile_sprint.id}"
|
||||
expect(response).to have_turbo_stream action: "replace", target: "backlogs-backlog-component-#{version_sprint.id}"
|
||||
assert_select %(turbo-stream[action="replace"][target="backlogs-sprint-component-#{agile_sprint.id}"])
|
||||
assert_select %(turbo-stream[action="replace"][target="backlogs-backlog-component-#{version_sprint.id}"][method="morph"])
|
||||
expect(response).to have_turbo_stream action: "flash", target: "op-primer-flash-component"
|
||||
expect(assigns(:project)).to eq(project)
|
||||
expect(assigns(:sprint)).to eq(agile_sprint)
|
||||
expect(assigns(:story)).to eq(story_in_agile_sprint)
|
||||
expect(assigns(:backlog)).to be_a(Backlog)
|
||||
end
|
||||
end
|
||||
|
||||
context "with Inbox as target" do
|
||||
@@ -388,7 +193,7 @@ RSpec.describe RbStoriesController do
|
||||
|
||||
describe "GET #menu" do
|
||||
subject do
|
||||
get :menu, params: { project_id: project.id, sprint_id: version_sprint.id, id: story.id }, format: :html
|
||||
get :menu, params: { project_id: project.id, sprint_id: agile_sprint.id, id: story.id }, format: :html
|
||||
end
|
||||
|
||||
it "returns deferred action menu list HTML", :aggregate_failures do
|
||||
|
||||
@@ -41,12 +41,6 @@ RSpec.describe RbTaskboardsController do
|
||||
|
||||
current_user { user }
|
||||
|
||||
before do
|
||||
allow(Setting)
|
||||
.to receive(:plugin_openproject_backlogs)
|
||||
.and_return({ "story_types" => [type_feature.id], "task_type" => type_task.id })
|
||||
end
|
||||
|
||||
describe "GET show" do
|
||||
let(:sprint) { create(:agile_sprint, project:) }
|
||||
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
# 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.
|
||||
#++
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe "Backlogs Admin Settings", :js do
|
||||
let!(:type1) { create(:type, name: "Story", position: 1) }
|
||||
let!(:type2) { create(:type_feature, position: 2) }
|
||||
let!(:type3) { create(:type_task, position: 3) }
|
||||
let!(:type4) { create(:type_milestone, position: 4) }
|
||||
|
||||
let(:story_autocompleter) { FormFields::Primerized::AutocompleteField.new("story_types", selector: "[data-test-selector='story_type_autocomplete']") }
|
||||
let(:task_autocompleter) { FormFields::Primerized::AutocompleteField.new("story_types", selector: "[data-test-selector='task_type_autocomplete']") }
|
||||
|
||||
let(:current_user) { create(:admin) }
|
||||
|
||||
before do
|
||||
login_as current_user
|
||||
|
||||
visit admin_backlogs_settings_path
|
||||
end
|
||||
|
||||
it "shows the sprint planning blankslate instead of legacy configuration" do
|
||||
expect(page).to have_no_field "Template for sprint wiki page"
|
||||
expect(page).to have_no_css "[data-test-selector='story_type_autocomplete']"
|
||||
expect(page).to have_no_css "[data-test-selector='task_type_autocomplete']"
|
||||
expect(page).to have_no_css "fieldset", text: "Points burn up/down"
|
||||
|
||||
expect(page).to have_content "Backlog admin settings are evolving"
|
||||
end
|
||||
end
|
||||
@@ -1,160 +0,0 @@
|
||||
# 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.
|
||||
#++
|
||||
|
||||
require "spec_helper"
|
||||
require_relative "../../support/pages/backlogs"
|
||||
|
||||
RSpec.describe "Backlogs context menu", :js do
|
||||
shared_let(:story_type) { create(:type_feature) }
|
||||
shared_let(:task_type) { create(:type_task) }
|
||||
shared_let(:project) { create(:project, types: [story_type, task_type]) }
|
||||
shared_let(:user) do
|
||||
create(:user,
|
||||
member_with_permissions: { project => %i[add_work_packages
|
||||
view_sprints
|
||||
view_work_packages
|
||||
assign_versions] })
|
||||
end
|
||||
shared_let(:sprint) do
|
||||
create(:version,
|
||||
project:,
|
||||
name: "Sprint",
|
||||
start_date: Date.yesterday,
|
||||
effective_date: Date.tomorrow)
|
||||
end
|
||||
shared_let(:default_status) { create(:default_status) }
|
||||
shared_let(:default_priority) { create(:default_priority) }
|
||||
shared_let(:story) do
|
||||
create(:work_package,
|
||||
type: story_type,
|
||||
project:,
|
||||
status: default_status,
|
||||
priority: default_priority,
|
||||
position: 1,
|
||||
story_points: 3,
|
||||
version: sprint)
|
||||
end
|
||||
|
||||
before do
|
||||
allow(Setting)
|
||||
.to receive(:plugin_openproject_backlogs)
|
||||
.and_return("story_types" => [story_type.id.to_s],
|
||||
"task_type" => task_type.id.to_s)
|
||||
login_as(user)
|
||||
end
|
||||
|
||||
let(:backlogs_page) { Pages::Backlogs.new(project) }
|
||||
|
||||
def within_backlog_context_menu(&)
|
||||
backlogs_page.visit!
|
||||
backlogs_page.within_backlog_menu(sprint, &)
|
||||
end
|
||||
|
||||
context "when the backlog is a sprint backlog (displayed on the left, the default)" do
|
||||
it "displays all menu entries" do
|
||||
within_backlog_context_menu do |menu|
|
||||
expect(menu).to have_selector :menuitem, count: 5
|
||||
expect(menu).to have_selector :menuitem, "New story"
|
||||
expect(menu).to have_selector :menuitem, "Stories/Tasks"
|
||||
expect(menu).to have_selector :menuitem, "Task board"
|
||||
expect(menu).to have_selector :menuitem, "Burndown chart"
|
||||
expect(menu).to have_selector :menuitem, "Wiki"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when the backlog is an owner backlog (displayed on the right)" do
|
||||
let!(:version_setting) do
|
||||
create(:version_setting,
|
||||
project:,
|
||||
version: sprint,
|
||||
display: VersionSetting::DISPLAY_RIGHT)
|
||||
end
|
||||
|
||||
it "only displays 2 menu entries" do
|
||||
within_backlog_context_menu do |menu|
|
||||
expect(menu).to have_selector :menuitem, count: 2
|
||||
expect(menu).to have_selector :menuitem, "New story"
|
||||
expect(menu).to have_selector :menuitem, "Stories/Tasks"
|
||||
expect(menu).to have_no_selector :menuitem, "Task board"
|
||||
expect(menu).to have_no_selector :menuitem, "Burndown chart"
|
||||
expect(menu).to have_no_selector :menuitem, "Wiki"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when the sprint does not have a start date" do
|
||||
before do
|
||||
sprint.update(start_date: nil)
|
||||
end
|
||||
|
||||
it 'disables the "Burndown chart" menu entry' do
|
||||
within_backlog_context_menu do |menu|
|
||||
expect(menu).to have_selector :menuitem, "Burndown chart", disabled: true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when the sprint does not have an effective date" do
|
||||
before do
|
||||
sprint.update(effective_date: nil)
|
||||
end
|
||||
|
||||
it 'disables the "Burndown chart" menu entry' do
|
||||
within_backlog_context_menu do |menu|
|
||||
expect(menu).to have_selector :menuitem, "Burndown chart", disabled: true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when the user does not have assign_versions permission" do
|
||||
before do
|
||||
RolePermission.where(permission: "assign_versions").delete_all
|
||||
end
|
||||
|
||||
it 'does not display the "New story" menu entry' do
|
||||
within_backlog_context_menu do |menu|
|
||||
expect(menu).to have_no_selector :menuitem, "New story"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when the wiki module is not enabled" do
|
||||
before do
|
||||
project.enabled_module_names -= ["wiki"]
|
||||
end
|
||||
|
||||
it 'does not display the "Wiki" menu entry' do
|
||||
within_backlog_context_menu do |menu|
|
||||
expect(menu).to have_no_selector :menuitem, "Wiki"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,151 +0,0 @@
|
||||
#-- 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.
|
||||
#++
|
||||
|
||||
require "spec_helper"
|
||||
require_relative "../../support/pages/backlogs"
|
||||
|
||||
RSpec.describe "Backlogs", :js do
|
||||
let(:story_type) do
|
||||
create(:type_feature)
|
||||
end
|
||||
let(:story_type2) do
|
||||
type = create(:type)
|
||||
|
||||
project.types << type
|
||||
|
||||
type
|
||||
end
|
||||
let(:inactive_story_type) do
|
||||
create(:type)
|
||||
end
|
||||
|
||||
let(:task_type) do
|
||||
type = create(:type_task)
|
||||
project.types << type
|
||||
|
||||
type
|
||||
end
|
||||
|
||||
let(:user) do
|
||||
create(:user,
|
||||
member_with_permissions: { project => %i(add_work_packages
|
||||
view_sprints
|
||||
view_work_packages
|
||||
assign_versions) })
|
||||
end
|
||||
let(:project) { create(:project) }
|
||||
|
||||
let(:backlog_version) { create(:version, project:) }
|
||||
|
||||
let!(:existing_story1) do
|
||||
create(:work_package,
|
||||
type: story_type,
|
||||
project:,
|
||||
status: default_status,
|
||||
priority: default_priority,
|
||||
position: 1,
|
||||
story_points: 3,
|
||||
version: backlog_version)
|
||||
end
|
||||
let!(:existing_story2) do
|
||||
create(:work_package,
|
||||
type: story_type,
|
||||
project:,
|
||||
status: default_status,
|
||||
priority: default_priority,
|
||||
position: 2,
|
||||
story_points: 4,
|
||||
version: backlog_version)
|
||||
end
|
||||
let!(:default_status) do
|
||||
create(:default_status)
|
||||
end
|
||||
let!(:default_priority) do
|
||||
create(:default_priority)
|
||||
end
|
||||
|
||||
let(:backlogs_page) { Pages::Backlogs.new(project) }
|
||||
|
||||
before do
|
||||
login_as(user)
|
||||
|
||||
allow(Setting)
|
||||
.to receive(:plugin_openproject_backlogs)
|
||||
.and_return("story_types" => [story_type.id.to_s,
|
||||
story_type2.id.to_s,
|
||||
inactive_story_type.id.to_s],
|
||||
"task_type" => task_type.id.to_s)
|
||||
end
|
||||
|
||||
it "allows creating a new story" do
|
||||
backlogs_page.visit!
|
||||
|
||||
backlogs_page.click_in_backlog_menu(backlog_version, "New story")
|
||||
|
||||
within_dialog "New work package" do
|
||||
fill_in "Subject", with: "The new story"
|
||||
# TODO: removed in OP #57688, to be reimplemented
|
||||
# fill_in "Story Points", with: "5"
|
||||
|
||||
select_combo_box_option story_type2.name, from: "Type"
|
||||
|
||||
# saving the new story
|
||||
click_on "Create"
|
||||
end
|
||||
|
||||
expect_and_dismiss_flash type: :success, exact_message: "Successful creation."
|
||||
|
||||
# velocity should be summed up immediately
|
||||
# TODO: removed in OP #57688, to be reimplemented
|
||||
# xpect(page).to have_css(".velocity", text: "12")
|
||||
|
||||
# this will ensure that the page refresh is through before we check the order
|
||||
backlogs_page.click_in_backlog_menu(backlog_version, "New story")
|
||||
|
||||
within_dialog "New work package" do
|
||||
fill_in "Subject", with: "Another story"
|
||||
end
|
||||
|
||||
# the order is kept even after a page refresh -> it is persisted in the db
|
||||
page.driver.refresh
|
||||
|
||||
expect(page)
|
||||
.to have_no_content "Another story"
|
||||
|
||||
new_story = WorkPackage.find_by(subject: "The new story")
|
||||
|
||||
# stories are ordered by position (ASC), with NULL positions at the end ordered by ID
|
||||
# existing stories have positions 1 and 2, new story has no position so appears at end
|
||||
backlogs_page.expect_stories_in_order(backlog_version, existing_story1, existing_story2, new_story)
|
||||
|
||||
# created with the selected type (HighlightedTypeComponent renders type name in uppercase)
|
||||
within("#story_#{new_story.id}") do
|
||||
expect(page).to have_text(story_type2.name.upcase)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -32,7 +32,9 @@ require "spec_helper"
|
||||
require_relative "../../support/pages/backlog"
|
||||
require_relative "../../../../boards/spec/features/support/board_page"
|
||||
|
||||
RSpec.describe "Start and finish sprints", :js do
|
||||
RSpec.describe "Start and finish sprints",
|
||||
:js,
|
||||
with_ee: %i[board_view] do
|
||||
shared_let(:project) do
|
||||
create(:project, enabled_module_names: %i[backlogs work_package_tracking board_view])
|
||||
end
|
||||
@@ -47,6 +49,7 @@ RSpec.describe "Start and finish sprints", :js do
|
||||
create(:user, member_with_permissions: { project => permissions })
|
||||
end
|
||||
let(:planning_page) { Pages::Backlog.new(project) }
|
||||
let(:story_type) { create(:type_feature) }
|
||||
let(:task_type) do
|
||||
type = create(:type_task)
|
||||
project.types << type
|
||||
|
||||
@@ -1,266 +0,0 @@
|
||||
#-- 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.
|
||||
#++
|
||||
|
||||
require "spec_helper"
|
||||
require_relative "../support/pages/backlogs"
|
||||
|
||||
RSpec.describe "Backlogs in backlog view", :js do
|
||||
let!(:project) do
|
||||
create(:project,
|
||||
types: [story, task],
|
||||
enabled_module_names: %w(work_package_tracking backlogs))
|
||||
end
|
||||
let!(:story) { create(:type_feature) }
|
||||
let!(:other_story) { create(:type) }
|
||||
let!(:task) { create(:type_task) }
|
||||
let!(:priority) { create(:default_priority) }
|
||||
let!(:default_status) { create(:status, is_default: true) }
|
||||
let!(:other_status) { create(:status) }
|
||||
let!(:workflows) do
|
||||
create(:workflow,
|
||||
old_status: default_status,
|
||||
new_status: other_status,
|
||||
role:,
|
||||
type_id: story.id)
|
||||
end
|
||||
let(:role) do
|
||||
create(:project_role,
|
||||
permissions: %i(
|
||||
view_project
|
||||
view_sprints
|
||||
create_sprints
|
||||
manage_sprint_items
|
||||
add_work_packages
|
||||
view_work_packages
|
||||
edit_work_packages
|
||||
manage_subtasks
|
||||
manage_versions
|
||||
))
|
||||
end
|
||||
let!(:current_user) do
|
||||
create(:user,
|
||||
member_with_roles: { project => role })
|
||||
end
|
||||
let!(:sprint) do
|
||||
create(:version,
|
||||
project:,
|
||||
start_date: 10.days.ago,
|
||||
effective_date: 10.days.from_now,
|
||||
version_settings_attributes: [{ project:, display: VersionSetting::DISPLAY_LEFT }])
|
||||
end
|
||||
let!(:backlog) do
|
||||
create(:version,
|
||||
project:,
|
||||
version_settings_attributes: [{ project:, display: VersionSetting::DISPLAY_RIGHT }])
|
||||
end
|
||||
let!(:other_project) do
|
||||
create(:project, member_with_roles: { current_user => role })
|
||||
end
|
||||
let!(:other_project_sprint) do
|
||||
create(:version,
|
||||
project: other_project,
|
||||
sharing: "system",
|
||||
start_date: 10.days.ago,
|
||||
effective_date: 10.days.from_now)
|
||||
end
|
||||
let!(:sprint_story1) do
|
||||
create(:work_package,
|
||||
project:,
|
||||
type: story,
|
||||
status: default_status,
|
||||
version: sprint,
|
||||
position: 1,
|
||||
story_points: 10)
|
||||
end
|
||||
let(:backlogs_page) { Pages::Backlogs.new(project) }
|
||||
|
||||
before do
|
||||
login_as current_user
|
||||
allow(Setting)
|
||||
.to receive(:plugin_openproject_backlogs)
|
||||
.and_return("story_types" => [story.id.to_s],
|
||||
"task_type" => task.id.to_s)
|
||||
end
|
||||
|
||||
it "displays stories which are editable" do
|
||||
backlogs_page.visit!
|
||||
|
||||
backlogs_page
|
||||
.expect_sprint(sprint)
|
||||
|
||||
# Shared versions are also displayed as a sprint.
|
||||
# Without version settings, it is displayed as a sprint
|
||||
backlogs_page
|
||||
.expect_sprint(other_project_sprint)
|
||||
|
||||
backlogs_page
|
||||
.expect_backlog(backlog)
|
||||
|
||||
# Versions can be folded
|
||||
backlogs_page
|
||||
.expect_story_in_backlog(sprint_story1, sprint)
|
||||
|
||||
backlogs_page
|
||||
.fold_backlog(sprint)
|
||||
|
||||
backlogs_page
|
||||
.expect_story_not_in_backlog(sprint_story1, sprint)
|
||||
|
||||
# The backlogs can be folded by default
|
||||
visit my_interface_path
|
||||
|
||||
check "Show sprints folded"
|
||||
|
||||
click_button "Update backlogs module"
|
||||
expect_and_dismiss_flash(message: "Account was successfully updated.")
|
||||
|
||||
backlogs_page.visit!
|
||||
|
||||
backlogs_page
|
||||
.expect_story_not_in_backlog(sprint_story1, sprint)
|
||||
|
||||
backlogs_page
|
||||
.fold_backlog(sprint)
|
||||
|
||||
backlogs_page
|
||||
.expect_story_in_backlog(sprint_story1, sprint)
|
||||
|
||||
# Alter the attributes of the sprint
|
||||
sleep(0.5)
|
||||
backlogs_page
|
||||
.edit_backlog(sprint, name: "")
|
||||
|
||||
backlogs_page
|
||||
.expect_and_dismiss_error("Name can't be blank.")
|
||||
|
||||
sleep(0.2)
|
||||
|
||||
backlogs_page
|
||||
.edit_backlog(sprint,
|
||||
name: "New sprint name",
|
||||
start_date: 5.days.from_now,
|
||||
effective_date: 20.days.from_now)
|
||||
|
||||
sleep(0.5)
|
||||
|
||||
sprint.reload
|
||||
|
||||
expect(sprint.name)
|
||||
.to eql "New sprint name"
|
||||
|
||||
expect(sprint.start_date)
|
||||
.to eql Date.today + 5.days
|
||||
|
||||
expect(sprint.effective_date)
|
||||
.to eql Date.today + 20.days
|
||||
|
||||
# Alter displaying a sprints as a backlog
|
||||
|
||||
backlogs_page
|
||||
.click_in_backlog_menu(sprint, "Properties")
|
||||
|
||||
select "right", from: "Column in backlog"
|
||||
|
||||
click_button "Save"
|
||||
|
||||
expect_and_dismiss_flash(message: "Successful update.")
|
||||
|
||||
backlogs_page
|
||||
.expect_backlog(sprint)
|
||||
|
||||
# The others are unchanged
|
||||
backlogs_page
|
||||
.expect_backlog(backlog)
|
||||
|
||||
backlogs_page
|
||||
.expect_sprint(other_project_sprint)
|
||||
|
||||
# Alter displaying a backlog as a sprint
|
||||
backlogs_page
|
||||
.click_in_backlog_menu(backlog, "Properties")
|
||||
|
||||
select "left", from: "Column in backlog"
|
||||
|
||||
click_button "Save"
|
||||
|
||||
expect_and_dismiss_flash(message: "Successful update.")
|
||||
|
||||
# Now works as a sprint instead of a backlog
|
||||
backlogs_page
|
||||
.expect_sprint(backlog)
|
||||
|
||||
# The others are unchanged
|
||||
backlogs_page
|
||||
.expect_backlog(sprint)
|
||||
|
||||
backlogs_page
|
||||
.expect_sprint(other_project_sprint)
|
||||
|
||||
# Alter displaying a version not at all
|
||||
backlogs_page
|
||||
.click_in_backlog_menu(backlog, "Properties")
|
||||
|
||||
select "none", from: "Column in backlog"
|
||||
|
||||
click_button "Save"
|
||||
|
||||
expect_and_dismiss_flash(message: "Successful update.")
|
||||
|
||||
# the disabled backlog/sprint is no longer visible
|
||||
expect(page)
|
||||
.to have_no_content(backlog.name)
|
||||
|
||||
# The others are unchanged
|
||||
backlogs_page
|
||||
.expect_backlog(sprint)
|
||||
|
||||
backlogs_page
|
||||
.expect_sprint(other_project_sprint)
|
||||
|
||||
# Inherited versions can also be modified
|
||||
backlogs_page
|
||||
.click_in_backlog_menu(other_project_sprint, "Properties")
|
||||
|
||||
select "none", from: "Column in backlog"
|
||||
|
||||
click_button "Save"
|
||||
|
||||
expect_and_dismiss_flash(message: "Successful update.")
|
||||
|
||||
# the disabled backlog/sprint is no longer visible
|
||||
expect(page)
|
||||
.to have_no_content(other_project_sprint.name)
|
||||
|
||||
# The others are unchanged
|
||||
backlogs_page
|
||||
.expect_backlog(sprint)
|
||||
|
||||
expect(page)
|
||||
.to have_no_content(backlog.name)
|
||||
end
|
||||
end
|
||||
@@ -37,11 +37,6 @@ RSpec.describe "Empty backlogs project",
|
||||
|
||||
before do
|
||||
login_as current_user
|
||||
allow(Setting)
|
||||
.to receive(:plugin_openproject_backlogs)
|
||||
.and_return("story_types" => [story.id.to_s],
|
||||
"task_type" => task.id.to_s)
|
||||
|
||||
visit backlogs_project_backlogs_path(project)
|
||||
end
|
||||
|
||||
@@ -49,9 +44,15 @@ RSpec.describe "Empty backlogs project",
|
||||
let(:current_user) { create(:admin) }
|
||||
|
||||
it "shows blankslate with description" do
|
||||
within ".blankslate" do
|
||||
expect(page).to have_heading(I18n.t(:backlogs_empty_title))
|
||||
expect(page).to have_text(I18n.t(:backlogs_empty_action_text))
|
||||
within "#owner_backlogs_container .blankslate" do
|
||||
expect(page).to have_heading(I18n.t(:"backlogs.inbox_component.blankslate_title"))
|
||||
expect(page).to have_text(I18n.t(:"backlogs.inbox_component.blankslate_description"))
|
||||
end
|
||||
|
||||
within "#sprint_backlogs_container .blankslate" do
|
||||
expect(page).to have_heading(I18n.t(:"backlogs.backlog.blankslate.title"))
|
||||
expect(page).to have_text(I18n.t(:"backlogs.backlog.blankslate.description_html",
|
||||
settings_link: "project settings"))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -61,9 +62,14 @@ RSpec.describe "Empty backlogs project",
|
||||
let(:current_user) { create(:user, member_with_roles: { project => role }) }
|
||||
|
||||
it "shows a blankslate without description" do
|
||||
within ".blankslate" do
|
||||
expect(page).to have_heading(I18n.t(:backlogs_empty_title))
|
||||
expect(page).to have_no_text(I18n.t(:backlogs_empty_action_text))
|
||||
within "#owner_backlogs_container .blankslate" do
|
||||
expect(page).to have_heading(I18n.t(:"backlogs.inbox_component.blankslate_title"))
|
||||
expect(page).to have_text(I18n.t(:"backlogs.inbox_component.blankslate_description"))
|
||||
end
|
||||
|
||||
within "#sprint_backlogs_container .blankslate" do
|
||||
expect(page).to have_heading(I18n.t(:"backlogs.backlog.blankslate.title"))
|
||||
expect(page).to have_text(I18n.t(:"backlogs.backlog.blankslate.no_actions_description_text"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
#-- 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.
|
||||
#++
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe "Impediments on taskboard", :js,
|
||||
:selenium do
|
||||
let!(:project) do
|
||||
create(:project,
|
||||
types: [story_type, task_type],
|
||||
enabled_module_names: %w(work_package_tracking backlogs))
|
||||
end
|
||||
let!(:story_type) { create(:type_feature) }
|
||||
let!(:task_type) { create(:type_task) }
|
||||
let!(:priority) { create(:default_priority) }
|
||||
let!(:status) { create(:status, is_default: true) }
|
||||
let!(:other_status) { create(:status) }
|
||||
let!(:workflows) do
|
||||
create(:workflow,
|
||||
old_status: status,
|
||||
new_status: other_status,
|
||||
role:,
|
||||
type_id: story_type.id)
|
||||
create(:workflow,
|
||||
old_status: status,
|
||||
new_status: other_status,
|
||||
role:,
|
||||
type_id: task_type.id)
|
||||
end
|
||||
let(:role) do
|
||||
create(:project_role,
|
||||
permissions: %i(view_sprints
|
||||
add_work_packages
|
||||
view_work_packages
|
||||
edit_work_packages
|
||||
manage_subtasks
|
||||
assign_versions
|
||||
work_package_assigned))
|
||||
end
|
||||
let!(:current_user) do
|
||||
create(:user,
|
||||
member_with_roles: { project => role })
|
||||
end
|
||||
let!(:task1) do
|
||||
create(:work_package,
|
||||
status:,
|
||||
project:,
|
||||
type: task_type,
|
||||
version: sprint,
|
||||
parent: story1)
|
||||
end
|
||||
let!(:story1) do
|
||||
create(:work_package,
|
||||
project:,
|
||||
type: story_type,
|
||||
version: sprint)
|
||||
end
|
||||
let!(:other_task) do
|
||||
create(:work_package,
|
||||
project:,
|
||||
type: task_type,
|
||||
version: sprint,
|
||||
parent: other_story)
|
||||
end
|
||||
let!(:other_story) do
|
||||
create(:work_package,
|
||||
project:,
|
||||
type: story_type,
|
||||
version: other_sprint)
|
||||
end
|
||||
let!(:sprint) do
|
||||
create(:version, project:)
|
||||
end
|
||||
let!(:other_sprint) do
|
||||
create(:version, project:)
|
||||
end
|
||||
|
||||
before do
|
||||
login_as current_user
|
||||
allow(Setting)
|
||||
.to receive(:plugin_openproject_backlogs)
|
||||
.and_return("story_types" => [story_type.id.to_s],
|
||||
"task_type" => task_type.id.to_s)
|
||||
end
|
||||
|
||||
it "allows creating and updating impediments" do
|
||||
visit backlogs_project_sprint_taskboard_path(project, sprint)
|
||||
|
||||
find("#impediments .add_new").click
|
||||
|
||||
fill_in "subject", with: "New impediment"
|
||||
fill_in "blocks_ids", with: task1.id
|
||||
select current_user.name, from: "assigned_to_id"
|
||||
click_on "OK"
|
||||
|
||||
# Saves successfully
|
||||
expect(page)
|
||||
.to have_css("div.impediment", text: "New impediment")
|
||||
expect(page)
|
||||
.to have_no_css("div.impediment.error", text: "New impediment")
|
||||
|
||||
# Attempt to create a new impediment with the id of a story from another sprint
|
||||
find("#impediments .add_new").click
|
||||
|
||||
fill_in "subject", with: "Other sprint impediment"
|
||||
fill_in "blocks_ids", with: other_story.id
|
||||
click_on "OK"
|
||||
|
||||
# Saves unsuccessfully
|
||||
expect(page)
|
||||
.to have_css("div.impediment", text: "Other sprint impediment")
|
||||
expect(page)
|
||||
.to have_css("div.impediment.error", text: "Other sprint impediment")
|
||||
expect(page)
|
||||
.to have_css("#msgBox",
|
||||
text: "IDs of blocked work packages can only contain IDs of work packages in the current sprint.")
|
||||
|
||||
click_on "OK"
|
||||
|
||||
# Attempt to create a new impediment with a non existing id
|
||||
find("#impediments .add_new").click
|
||||
|
||||
fill_in "subject", with: "Invalid id impediment"
|
||||
fill_in "blocks_ids", with: "0"
|
||||
click_on "OK"
|
||||
|
||||
# Saves unsuccessfully
|
||||
expect(page)
|
||||
.to have_css("div.impediment", text: "Invalid id impediment")
|
||||
expect(page)
|
||||
.to have_css("div.impediment.error", text: "Invalid id impediment")
|
||||
expect(page)
|
||||
.to have_css("#msgBox",
|
||||
text: "IDs of blocked work packages can only contain IDs of work packages in the current sprint.")
|
||||
click_on "OK"
|
||||
|
||||
# Attempt to create a new impediment without specifying the blocked story/task
|
||||
find("#impediments .add_new").click
|
||||
|
||||
fill_in "subject", with: "Unblocking impediment"
|
||||
click_on "OK"
|
||||
|
||||
# Saves unsuccessfully
|
||||
expect(page)
|
||||
.to have_css("div.impediment", text: "Unblocking impediment")
|
||||
expect(page)
|
||||
.to have_css("div.impediment.error", text: "Unblocking impediment")
|
||||
expect(page)
|
||||
.to have_css("#msgBox", text: "IDs of blocked work packages must contain the ID of at least one ticket")
|
||||
click_on "OK"
|
||||
|
||||
# Updating an impediment
|
||||
find("#impediments .subject", text: "New impediment").click
|
||||
|
||||
fill_in "subject", with: "Updated impediment"
|
||||
fill_in "blocks_ids", with: story1.id
|
||||
click_on "OK"
|
||||
|
||||
# Saves successfully
|
||||
expect(page)
|
||||
.to have_css("div.impediment", text: "Updated impediment")
|
||||
expect(page)
|
||||
.to have_no_css("div.impediment.error", text: "Updated impediment")
|
||||
end
|
||||
end
|
||||
@@ -1,209 +0,0 @@
|
||||
# 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.
|
||||
#++
|
||||
|
||||
require "spec_helper"
|
||||
require_relative "../support/pages/backlogs"
|
||||
|
||||
RSpec.describe "Stories in backlog", :js, :settings_reset do
|
||||
let!(:project) do
|
||||
create(:project,
|
||||
types: [story, task, other_story],
|
||||
enabled_module_names: %w(work_package_tracking backlogs))
|
||||
end
|
||||
let!(:story) { create(:type_feature) }
|
||||
let!(:other_story) { create(:type, name: "Story") }
|
||||
let!(:task) { create(:type_task) }
|
||||
let!(:priority) { create(:default_priority) }
|
||||
let!(:default_status) { create(:status, is_default: true) }
|
||||
let!(:other_status) { create(:status) }
|
||||
let!(:workflows) do
|
||||
create(:workflow,
|
||||
old_status: default_status,
|
||||
new_status: other_status,
|
||||
role:,
|
||||
type_id: story.id)
|
||||
end
|
||||
let(:role) do
|
||||
create(:project_role,
|
||||
permissions: %i(view_sprints
|
||||
assign_versions
|
||||
add_work_packages
|
||||
view_work_packages
|
||||
edit_work_packages
|
||||
manage_subtasks))
|
||||
end
|
||||
let!(:current_user) do
|
||||
create(:user,
|
||||
member_with_roles: { project => role })
|
||||
end
|
||||
let!(:sprint_story1) do
|
||||
create(:work_package,
|
||||
project:,
|
||||
type: story,
|
||||
status: default_status,
|
||||
version: sprint,
|
||||
position: 1,
|
||||
story_points: 8)
|
||||
end
|
||||
let!(:sprint_story1_task) do
|
||||
create(:work_package,
|
||||
project:,
|
||||
type: task,
|
||||
status: default_status,
|
||||
version: sprint)
|
||||
end
|
||||
let!(:sprint_story2_parent) do
|
||||
create(:work_package,
|
||||
project:,
|
||||
type: create(:type),
|
||||
status: default_status,
|
||||
version: sprint)
|
||||
end
|
||||
let!(:sprint_story2) do
|
||||
create(:work_package,
|
||||
project:,
|
||||
type: story,
|
||||
status: default_status,
|
||||
version: sprint,
|
||||
position: 2,
|
||||
story_points: 13)
|
||||
end
|
||||
let!(:backlog_story1) do
|
||||
create(:work_package,
|
||||
project:,
|
||||
type: story,
|
||||
status: default_status,
|
||||
version: backlog)
|
||||
end
|
||||
let!(:sprint) do
|
||||
create(:version,
|
||||
project:,
|
||||
start_date: Time.zone.today - 10.days,
|
||||
effective_date: Time.zone.today + 10.days,
|
||||
version_settings_attributes: [{ project:, display: VersionSetting::DISPLAY_LEFT }])
|
||||
end
|
||||
let!(:backlog) do
|
||||
create(:version,
|
||||
project:,
|
||||
version_settings_attributes: [{ project:, display: VersionSetting::DISPLAY_RIGHT }])
|
||||
end
|
||||
let!(:other_project) do
|
||||
create(:project).tap do |p|
|
||||
create(:member,
|
||||
principal: current_user,
|
||||
project: p,
|
||||
roles: [role])
|
||||
end
|
||||
end
|
||||
let!(:sprint_story_in_other_project) do
|
||||
create(:work_package,
|
||||
project: other_project,
|
||||
type: story,
|
||||
status: default_status,
|
||||
version: sprint,
|
||||
story_points: 5)
|
||||
end
|
||||
let(:backlogs_page) { Pages::Backlogs.new(project) }
|
||||
|
||||
before do
|
||||
Setting.plugin_openproject_backlogs = {
|
||||
"story_types" => [story.id.to_s, other_story.id.to_s],
|
||||
"task_type" => task.id.to_s
|
||||
}
|
||||
|
||||
login_as current_user
|
||||
backlogs_page.visit!
|
||||
end
|
||||
|
||||
it "displays stories in correct order, calculates velocity, and allows editing story points" do
|
||||
backlogs_page
|
||||
.expect_story_in_backlog(sprint_story1, sprint)
|
||||
|
||||
backlogs_page
|
||||
.expect_story_in_backlog(sprint_story2, sprint)
|
||||
|
||||
backlogs_page
|
||||
.expect_story_in_backlog(backlog_story1, backlog)
|
||||
|
||||
backlogs_page
|
||||
.expect_story_not_in_backlog(sprint_story2_parent, sprint)
|
||||
|
||||
backlogs_page
|
||||
.expect_story_not_in_backlog(sprint_story1_task, sprint)
|
||||
|
||||
backlogs_page
|
||||
.expect_story_not_in_backlog(sprint_story_in_other_project, sprint)
|
||||
|
||||
backlogs_page
|
||||
.expect_stories_in_order(sprint, sprint_story1, sprint_story2)
|
||||
|
||||
# Velocity is calculated by summing up all story points in a sprint
|
||||
backlogs_page.expect_velocity(sprint, 21)
|
||||
|
||||
backlogs_page
|
||||
.edit_story_in_details_view(sprint_story1, story_points: 5)
|
||||
|
||||
backlogs_page.expect_velocity(sprint, 18)
|
||||
|
||||
backlogs_page
|
||||
.edit_story_in_details_view(sprint_story2, subject: "Updated story", story_points: 3)
|
||||
|
||||
backlogs_page.expect_velocity(sprint, 8)
|
||||
end
|
||||
|
||||
it "moves story from sprint to backlog when version is changed via details view" do
|
||||
backlogs_page
|
||||
.edit_story_in_details_view(sprint_story1, version: backlog)
|
||||
|
||||
backlogs_page.expect_story_not_in_backlog(sprint_story1, sprint)
|
||||
backlogs_page.expect_story_in_backlog(sprint_story1, backlog)
|
||||
end
|
||||
|
||||
it "switches the details view from one story to another" do
|
||||
backlogs_page
|
||||
.click_in_story_menu(sprint_story1, "Open details view")
|
||||
|
||||
backlogs_page.expect_details_view(sprint_story1)
|
||||
backlogs_page.expect_story_in_backlog(sprint_story2, sprint)
|
||||
|
||||
backlogs_page
|
||||
.click_in_story_menu(sprint_story2, "Open details view")
|
||||
|
||||
backlogs_page.expect_details_view(sprint_story2)
|
||||
backlogs_page.expect_story_in_backlog(sprint_story1, sprint)
|
||||
end
|
||||
|
||||
it "removes story from sprint when type is changed to non-story type via details view" do
|
||||
backlogs_page
|
||||
.edit_story_in_details_view(sprint_story2, type: task.name)
|
||||
|
||||
backlogs_page.expect_story_not_in_backlog(sprint_story2, sprint)
|
||||
end
|
||||
end
|
||||
@@ -1,254 +0,0 @@
|
||||
#-- 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.
|
||||
#++
|
||||
|
||||
require "spec_helper"
|
||||
require_relative "../support/pages/taskboard"
|
||||
|
||||
RSpec.describe "Tasks on taskboard", :js,
|
||||
:selenium do
|
||||
let!(:project) do
|
||||
create(:project,
|
||||
types: [story, task, other_story],
|
||||
enabled_module_names: %w(work_package_tracking backlogs))
|
||||
end
|
||||
let!(:story) { create(:type_feature) }
|
||||
let!(:other_story) { create(:type) }
|
||||
let!(:task) { create(:type_task) }
|
||||
let!(:priority) { create(:default_priority) }
|
||||
let!(:default_status) { create(:status, is_default: true) }
|
||||
let!(:other_status) { create(:status) }
|
||||
let!(:workflows) do
|
||||
create(:workflow,
|
||||
old_status: default_status,
|
||||
new_status: other_status,
|
||||
role:,
|
||||
type_id: task.id)
|
||||
end
|
||||
let(:role) do
|
||||
create(:project_role,
|
||||
permissions: %i(view_sprints
|
||||
add_work_packages
|
||||
view_work_packages
|
||||
edit_work_packages
|
||||
manage_subtasks
|
||||
assign_versions
|
||||
work_package_assigned))
|
||||
end
|
||||
let!(:current_user) do
|
||||
create(:user,
|
||||
member_with_roles: { project => role })
|
||||
end
|
||||
let!(:story1) do
|
||||
create(:work_package,
|
||||
project:,
|
||||
type: story,
|
||||
status: default_status,
|
||||
version: sprint,
|
||||
position: 1,
|
||||
story_points: 10)
|
||||
end
|
||||
let!(:story1_task) do
|
||||
create(:work_package,
|
||||
project:,
|
||||
parent: story1,
|
||||
type: task,
|
||||
status: default_status,
|
||||
version: sprint)
|
||||
end
|
||||
let!(:story1_task_subtask) do
|
||||
create(:work_package,
|
||||
project:,
|
||||
parent: story1_task,
|
||||
type: task,
|
||||
status: default_status,
|
||||
version: sprint)
|
||||
end
|
||||
let!(:other_work_package) do
|
||||
create(:work_package,
|
||||
project:,
|
||||
type: create(:type),
|
||||
status: default_status,
|
||||
version: sprint)
|
||||
end
|
||||
let!(:other_work_package_subtask) do
|
||||
create(:work_package,
|
||||
project:,
|
||||
parent: other_work_package,
|
||||
type: task,
|
||||
status: default_status,
|
||||
version: sprint)
|
||||
end
|
||||
let!(:story2) do
|
||||
create(:work_package,
|
||||
project:,
|
||||
type: story,
|
||||
status: default_status,
|
||||
version: sprint,
|
||||
position: 2,
|
||||
story_points: 20)
|
||||
end
|
||||
let!(:sprint) do
|
||||
create(:version,
|
||||
project:,
|
||||
start_date: Date.today - 10.days,
|
||||
effective_date: Date.today + 10.days,
|
||||
version_settings_attributes: [{ project:, display: VersionSetting::DISPLAY_LEFT }])
|
||||
end
|
||||
let!(:other_project) do
|
||||
create(:project).tap do |p|
|
||||
create(:member,
|
||||
principal: current_user,
|
||||
project: p,
|
||||
roles: [role])
|
||||
end
|
||||
end
|
||||
let!(:story_in_other_project) do
|
||||
create(:work_package,
|
||||
project: other_project,
|
||||
type: story,
|
||||
status: default_status,
|
||||
version: sprint,
|
||||
story_points: 10)
|
||||
end
|
||||
let(:taskboard_page) { Pages::Taskboard.new(project, sprint) }
|
||||
|
||||
before do
|
||||
login_as current_user
|
||||
allow(Setting)
|
||||
.to receive(:plugin_openproject_backlogs)
|
||||
.and_return("story_types" => [story.id.to_s, other_story.id.to_s],
|
||||
"task_type" => task.id.to_s)
|
||||
end
|
||||
|
||||
it "displays stories which are editable" do
|
||||
taskboard_page.visit!
|
||||
|
||||
# All stories of the sprint are visible
|
||||
taskboard_page
|
||||
.expect_story(story1)
|
||||
|
||||
taskboard_page
|
||||
.expect_story(story2)
|
||||
|
||||
# All tasks of the sprint are visible
|
||||
taskboard_page
|
||||
.expect_task(story1_task)
|
||||
|
||||
# Other work packages also assigned to the sprint are not visible
|
||||
taskboard_page
|
||||
.expect_work_package_not_visible(other_work_package)
|
||||
|
||||
# Tasks that have a non story as their parent are not visible
|
||||
taskboard_page
|
||||
.expect_work_package_not_visible(other_work_package_subtask)
|
||||
|
||||
# Tasks that have a task and not a story as their parent are not visible
|
||||
taskboard_page
|
||||
.expect_work_package_not_visible(story1_task_subtask)
|
||||
|
||||
# The task is in the first status column belonging to its parent story
|
||||
taskboard_page
|
||||
.expect_task_in_story_column(story1_task, story1, 1)
|
||||
|
||||
# Adding a task will have it added to the same sprint and belonging to the story
|
||||
taskboard_page
|
||||
.add_task(story1,
|
||||
subject: "Added task",
|
||||
assignee: current_user.name,
|
||||
remaining_hours: 7)
|
||||
|
||||
added_task = WorkPackage.find_by(subject: "Added task")
|
||||
|
||||
expect(added_task.version)
|
||||
.to eql sprint
|
||||
|
||||
expect(added_task.parent)
|
||||
.to eql story1
|
||||
|
||||
# Added task will also be displayed
|
||||
taskboard_page
|
||||
.expect_task_in_story_column(added_task, story1, 1)
|
||||
|
||||
# Updating a task
|
||||
taskboard_page
|
||||
.update_task(story1_task,
|
||||
subject: "Updated task",
|
||||
assignee: current_user.name)
|
||||
|
||||
story1_task.reload
|
||||
|
||||
expect(story1_task.subject)
|
||||
.to eql "Updated task"
|
||||
|
||||
# Dragging a task within the same column (switching order)
|
||||
taskboard_page
|
||||
.drag_to_task(story1_task, added_task, :before)
|
||||
|
||||
taskboard_page
|
||||
.expect_task_in_story_column(added_task, story1, 1)
|
||||
|
||||
taskboard_page
|
||||
.expect_task_in_story_column(story1_task, story1, 1)
|
||||
|
||||
sleep(0.5)
|
||||
|
||||
expect(added_task.reload.higher_item.id)
|
||||
.to eql story1_task.id
|
||||
|
||||
# Dragging a task to the next column (switching status)
|
||||
taskboard_page
|
||||
.drag_to_column(story1_task, story1, 2)
|
||||
|
||||
taskboard_page
|
||||
.expect_task_in_story_column(story1_task, story1, 2)
|
||||
|
||||
sleep(0.5)
|
||||
|
||||
expect(story1_task.reload.status)
|
||||
.to eql other_status
|
||||
|
||||
# There is a button to the burndown chart
|
||||
expect(page)
|
||||
.to have_css("a[href='#{backlogs_project_sprint_burndown_chart_path(project, sprint)}']",
|
||||
text: "Burndown chart")
|
||||
|
||||
# Tasks can get a color per assigned user
|
||||
visit my_interface_path
|
||||
|
||||
fill_in "Task color", with: "#FBC4B3"
|
||||
|
||||
click_button "Update backlogs module"
|
||||
|
||||
expect_and_dismiss_flash(message: "Account was successfully updated.")
|
||||
|
||||
taskboard_page.visit!
|
||||
|
||||
taskboard_page
|
||||
.expect_color_for_task("#FBC4B3", story1_task)
|
||||
end
|
||||
end
|
||||
@@ -75,13 +75,6 @@ RSpec.describe "Create work package in sprint", :js do
|
||||
end
|
||||
|
||||
before do
|
||||
# Faulty and mostly irrelevant for the test. Only needed to make the sprints appear on the page.
|
||||
# To be removed once the setting is removed.
|
||||
Setting.plugin_openproject_backlogs = {
|
||||
"story_types" => [type.id.to_s],
|
||||
"task_type" => type.id.to_s
|
||||
}
|
||||
|
||||
backlogs_page.visit!
|
||||
end
|
||||
|
||||
|
||||
@@ -77,13 +77,6 @@ RSpec.describe "Dragging work packages in and between sprints",
|
||||
end
|
||||
|
||||
before do
|
||||
# Faulty and mostly irrelevant for the test. Only needed to make the sprints appear on the page.
|
||||
# To be removed once the setting is removed.
|
||||
Setting.plugin_openproject_backlogs = {
|
||||
"story_types" => [type.id.to_s],
|
||||
"task_type" => type.id.to_s
|
||||
}
|
||||
|
||||
backlogs_page.visit!
|
||||
end
|
||||
|
||||
|
||||
@@ -50,8 +50,14 @@ RSpec.describe "Filter work packages by backlog filters", :js do
|
||||
shared_let(:work_package_in_own_sprint) { create(:work_package, type: task_type, project:, sprint: own_sprint) }
|
||||
shared_let(:work_package_in_shared_sprint) { create(:work_package, type: task_type, project:, sprint: shared_sprint) }
|
||||
|
||||
let(:user) { create(:user, member_with_permissions: { project => permissions }) }
|
||||
let(:permissions) { %i(view_work_packages save_queries view_sprints) }
|
||||
let(:user) do
|
||||
create(:user,
|
||||
member_with_permissions: {
|
||||
project => permissions,
|
||||
shared_sprint.project => %i[show_board_views view_sprints]
|
||||
})
|
||||
end
|
||||
let(:permissions) { %i(view_work_packages save_queries show_board_views view_sprints) }
|
||||
|
||||
let(:wp_table) { Pages::WorkPackagesTable.new(project) }
|
||||
let(:filters) { Components::WorkPackages::Filters.new }
|
||||
@@ -59,60 +65,9 @@ RSpec.describe "Filter work packages by backlog filters", :js do
|
||||
before do
|
||||
login_as(user)
|
||||
|
||||
allow(Setting)
|
||||
.to receive(:plugin_openproject_backlogs)
|
||||
.and_return("story_types" => [story_type.id.to_s],
|
||||
"task_type" => task_type.id.to_s)
|
||||
|
||||
wp_table.visit!
|
||||
end
|
||||
|
||||
context "on the backlog type" do
|
||||
it "allows filtering, saving and retaining the filter" do
|
||||
filters.open
|
||||
|
||||
filters.add_filter_by("Backlog type", "is (OR)", "Story", "backlogsWorkPackageType")
|
||||
|
||||
wp_table.expect_work_package_listed work_package_with_story_type
|
||||
wp_table.ensure_work_package_not_listed! work_package_with_task_type
|
||||
|
||||
wp_table.save_as("Some query name")
|
||||
|
||||
filters.remove_filter "backlogsWorkPackageType"
|
||||
|
||||
wp_table.expect_work_package_listed work_package_with_story_type, work_package_with_task_type
|
||||
|
||||
last_query = Query.last
|
||||
|
||||
wp_table.visit_query(last_query)
|
||||
|
||||
wp_table.expect_work_package_listed work_package_with_story_type
|
||||
wp_table.ensure_work_package_not_listed! work_package_with_task_type
|
||||
|
||||
filters.open
|
||||
|
||||
filters.expect_filter_by("Backlog type", "is (OR)", "Story", "backlogsWorkPackageType")
|
||||
end
|
||||
|
||||
it "can filter by task or any" do
|
||||
filters.open
|
||||
|
||||
filters.add_filter_by("Backlog type", "is (OR)", "Story", "backlogsWorkPackageType")
|
||||
|
||||
wp_table.ensure_work_package_not_listed! work_package_with_task_type
|
||||
|
||||
filters.remove_filter "backlogsWorkPackageType"
|
||||
filters.add_filter_by("Backlog type", "is (OR)", "Task", "backlogsWorkPackageType")
|
||||
|
||||
wp_table.expect_work_package_listed work_package_with_task_type
|
||||
|
||||
filters.remove_filter "backlogsWorkPackageType"
|
||||
filters.add_filter_by("Backlog type", "is (OR)", "any", "backlogsWorkPackageType")
|
||||
|
||||
wp_table.expect_work_package_listed work_package_with_story_type, work_package_with_task_type
|
||||
end
|
||||
end
|
||||
|
||||
context "on the sprint" do
|
||||
shared_examples_for "filtering on sprints" do
|
||||
it "allows filtering by sprint" do
|
||||
@@ -125,9 +80,8 @@ RSpec.describe "Filter work packages by backlog filters", :js do
|
||||
work_package_with_task_type
|
||||
wp_table.expect_work_package_listed work_package_in_own_sprint
|
||||
|
||||
filters.remove_filter "sprint"
|
||||
|
||||
filters.add_filter_by("Sprint", "is (OR)", shared_sprint.name)
|
||||
filters.clear_filter_value "sprint"
|
||||
filters.set_filter("Sprint", "is (OR)", shared_sprint.name)
|
||||
|
||||
wp_table.ensure_work_package_not_listed! work_package_in_own_sprint,
|
||||
work_package_with_story_type,
|
||||
|
||||
@@ -31,10 +31,6 @@ require "spec_helper"
|
||||
RSpec.describe "Work packages having story points", :js do
|
||||
before do
|
||||
login_as current_user
|
||||
allow(Setting).to receive(:plugin_openproject_backlogs).and_return("points_burn_direction" => "down",
|
||||
"wiki_template" => "",
|
||||
"story_types" => [story_type.id.to_s],
|
||||
"task_type" => task_type.id.to_s)
|
||||
end
|
||||
|
||||
let(:current_user) { create(:admin) }
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
# 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.
|
||||
#++
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe Admin::Settings::BacklogsSettingsForm, type: :forms do
|
||||
include_context "with rendered form"
|
||||
|
||||
let(:form_arguments) { { url: "/foo", model: false, scope: :settings } }
|
||||
|
||||
subject(:rendered_form) do
|
||||
vc_render_form
|
||||
page
|
||||
end
|
||||
|
||||
it "renders", :aggregate_failures do
|
||||
expect(rendered_form).to have_element "opce-autocompleter", "data-label-for-id": "\"settings_story_types\"" do |autocompleter|
|
||||
expect(autocompleter["data-multiple"]).to be_json_eql(%{true})
|
||||
end
|
||||
|
||||
expect(rendered_form).to have_element "opce-autocompleter", "data-label-for-id": "\"settings_task_type\"" do |autocompleter|
|
||||
expect(autocompleter["data-multiple"]).to be_json_eql(%{false})
|
||||
end
|
||||
|
||||
expect(rendered_form).to have_field "Template for sprint wiki page", type: :text do |field|
|
||||
expect(field["name"]).to eq "settings[wiki_template]"
|
||||
end
|
||||
|
||||
expect(rendered_form).to have_field "Up", type: :radio do |field|
|
||||
expect(field["name"]).to eq "settings[points_burn_direction]"
|
||||
expect(field["value"]).to eq "up"
|
||||
end
|
||||
|
||||
expect(rendered_form).to have_field "Down", type: :radio do |field|
|
||||
expect(field["name"]).to eq "settings[points_burn_direction]"
|
||||
expect(field["value"]).to eq "down"
|
||||
end
|
||||
|
||||
expect(rendered_form).to have_button "Save", type: "submit"
|
||||
end
|
||||
end
|
||||
@@ -1,43 +0,0 @@
|
||||
# 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.
|
||||
#++
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe Admin::Settings::BacklogsSettingsModel, type: :model do
|
||||
describe "validations" do
|
||||
subject(:model) { described_class.new(story_types: [1, 2, 3]) }
|
||||
|
||||
it "validates that a story type cannot be used as a task type" do
|
||||
expect(subject).to validate_exclusion_of(:task_type)
|
||||
.in_array([1, 2, 3])
|
||||
.with_message(I18n.t("errors.attributes.task_type.cannot_be_story_type"))
|
||||
end
|
||||
end
|
||||
end
|
||||
-40
@@ -52,7 +52,6 @@ RSpec.describe API::V3::WorkPackages::Schema::WorkPackageSchemaRepresenter do
|
||||
login_as(current_user)
|
||||
|
||||
allow(schema.project).to receive(:backlogs_enabled?).and_return(true)
|
||||
allow(work_package.type).to receive(:story?).and_return(true)
|
||||
allow(work_package).to receive(:leaf?).and_return(true)
|
||||
end
|
||||
|
||||
@@ -77,19 +76,6 @@ RSpec.describe API::V3::WorkPackages::Schema::WorkPackageSchemaRepresenter do
|
||||
end
|
||||
end
|
||||
|
||||
context "when not a story" do
|
||||
before do
|
||||
allow(schema.type).to receive(:story?).and_return(false)
|
||||
end
|
||||
|
||||
it_behaves_like "has basic schema properties" do
|
||||
let(:path) { "storyPoints" }
|
||||
let(:type) { "Integer" }
|
||||
let(:name) { I18n.t("activerecord.attributes.work_package.story_points") }
|
||||
let(:required) { false }
|
||||
let(:writable) { true }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "position" do
|
||||
@@ -111,19 +97,6 @@ RSpec.describe API::V3::WorkPackages::Schema::WorkPackageSchemaRepresenter do
|
||||
end
|
||||
end
|
||||
|
||||
context "when not a story" do
|
||||
before do
|
||||
allow(schema.type).to receive(:story?).and_return(false)
|
||||
end
|
||||
|
||||
it_behaves_like "has basic schema properties" do
|
||||
let(:path) { "position" }
|
||||
let(:type) { "Integer" }
|
||||
let(:name) { I18n.t("activerecord.attributes.work_package.position") }
|
||||
let(:required) { false }
|
||||
let(:writable) { false }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "sprint" do
|
||||
@@ -161,18 +134,5 @@ RSpec.describe API::V3::WorkPackages::Schema::WorkPackageSchemaRepresenter do
|
||||
end
|
||||
end
|
||||
|
||||
context "when not a story" do
|
||||
before do
|
||||
allow(schema.type).to receive(:story?).and_return(false)
|
||||
end
|
||||
|
||||
it_behaves_like "has basic schema properties" do
|
||||
let(:type) { "Sprint" }
|
||||
let(:name) { I18n.t("activerecord.attributes.work_package.sprint") }
|
||||
let(:required) { false }
|
||||
let(:writable) { true }
|
||||
let(:location) { "_links" }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-5
@@ -65,11 +65,6 @@ RSpec.describe API::V3::WorkPackages::WorkPackageRepresenter, "rendering" do
|
||||
current_user { build_stubbed(:user) }
|
||||
|
||||
before do
|
||||
allow(Setting)
|
||||
.to receive(:plugin_openproject_backlogs)
|
||||
.and_return("story_types" => [story_type.id.to_s],
|
||||
"task_type" => task_type.id.to_s)
|
||||
|
||||
mock_permissions_for(current_user) do |mock|
|
||||
permissions.each do |permission|
|
||||
mock.allow_in_project(*permission, project:) if project
|
||||
|
||||
@@ -32,9 +32,6 @@ RSpec.describe Backlog do
|
||||
let(:project) { build(:project) }
|
||||
|
||||
before do
|
||||
@feature = create(:type_feature)
|
||||
allow(Setting).to receive(:plugin_openproject_backlogs).and_return({ "story_types" => [@feature.id.to_s],
|
||||
"task_type" => "0" })
|
||||
@status = create(:status)
|
||||
end
|
||||
|
||||
@@ -77,19 +74,6 @@ RSpec.describe Backlog do
|
||||
end
|
||||
end
|
||||
|
||||
describe "#owner_backlogs" do
|
||||
describe "WITH one open version defined in the project" do
|
||||
before do
|
||||
@project = project
|
||||
@work_packages = [create(:work_package, subject: "work_package1", project: @project, type: @feature,
|
||||
status: @status)]
|
||||
@version = create(:version, project:, work_packages: @work_packages)
|
||||
@version_settings = @version.version_settings.create(display: VersionSetting::DISPLAY_RIGHT, project:)
|
||||
end
|
||||
|
||||
it { expect(Backlog.owner_backlogs(@project)[0]).to be_owner_backlog }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "ActiveModel naming" do
|
||||
|
||||
@@ -55,123 +55,214 @@ RSpec.describe Burndown do
|
||||
let(:issue_open) { create(:status, name: "status 1", is_default: true) }
|
||||
let(:issue_closed) { create(:status, name: "status 2", is_closed: true) }
|
||||
let(:issue_resolved) { create(:status, name: "status 3", is_closed: false) }
|
||||
let(:sprint) { create(:agile_sprint, project:) }
|
||||
|
||||
current_user { create(:user, member_with_roles: { project => role }) }
|
||||
|
||||
subject(:burndown) { described_class.new(sprint, project) }
|
||||
|
||||
describe "WITH the today date fixed to April 4th, 2011 and having a 10 (working days) sprint" do
|
||||
around do |example|
|
||||
travel_to(Time.utc(2011, "apr", 4, 20, 15, 1)) { example.run }
|
||||
end
|
||||
describe "for a version sprint" do
|
||||
let(:version) { create(:version, project:) }
|
||||
let(:sprint) { Sprint.find(version.id) }
|
||||
|
||||
describe "WITH having a sprint in the future" do
|
||||
before do
|
||||
sprint.start_date = Time.zone.today + 1.day
|
||||
sprint.finish_date = Time.zone.today + 6.days
|
||||
sprint.save!
|
||||
describe "WITH the today date fixed to April 4th, 2011 and having a 10 (working days) sprint" do
|
||||
around do |example|
|
||||
travel_to(Time.utc(2011, "apr", 4, 20, 15, 1)) { example.run }
|
||||
end
|
||||
|
||||
it "generates an empty burndown" do
|
||||
expect(burndown.series[:story_points]).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe "WITH having a 10 (working days) sprint and being 5 (working) days into it" do
|
||||
before do
|
||||
sprint.start_date = Time.zone.today - 7.days
|
||||
sprint.finish_date = Time.zone.today + 6.days
|
||||
sprint.save!
|
||||
end
|
||||
|
||||
describe "WITH 1 story assigned to the sprint" do
|
||||
let(:story) do
|
||||
build(:story, subject: "Story 1",
|
||||
project:,
|
||||
sprint:,
|
||||
type: type_feature,
|
||||
status: issue_open,
|
||||
priority: issue_priority,
|
||||
created_at: Time.zone.today - 20.days,
|
||||
updated_at: Time.zone.today - 20.days)
|
||||
describe "WITH having a version in the future" do
|
||||
before do
|
||||
version.start_date = Time.zone.today + 1.day
|
||||
version.effective_date = Time.zone.today + 6.days
|
||||
version.save!
|
||||
end
|
||||
|
||||
describe "WITH the story having story_point defined on creation" do
|
||||
it "generates an empty burndown" do
|
||||
expect(burndown.series[:story_points]).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe "WITH having a 10 (working days) sprint and being 5 (working) days into it" do
|
||||
before do
|
||||
version.start_date = Time.zone.today - 7.days
|
||||
version.effective_date = Time.zone.today + 6.days
|
||||
version.save!
|
||||
end
|
||||
|
||||
describe "WITH a work package of another type assigned to the sprint" do
|
||||
let(:other_type) { create(:type) }
|
||||
let(:other_work_package) do
|
||||
build(:work_package,
|
||||
subject: "Other work package",
|
||||
project:,
|
||||
version:,
|
||||
type: other_type,
|
||||
status: issue_open,
|
||||
priority: issue_priority,
|
||||
story_points: 7,
|
||||
created_at: Time.zone.today - 20.days,
|
||||
updated_at: Time.zone.today - 20.days)
|
||||
end
|
||||
|
||||
before do
|
||||
story.story_points = 9
|
||||
story.save!
|
||||
story.last_journal.update_columns(created_at: story.created_at, updated_at: story.created_at)
|
||||
other_work_package.save!
|
||||
other_work_package.last_journal.update_columns(created_at: other_work_package.created_at,
|
||||
updated_at: other_work_package.created_at)
|
||||
end
|
||||
|
||||
describe "WITH the story being closed and opened again within the sprint duration" do
|
||||
before do
|
||||
set_attribute_journalized story, :status_id=, issue_closed.id, 6.days.ago
|
||||
set_attribute_journalized story, :status_id=, issue_open.id, 3.days.ago
|
||||
end
|
||||
|
||||
it { expect(burndown.story_points).to eql [9.0, 0.0, 0.0, 0.0, 9.0, 9.0] }
|
||||
it { expect(burndown.story_points.unit).to be :points }
|
||||
|
||||
it {
|
||||
expect(burndown.days).to eql(Day.working.from_range(from: sprint.start_date,
|
||||
to: sprint.finish_date).map(&:date))
|
||||
}
|
||||
|
||||
it { expect(burndown.max[:points]).to be 9.0 }
|
||||
it { expect(burndown.story_points_ideal).to eql [9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0] }
|
||||
end
|
||||
|
||||
describe "WITH the story marked as resolved and consequently 'done'" do
|
||||
before do
|
||||
set_attribute_journalized story, :status_id=, issue_resolved.id, 6.days.ago
|
||||
set_attribute_journalized story, :status_id=, issue_open.id, 3.days.ago
|
||||
project.done_statuses << issue_resolved
|
||||
end
|
||||
|
||||
it { expect(story.done?).to be false }
|
||||
it { expect(burndown.story_points).to eql [9.0, 0.0, 0.0, 0.0, 9.0, 9.0] }
|
||||
it "includes its story points in the burndown" do
|
||||
expect(burndown.story_points).to eql(Array.new(burndown.story_points.size, 7.0))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "WITH 10 stories assigned to the sprint" do
|
||||
let!(:stories) do
|
||||
stories = []
|
||||
|
||||
10.times do |i|
|
||||
stories[i] = create(:story, subject: "Story #{i}",
|
||||
project:,
|
||||
sprint:,
|
||||
type: type_feature,
|
||||
status: issue_open,
|
||||
priority: issue_priority,
|
||||
created_at: Time.zone.today - (20 - i).days,
|
||||
updated_at: Time.zone.today - (20 - i).days)
|
||||
stories[i].last_journal.update_columns(created_at: stories[i].created_at,
|
||||
updated_at: stories[i].created_at,
|
||||
validity_period: stories[i].created_at..Float::INFINITY)
|
||||
describe "WITH 1 story assigned to the sprint" do
|
||||
let(:story) do
|
||||
build(:story, subject: "Story 1",
|
||||
project:,
|
||||
version:,
|
||||
type: type_feature,
|
||||
status: issue_open,
|
||||
priority: issue_priority,
|
||||
created_at: Time.zone.today - 20.days,
|
||||
updated_at: Time.zone.today - 20.days)
|
||||
end
|
||||
|
||||
stories
|
||||
end
|
||||
describe "WITH the story having story_point defined on creation" do
|
||||
before do
|
||||
story.story_points = 9
|
||||
story.save!
|
||||
story.last_journal.update_columns(created_at: story.created_at, updated_at: story.created_at)
|
||||
end
|
||||
|
||||
describe "WITH each story having story points defined at start" do
|
||||
before do
|
||||
stories.each do |s|
|
||||
set_attribute_journalized s, :story_points=, 10, sprint.start_date - 3.days
|
||||
describe "WITH the story being closed and opened again within the sprint duration" do
|
||||
before do
|
||||
set_attribute_journalized story, :status_id=, issue_closed.id, 6.days.ago
|
||||
set_attribute_journalized story, :status_id=, issue_open.id, 3.days.ago
|
||||
end
|
||||
|
||||
it { expect(burndown.story_points).to eql [9.0, 0.0, 0.0, 0.0, 9.0, 9.0] }
|
||||
it { expect(burndown.story_points.unit).to be :points }
|
||||
it { expect(burndown.days).to eql(sprint.days) }
|
||||
it { expect(burndown.max[:hours]).to be 0.0 }
|
||||
it { expect(burndown.max[:points]).to be 9.0 }
|
||||
it { expect(burndown.story_points_ideal).to eql [9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0] }
|
||||
end
|
||||
|
||||
describe "WITH the story marked as resolved and consequently 'done'" do
|
||||
before do
|
||||
set_attribute_journalized story, :status_id=, issue_resolved.id, 6.days.ago
|
||||
set_attribute_journalized story, :status_id=, issue_open.id, 3.days.ago
|
||||
project.done_statuses << issue_resolved
|
||||
end
|
||||
|
||||
it { expect(story.done?).to be false }
|
||||
it { expect(burndown.story_points).to eql [9.0, 0.0, 0.0, 0.0, 9.0, 9.0] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "WITH 5 stories having been reduced to 0 story points, one story per day" do
|
||||
describe "WITH 10 stories assigned to the sprint" do
|
||||
let!(:stories) do
|
||||
stories = []
|
||||
|
||||
10.times do |i|
|
||||
stories[i] = create(:story, subject: "Story #{i}",
|
||||
project:,
|
||||
version:,
|
||||
type: type_feature,
|
||||
status: issue_open,
|
||||
priority: issue_priority,
|
||||
created_at: Time.zone.today - (20 - i).days,
|
||||
updated_at: Time.zone.today - (20 - i).days)
|
||||
stories[i].last_journal.update_columns(created_at: stories[i].created_at,
|
||||
updated_at: stories[i].created_at,
|
||||
validity_period: stories[i].created_at..Float::INFINITY)
|
||||
end
|
||||
|
||||
stories
|
||||
end
|
||||
|
||||
describe "WITH each story having story points defined at start" do
|
||||
before do
|
||||
5.times do |i|
|
||||
set_attribute_journalized stories[i], :story_points=, nil, sprint.start_date + i.days + 1.hour
|
||||
stories.each_with_index do |s, _i|
|
||||
set_attribute_journalized s, :story_points=, 10, version.start_date - 3.days
|
||||
end
|
||||
end
|
||||
|
||||
describe "THEN" do
|
||||
it { expect(burndown.story_points).to eql [90.0, 80.0, 70.0, 60.0, 50.0, 50.0] }
|
||||
describe "WITH 5 stories having been reduced to 0 story points, one story per day" do
|
||||
before do
|
||||
5.times do |i|
|
||||
set_attribute_journalized stories[i], :story_points=, nil, version.start_date + i.days + 1.hour
|
||||
end
|
||||
end
|
||||
|
||||
describe "THEN" do
|
||||
it { expect(burndown.story_points).to eql [90.0, 80.0, 70.0, 60.0, 50.0, 50.0] }
|
||||
it { expect(burndown.story_points.unit).to be :points }
|
||||
it { expect(burndown.days).to eql(sprint.days) }
|
||||
it { expect(burndown.max[:hours]).to be 0.0 }
|
||||
it { expect(burndown.max[:points]).to be 90.0 }
|
||||
it { expect(burndown.story_points_ideal).to eql [90.0, 80.0, 70.0, 60.0, 50.0, 40.0, 30.0, 20.0, 10.0, 0.0] }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "for an agile sprint" do
|
||||
let(:sprint) { create(:agile_sprint, project:) }
|
||||
|
||||
describe "WITH the today date fixed to April 4th, 2011 and having a 10 (working days) sprint" do
|
||||
around do |example|
|
||||
travel_to(Time.utc(2011, "apr", 4, 20, 15, 1)) { example.run }
|
||||
end
|
||||
|
||||
describe "WITH having a sprint in the future" do
|
||||
before do
|
||||
sprint.start_date = Time.zone.today + 1.day
|
||||
sprint.finish_date = Time.zone.today + 6.days
|
||||
sprint.save!
|
||||
end
|
||||
|
||||
it "generates an empty burndown" do
|
||||
expect(burndown.series[:story_points]).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe "WITH having a 10 (working days) sprint and being 5 (working) days into it" do
|
||||
before do
|
||||
sprint.start_date = Time.zone.today - 7.days
|
||||
sprint.finish_date = Time.zone.today + 6.days
|
||||
sprint.save!
|
||||
end
|
||||
|
||||
describe "WITH 1 story assigned to the sprint" do
|
||||
let(:story) do
|
||||
build(:story, subject: "Story 1",
|
||||
project:,
|
||||
sprint:,
|
||||
type: type_feature,
|
||||
status: issue_open,
|
||||
priority: issue_priority,
|
||||
created_at: Time.zone.today - 20.days,
|
||||
updated_at: Time.zone.today - 20.days)
|
||||
end
|
||||
|
||||
describe "WITH the story having story_point defined on creation" do
|
||||
before do
|
||||
story.story_points = 9
|
||||
story.save!
|
||||
story.last_journal.update_columns(created_at: story.created_at, updated_at: story.created_at)
|
||||
end
|
||||
|
||||
describe "WITH the story being closed and opened again within the sprint duration" do
|
||||
before do
|
||||
set_attribute_journalized story, :status_id=, issue_closed.id, 6.days.ago
|
||||
set_attribute_journalized story, :status_id=, issue_open.id, 3.days.ago
|
||||
end
|
||||
|
||||
it { expect(burndown.story_points).to eql [9.0, 0.0, 0.0, 0.0, 9.0, 9.0] }
|
||||
it { expect(burndown.story_points.unit).to be :points }
|
||||
|
||||
it {
|
||||
@@ -179,32 +270,94 @@ RSpec.describe Burndown do
|
||||
to: sprint.finish_date).map(&:date))
|
||||
}
|
||||
|
||||
it { expect(burndown.max[:points]).to be 90.0 }
|
||||
it { expect(burndown.story_points_ideal).to eql [90.0, 80.0, 70.0, 60.0, 50.0, 40.0, 30.0, 20.0, 10.0, 0.0] }
|
||||
it { expect(burndown.max[:points]).to be 9.0 }
|
||||
it { expect(burndown.story_points_ideal).to eql [9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0] }
|
||||
end
|
||||
|
||||
describe "WITH the story marked as resolved and consequently 'done'" do
|
||||
before do
|
||||
set_attribute_journalized story, :status_id=, issue_resolved.id, 6.days.ago
|
||||
set_attribute_journalized story, :status_id=, issue_open.id, 3.days.ago
|
||||
project.done_statuses << issue_resolved
|
||||
end
|
||||
|
||||
it { expect(story.done?).to be false }
|
||||
it { expect(burndown.story_points).to eql [9.0, 0.0, 0.0, 0.0, 9.0, 9.0] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "WITH 10 stories assigned to the sprint" do
|
||||
let!(:stories) do
|
||||
stories = []
|
||||
|
||||
10.times do |i|
|
||||
stories[i] = create(:story, subject: "Story #{i}",
|
||||
project:,
|
||||
sprint:,
|
||||
type: type_feature,
|
||||
status: issue_open,
|
||||
priority: issue_priority,
|
||||
created_at: Time.zone.today - (20 - i).days,
|
||||
updated_at: Time.zone.today - (20 - i).days)
|
||||
stories[i].last_journal.update_columns(created_at: stories[i].created_at,
|
||||
updated_at: stories[i].created_at,
|
||||
validity_period: stories[i].created_at..Float::INFINITY)
|
||||
end
|
||||
|
||||
stories
|
||||
end
|
||||
|
||||
describe "WITH each story having story points defined at start" do
|
||||
before do
|
||||
stories.each do |s|
|
||||
set_attribute_journalized s, :story_points=, 10, sprint.start_date - 3.days
|
||||
end
|
||||
end
|
||||
|
||||
describe "WITH 5 stories having been reduced to 0 story points, one story per day" do
|
||||
before do
|
||||
5.times do |i|
|
||||
set_attribute_journalized stories[i], :story_points=, nil, sprint.start_date + i.days + 1.hour
|
||||
end
|
||||
end
|
||||
|
||||
describe "THEN" do
|
||||
it { expect(burndown.story_points).to eql [90.0, 80.0, 70.0, 60.0, 50.0, 50.0] }
|
||||
it { expect(burndown.story_points.unit).to be :points }
|
||||
|
||||
it {
|
||||
expect(burndown.days).to eql(Day.working.from_range(from: sprint.start_date,
|
||||
to: sprint.finish_date).map(&:date))
|
||||
}
|
||||
|
||||
it { expect(burndown.max[:points]).to be 90.0 }
|
||||
it { expect(burndown.story_points_ideal).to eql [90.0, 80.0, 70.0, 60.0, 50.0, 40.0, 30.0, 20.0, 10.0, 0.0] }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "without dates on the sprint" do
|
||||
let(:sprint) { create(:agile_sprint, project:, start_date: nil, finish_date: nil) }
|
||||
let(:story) do
|
||||
build(:story,
|
||||
:created_in_past,
|
||||
subject: "Story 1",
|
||||
project:,
|
||||
sprint:,
|
||||
type: type_feature,
|
||||
status: issue_open,
|
||||
priority: issue_priority,
|
||||
created_at: Time.zone.today - 20.days,
|
||||
updated_at: Time.zone.today - 20.days)
|
||||
end
|
||||
context "without dates on the sprint" do
|
||||
let(:sprint) { create(:agile_sprint, project:, start_date: nil, finish_date: nil) }
|
||||
let(:story) do
|
||||
build(:story,
|
||||
:created_in_past,
|
||||
subject: "Story 1",
|
||||
project:,
|
||||
sprint:,
|
||||
type: type_feature,
|
||||
status: issue_open,
|
||||
priority: issue_priority,
|
||||
created_at: Time.zone.today - 20.days,
|
||||
updated_at: Time.zone.today - 20.days)
|
||||
end
|
||||
|
||||
it "generates an empty burndown" do
|
||||
expect(burndown.series[:story_points]).to be_empty
|
||||
it "generates an empty burndown" do
|
||||
expect(burndown.series[:story_points]).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
#-- 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.
|
||||
#++
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe Impediment do
|
||||
let(:user) { @user ||= create(:user) }
|
||||
let(:role) { @role ||= create(:project_role) }
|
||||
let(:type_feature) { @type_feature ||= create(:type_feature) }
|
||||
let(:type_task) { @type_task ||= create(:type_task) }
|
||||
let(:issue_priority) { @issue_priority ||= create(:priority, is_default: true) }
|
||||
let(:status) { create(:status) }
|
||||
let(:task) do
|
||||
build(:task, type: type_task,
|
||||
project:,
|
||||
author: user,
|
||||
priority: issue_priority,
|
||||
status:)
|
||||
end
|
||||
let(:feature) do
|
||||
build(:work_package, type: type_feature,
|
||||
project:,
|
||||
author: user,
|
||||
priority: issue_priority,
|
||||
status:)
|
||||
end
|
||||
let(:version) { create(:version, project:) }
|
||||
|
||||
let(:project) do
|
||||
unless @project
|
||||
@project = build(:project, types: [type_feature, type_task])
|
||||
@project.members = [build(:member, principal: user,
|
||||
project: @project,
|
||||
roles: [role])]
|
||||
end
|
||||
@project
|
||||
end
|
||||
|
||||
let(:impediment) do
|
||||
build(:impediment, author: user,
|
||||
version:,
|
||||
assigned_to: user,
|
||||
priority: issue_priority,
|
||||
project:,
|
||||
type: type_task,
|
||||
status:)
|
||||
end
|
||||
|
||||
before do
|
||||
allow(Setting)
|
||||
.to receive(:plugin_openproject_backlogs)
|
||||
.and_return({ "points_burn_direction" => "down",
|
||||
"wiki_template" => "",
|
||||
"story_types" => [type_feature.id.to_s],
|
||||
"task_type" => type_task.id.to_s })
|
||||
|
||||
login_as user
|
||||
end
|
||||
|
||||
describe "instance methods" do
|
||||
describe "blocks_ids=/blocks_ids" do
|
||||
describe "WITH an integer" do
|
||||
it do
|
||||
impediment.blocks_ids = 2
|
||||
expect(impediment.blocks_ids).to eql [2]
|
||||
end
|
||||
end
|
||||
|
||||
describe "WITH a string" do
|
||||
it do
|
||||
impediment.blocks_ids = "1, 2, 3"
|
||||
expect(impediment.blocks_ids).to eql [1, 2, 3]
|
||||
end
|
||||
end
|
||||
|
||||
describe "WITH an array" do
|
||||
it do
|
||||
impediment.blocks_ids = [1, 2, 3]
|
||||
expect(impediment.blocks_ids).to eql [1, 2, 3]
|
||||
end
|
||||
end
|
||||
|
||||
describe "WITH loading from the backend" do
|
||||
before do
|
||||
feature.version = version
|
||||
feature.save
|
||||
task.version = version
|
||||
task.save
|
||||
|
||||
impediment.blocks_ids = [feature.id, task.id]
|
||||
impediment.save
|
||||
end
|
||||
|
||||
it { expect(described_class.find(impediment.id).blocks_ids).to eql [feature.id, task.id] }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,200 +0,0 @@
|
||||
#-- 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.
|
||||
#++
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe Story do
|
||||
let(:user) { @user ||= create(:user) }
|
||||
let(:role) { @role ||= create(:project_role) }
|
||||
let(:status1) { @status1 ||= create(:status, name: "status 1", is_default: true) }
|
||||
let(:type_feature) { @type_feature ||= create(:type_feature) }
|
||||
let(:version) { @version ||= create(:version, project:) }
|
||||
let(:version2) { create(:version, project:) }
|
||||
let(:sprint) { @sprint ||= create(:sprint, project:) }
|
||||
let(:issue_priority) { @issue_priority ||= create(:priority) }
|
||||
let(:task_type) { create(:type_task) }
|
||||
let(:task) do
|
||||
create(:story, version:,
|
||||
project:,
|
||||
status: status1,
|
||||
type: task_type,
|
||||
priority: issue_priority)
|
||||
end
|
||||
let(:story1) do
|
||||
create(:story, version:,
|
||||
project:,
|
||||
status: status1,
|
||||
type: type_feature,
|
||||
priority: issue_priority)
|
||||
end
|
||||
|
||||
let(:story2) do
|
||||
create(:story, version:,
|
||||
project:,
|
||||
status: status1,
|
||||
type: type_feature,
|
||||
priority: issue_priority)
|
||||
end
|
||||
|
||||
let(:project) do
|
||||
unless @project
|
||||
@project = build(:project)
|
||||
@project.members = [build(:member, principal: user,
|
||||
project: @project,
|
||||
roles: [role])]
|
||||
end
|
||||
@project
|
||||
end
|
||||
|
||||
before do
|
||||
ActionController::Base.perform_caching = false
|
||||
|
||||
allow(Setting).to receive(:plugin_openproject_backlogs).and_return({ "points_burn_direction" => "down",
|
||||
"wiki_template" => "",
|
||||
"story_types" => [type_feature.id.to_s],
|
||||
"task_type" => task_type.id.to_s })
|
||||
project.types << task_type
|
||||
end
|
||||
|
||||
describe "Class methods" do
|
||||
describe "#backlogs" do
|
||||
describe "WITH one sprint " \
|
||||
"WITH the sprint having 1 story" do
|
||||
before do
|
||||
story1
|
||||
end
|
||||
|
||||
it { expect(Story.backlogs(project, [version.id])[version.id]).to contain_exactly(story1) }
|
||||
end
|
||||
|
||||
describe "WITH two sprints " \
|
||||
"WITH two stories " \
|
||||
"WITH one story per sprint " \
|
||||
"WITH querying for the two sprints" do
|
||||
before do
|
||||
version2
|
||||
story1
|
||||
story2.version_id = version2.id
|
||||
story2.save!
|
||||
end
|
||||
|
||||
it { expect(Story.backlogs(project, [version.id, version2.id])[version.id]).to contain_exactly(story1) }
|
||||
it { expect(Story.backlogs(project, [version.id, version2.id])[version2.id]).to contain_exactly(story2) }
|
||||
end
|
||||
|
||||
describe "WITH two sprints " \
|
||||
"WITH two stories " \
|
||||
"WITH one story per sprint " \
|
||||
"WITH querying one sprints" do
|
||||
before do
|
||||
version2
|
||||
story1
|
||||
|
||||
story2.version_id = version2.id
|
||||
story2.save!
|
||||
end
|
||||
|
||||
it { expect(Story.backlogs(project, [version.id])[version.id]).to contain_exactly(story1) }
|
||||
it { expect(Story.backlogs(project, [version.id])[version2.id]).to be_empty }
|
||||
end
|
||||
|
||||
describe "WITH two sprints " \
|
||||
"WITH two stories " \
|
||||
"WITH one story per sprint " \
|
||||
"WITH querying for the two sprints " \
|
||||
"WITH one sprint being in another project" do
|
||||
before do
|
||||
story1
|
||||
|
||||
other_project = create(:project)
|
||||
version2.update! project_id: other_project.id
|
||||
|
||||
story2.version_id = version2.id
|
||||
story2.project = other_project
|
||||
# reset memoized versions to reflect changes above
|
||||
story2.instance_variable_set(:@assignable_versions, nil)
|
||||
story2.save!
|
||||
end
|
||||
|
||||
it { expect(Story.backlogs(project, [version.id, version2.id])[version.id]).to contain_exactly(story1) }
|
||||
it { expect(Story.backlogs(project, [version.id, version2.id])[version2.id]).to be_empty }
|
||||
end
|
||||
|
||||
describe "WITH one sprint " \
|
||||
"WITH the sprint having one story in this project and one story in another project" do
|
||||
before do
|
||||
version.sharing = "system"
|
||||
version.save!
|
||||
|
||||
another_project = create(:project)
|
||||
|
||||
story1
|
||||
story2.project = another_project
|
||||
story2.save!
|
||||
end
|
||||
|
||||
it { expect(Story.backlogs(project, [version.id])[version.id]).to contain_exactly(story1) }
|
||||
end
|
||||
|
||||
describe "WITH one sprint " \
|
||||
"WITH the sprint having two storys " \
|
||||
"WITH one being the child of the other" do
|
||||
before do
|
||||
story1.parent_id = story2.id
|
||||
|
||||
story1.save
|
||||
end
|
||||
|
||||
it { expect(Story.backlogs(project, [version.id])[version.id]).to contain_exactly(story1, story2) }
|
||||
end
|
||||
|
||||
describe "WITH one sprint " \
|
||||
"WITH the sprint having one story " \
|
||||
"WITH the story having a child task" do
|
||||
before do
|
||||
task.parent_id = story1.id
|
||||
|
||||
task.save
|
||||
end
|
||||
|
||||
it { expect(Story.backlogs(project, [version.id])[version.id]).to contain_exactly(story1) }
|
||||
end
|
||||
|
||||
describe "WITH one sprint " \
|
||||
"WITH the sprint having one story and one task " \
|
||||
"WITH the two having no connection" do
|
||||
before do
|
||||
task
|
||||
story1
|
||||
end
|
||||
|
||||
it { expect(Story.backlogs(project, [version.id])[version.id]).to contain_exactly(story1) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -39,12 +39,6 @@ RSpec.describe Task do
|
||||
type: task_type)
|
||||
end
|
||||
|
||||
before do
|
||||
allow(Setting)
|
||||
.to receive(:plugin_openproject_backlogs)
|
||||
.and_return({ "task_type" => task_type.id.to_s })
|
||||
end
|
||||
|
||||
describe "having custom journables", with_settings: { journal_aggregation_time_minutes: 0 } do
|
||||
let(:user) { create(:user) }
|
||||
let(:role) do
|
||||
|
||||
@@ -1,252 +0,0 @@
|
||||
#-- 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.
|
||||
#++
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe Version do
|
||||
it { is_expected.to have_many :version_settings }
|
||||
|
||||
describe "#used_as_backlog?" do
|
||||
let(:project) { create(:project) }
|
||||
let(:version) { create(:version, project:) }
|
||||
|
||||
context "when backlogs is not enabled" do
|
||||
before do
|
||||
project.enabled_module_names = project.enabled_module_names - ["backlogs"]
|
||||
end
|
||||
|
||||
it "returns false" do
|
||||
expect(version.used_as_backlog?(project)).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context "when backlogs is enabled" do
|
||||
before do
|
||||
project.enabled_module_names = project.enabled_module_names + ["backlogs"]
|
||||
end
|
||||
|
||||
context "when no version_settings exist" do
|
||||
it "returns false" do
|
||||
expect(version.used_as_backlog?(project)).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context "when version_settings exist with display_right" do
|
||||
before do
|
||||
create(:version_setting, version:, project:, display: VersionSetting::DISPLAY_RIGHT)
|
||||
end
|
||||
|
||||
it "returns true" do
|
||||
expect(version.used_as_backlog?(project)).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context "when version_settings exist with display_left" do
|
||||
before do
|
||||
create(:version_setting, version:, project:, display: VersionSetting::DISPLAY_LEFT)
|
||||
end
|
||||
|
||||
it "returns false" do
|
||||
expect(version.used_as_backlog?(project)).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context "when version_settings exist with display_none" do
|
||||
before do
|
||||
create(:version_setting, version:, project:, display: VersionSetting::DISPLAY_NONE)
|
||||
end
|
||||
|
||||
it "returns false" do
|
||||
expect(version.used_as_backlog?(project)).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context "when multiple version_settings exist for different projects" do
|
||||
let(:other_project) { create(:project) }
|
||||
|
||||
before do
|
||||
project.enabled_module_names = project.enabled_module_names + ["backlogs"]
|
||||
other_project.enabled_module_names = other_project.enabled_module_names + ["backlogs"]
|
||||
create(:version_setting, version:, project:, display: VersionSetting::DISPLAY_RIGHT)
|
||||
create(:version_setting, version:, project: other_project, display: VersionSetting::DISPLAY_LEFT)
|
||||
end
|
||||
|
||||
it "returns true for the project with display_right" do
|
||||
expect(version.used_as_backlog?(project)).to be true
|
||||
end
|
||||
|
||||
it "returns false for the project with display_left" do
|
||||
expect(version.used_as_backlog?(other_project)).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context "when project parameter is not provided" do
|
||||
context "and version has a project" do
|
||||
before do
|
||||
project.enabled_module_names = project.enabled_module_names + ["backlogs"]
|
||||
create(:version_setting, version:, project:, display: VersionSetting::DISPLAY_RIGHT)
|
||||
end
|
||||
|
||||
it "uses the version's project" do
|
||||
expect(version.used_as_backlog?).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "rebuild positions" do
|
||||
def build_work_package(options = {})
|
||||
build(:work_package, options.reverse_merge(version_id: version.id,
|
||||
priority_id: priority.id,
|
||||
project_id: project.id,
|
||||
status_id: status.id))
|
||||
end
|
||||
|
||||
def create_work_package(options = {})
|
||||
build_work_package(options).tap(&:save!)
|
||||
end
|
||||
|
||||
let(:status) { create(:status) }
|
||||
let(:priority) { create(:priority_normal) }
|
||||
let(:project) { create(:project, name: "Project 1", types: [epic_type, story_type, task_type, other_type]) }
|
||||
|
||||
let(:epic_type) { create(:type, name: "Epic") }
|
||||
let(:story_type) { create(:type, name: "Story") }
|
||||
let(:task_type) { create(:type, name: "Task") }
|
||||
let(:other_type) { create(:type, name: "Other") }
|
||||
|
||||
let(:version) { create(:version, project_id: project.id, name: "Version") }
|
||||
|
||||
shared_let(:admin) { create(:admin) }
|
||||
|
||||
def move_to_project(work_package, project)
|
||||
WorkPackages::UpdateService
|
||||
.new(model: work_package, user: admin)
|
||||
.call(project:)
|
||||
end
|
||||
|
||||
before do
|
||||
# We had problems while writing these specs, that some elements kept
|
||||
# creeping around between tests. This should be fast enough to not harm
|
||||
# anybody while adding an additional safety net to make sure, that
|
||||
# everything runs in isolation.
|
||||
WorkPackage.delete_all
|
||||
IssuePriority.delete_all
|
||||
Status.delete_all
|
||||
Project.delete_all
|
||||
Type.delete_all
|
||||
Version.delete_all
|
||||
|
||||
# Enable and configure backlogs
|
||||
project.enabled_module_names = project.enabled_module_names + ["backlogs"]
|
||||
allow(Setting).to receive(:plugin_openproject_backlogs).and_return({ "story_types" => [epic_type.id, story_type.id],
|
||||
"task_type" => task_type.id })
|
||||
|
||||
# Otherwise the type id's from the previous test are still active
|
||||
WorkPackage.instance_variable_set(:@backlogs_types, nil)
|
||||
|
||||
project.types = [epic_type, story_type, task_type, other_type]
|
||||
version
|
||||
end
|
||||
|
||||
it "moves an work_package to a project where backlogs is disabled while using versions" do
|
||||
project2 = create(:project, name: "Project 2", types: [epic_type, story_type, task_type, other_type])
|
||||
project2.enabled_module_names = project2.enabled_module_names - ["backlogs"]
|
||||
project2.save!
|
||||
project2.reload
|
||||
|
||||
work_package1 = create(:work_package, type_id: task_type.id, status_id: status.id, project_id: project.id)
|
||||
work_package2 = create(:work_package, parent_id: work_package1.id, type_id: task_type.id, status_id: status.id,
|
||||
project_id: project.id)
|
||||
work_package3 = create(:work_package, parent_id: work_package2.id, type_id: task_type.id, status_id: status.id,
|
||||
project_id: project.id)
|
||||
|
||||
work_package1.reload
|
||||
work_package1.version_id = version.id
|
||||
work_package1.save!
|
||||
|
||||
work_package1.reload
|
||||
work_package2.reload
|
||||
work_package3.reload
|
||||
|
||||
move_to_project(work_package3, project2)
|
||||
|
||||
work_package1.reload
|
||||
work_package2.reload
|
||||
work_package3.reload
|
||||
|
||||
move_to_project(work_package2, project2)
|
||||
|
||||
work_package1.reload
|
||||
work_package2.reload
|
||||
work_package3.reload
|
||||
|
||||
expect(work_package3.project).to eq(project2)
|
||||
expect(work_package2.project).to eq(project2)
|
||||
expect(work_package1.project).to eq(project)
|
||||
|
||||
expect(work_package3.version_id).to be_nil
|
||||
expect(work_package2.version_id).to be_nil
|
||||
expect(work_package1.version_id).to eq(version.id)
|
||||
end
|
||||
|
||||
it "rebuilds positions" do
|
||||
e1 = create_work_package(type_id: epic_type.id)
|
||||
s2 = create_work_package(type_id: story_type.id)
|
||||
s3 = create_work_package(type_id: story_type.id)
|
||||
s4 = create_work_package(type_id: story_type.id)
|
||||
s5 = create_work_package(type_id: story_type.id)
|
||||
t3 = create_work_package(type_id: task_type.id)
|
||||
o9 = create_work_package(type_id: other_type.id)
|
||||
|
||||
[e1, s2, s3, s4, s5].each(&:move_to_bottom)
|
||||
|
||||
# Messing around with positions
|
||||
s3.update_column(:position, nil)
|
||||
s4.update_column(:position, nil)
|
||||
|
||||
t3.update_column(:position, 3)
|
||||
o9.update_column(:position, 9)
|
||||
|
||||
version.rebuild_story_positions(project)
|
||||
|
||||
work_packages = version
|
||||
.work_packages
|
||||
.where(project_id: project)
|
||||
.order(Arel.sql("COALESCE(position, 0) ASC, id ASC"))
|
||||
|
||||
expect(work_packages.map(&:position)).to eq([nil, nil, 1, 2, 3, 4, 5])
|
||||
expect(work_packages.map(&:subject)).to eq([t3, o9, e1, s2, s5, s3, s4].map(&:subject))
|
||||
|
||||
# Makes sure, that all work_package subjects are uniq, so that the above
|
||||
# assertion works as expected
|
||||
expect(work_packages.map(&:subject).uniq.size).to eq(7)
|
||||
end
|
||||
end
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user