From 7b3b6bdbf3029bdfa381d10666a366bc5ac90bb0 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Tue, 14 Apr 2026 20:39:24 +0100 Subject: [PATCH] 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. --- config/locales/js-en.yml | 6 - .../schema/work_package_schema_representer.rb | 3 +- .../backlogs/backlog_component.html.erb | 63 -- .../components/backlogs/backlog_component.rb | 85 --- .../backlog_header_component.html.erb | 90 --- .../backlogs/backlog_header_component.rb | 82 --- .../backlogs/backlog_menu_component.html.erb | 121 ---- .../backlogs/backlog_menu_component.rb | 65 -- .../backlogs/sprint_menu_component.html.erb | 3 +- .../backlogs/sprint_menu_component.rb | 9 - .../backlogs/sprint_page_header_component.rb | 6 +- .../backlogs/story_component.html.erb | 30 +- .../components/backlogs/story_component.rb | 14 +- .../backlogs/story_menu_list_component.rb | 2 +- .../backlogs_settings_controller.rb | 16 +- .../controllers/rb_application_controller.rb | 3 +- .../rb_burndown_charts_controller.rb | 6 +- .../controllers/rb_impediments_controller.rb | 85 --- .../rb_master_backlogs_controller.rb | 1 - .../app/controllers/rb_queries_controller.rb | 54 -- .../app/controllers/rb_sprints_controller.rb | 55 +- .../app/controllers/rb_stories_controller.rb | 78 +-- .../controllers/rb_taskboards_controller.rb | 27 +- .../app/controllers/rb_tasks_controller.rb | 72 --- .../app/controllers/rb_wikis_controller.rb | 43 -- .../admin/settings/backlogs_settings_form.rb | 124 ---- .../admin/settings/backlogs_settings_model.rb | 60 -- .../app/forms/backlogs/backlog_header_form.rb | 85 --- .../backlogs/app/helpers/rb_common_helper.rb | 129 ---- .../backlogs/app/helpers/taskboards_helper.rb | 36 -- modules/backlogs/app/models/backlog.rb | 23 +- modules/backlogs/app/models/impediment.rb | 80 --- modules/backlogs/app/models/sprint.rb | 8 +- modules/backlogs/app/models/story.rb | 106 +--- modules/backlogs/app/models/task.rb | 28 - .../basic_data/backlogs/setting_seeder.rb | 104 ---- .../services/impediments/create_service.rb | 43 -- .../services/impediments/update_service.rb | 43 -- .../app/services/tasks/create_service.rb | 49 -- .../app/services/tasks/update_service.rb | 49 -- .../views/rb_burndown_charts/show.html.erb | 6 +- .../views/rb_impediments/_impediment.html.erb | 72 --- .../views/rb_master_backlogs/_list.html.erb | 52 -- .../views/rb_master_backlogs/index.html.erb | 66 -- .../app/views/rb_taskboards/show.html.erb | 172 ------ .../app/views/rb_tasks/_task.html.erb | 65 -- .../app/views/rb_tasks/index.html.erb | 36 -- .../app/views/shared/_model_errors.html.erb | 34 -- .../app/views/shared/_server_variables.js.erb | 67 --- .../app/views/shared/not_configured.html.erb | 55 -- modules/backlogs/config/locales/en.yml | 56 +- modules/backlogs/config/routes.rb | 38 +- .../backlogs/burndown/series_raw_data.rb | 13 - .../lib/open_project/backlogs/engine.rb | 35 +- .../lib/open_project/backlogs/list.rb | 44 +- .../patches/permitted_params_patch.rb | 6 - .../backlogs/patches/project_patch.rb | 7 - .../backlogs/patches/type_patch.rb | 49 -- .../backlogs/patches/version_patch.rb | 2 + .../patches/versions/row_component_patch.rb | 46 -- .../backlogs/patches/work_package_patch.rb | 15 - .../backlogs/backlog_component_spec.rb | 152 ----- .../backlogs/backlog_header_component_spec.rb | 221 ------- .../backlogs/backlog_menu_component_spec.rb | 340 ----------- .../backlogs/inbox_item_component_spec.rb | 2 +- .../backlogs/sprint_component_spec.rb | 6 +- .../backlogs/sprint_header_component_spec.rb | 8 +- .../backlogs/sprint_menu_component_spec.rb | 7 +- .../sprint_page_header_component_spec.rb | 14 +- .../backlogs/story_component_spec.rb | 12 +- .../story_menu_list_component_spec.rb | 26 +- .../work_packages/base_contract_spec.rb | 4 - .../backlogs_settings_controller_spec.rb | 82 +-- .../rb_sprints_controller_permissions_spec.rb | 105 ---- .../controllers/rb_sprints_controller_spec.rb | 565 +++++++----------- .../controllers/rb_stories_controller_spec.rb | 229 +------ .../rb_taskboards_controller_spec.rb | 6 - .../features/admin/backlogs_settings_spec.rb | 58 -- .../features/backlogs/context_menu_spec.rb | 160 ----- .../features/backlogs/create_story_spec.rb | 151 ----- .../features/backlogs/start_finish_spec.rb | 5 +- .../features/backlogs_in_backlog_view_spec.rb | 266 --------- .../spec/features/empty_backlogs_spec.rb | 28 +- .../spec/features/impediments_spec.rb | 190 ------ .../spec/features/stories_in_backlog_spec.rb | 209 ------- .../spec/features/tasks_on_taskboard_spec.rb | 254 -------- .../work_packages/create_work_package_spec.rb | 7 - .../work_packages/drag_in_sprint_spec.rb | 7 - .../features/work_packages/filter_spec.rb | 66 +- .../work_packages/story_points_spec.rb | 4 - .../settings/backlogs_settings_form_spec.rb | 68 --- .../settings/backlogs_settings_model_spec.rb | 43 -- .../work_package_schema_representer_spec.rb | 40 -- ...work_package_representer_rendering_spec.rb | 5 - modules/backlogs/spec/models/backlog_spec.rb | 16 - modules/backlogs/spec/models/burndown_spec.rb | 373 ++++++++---- .../backlogs/spec/models/impediment_spec.rb | 123 ---- modules/backlogs/spec/models/story_spec.rb | 200 ------- modules/backlogs/spec/models/task_spec.rb | 6 - modules/backlogs/spec/models/version_spec.rb | 252 -------- .../backlogs/spec/models/work_package_spec.rb | 2 + .../v3/sprints/project_index_resource_spec.rb | 1 - .../api/v3/sprints/show_resource_spec.rb | 1 - .../spec/requests/rb_master_backlogs_spec.rb | 15 +- .../routing/rb_impediments_routing_spec.rb | 48 -- .../spec/routing/rb_queries_routing_spec.rb | 40 -- .../spec/routing/rb_sprints_routing_spec.rb | 25 - .../spec/routing/rb_stories_routing_spec.rb | 10 - .../spec/routing/rb_tasks_routing_spec.rb | 48 -- .../spec/routing/rb_wikis_routing_spec.rb | 47 -- .../impediments/create_services_spec.rb | 186 ------ .../impediments/update_service_spec.rb | 199 ------ ...uild_positions_service_integration_spec.rb | 22 +- .../backlogs/spec/support/pages/backlog.rb | 6 +- .../backlogs/spec/support/pages/backlogs.rb | 241 -------- .../backlogs/spec/support/pages/taskboard.rb | 131 ---- .../views/rb_burndown_charts/show_spec.rb | 50 +- .../rb_master_backlogs/index.html.erb_spec.rb | 91 --- .../spec/views/rb_taskboards/show_spec.rb | 250 -------- .../spec/views/shared/not_configured_spec.rb | 37 -- spec/models/type/attribute_groups_spec.rb | 5 +- .../pdf_export/work_package_to_pdf_spec.rb | 40 +- .../backlogs/setting_seeder_spec.rb | 118 ---- 123 files changed, 680 insertions(+), 8165 deletions(-) delete mode 100644 modules/backlogs/app/components/backlogs/backlog_component.html.erb delete mode 100644 modules/backlogs/app/components/backlogs/backlog_component.rb delete mode 100644 modules/backlogs/app/components/backlogs/backlog_header_component.html.erb delete mode 100644 modules/backlogs/app/components/backlogs/backlog_header_component.rb delete mode 100644 modules/backlogs/app/components/backlogs/backlog_menu_component.html.erb delete mode 100644 modules/backlogs/app/components/backlogs/backlog_menu_component.rb delete mode 100644 modules/backlogs/app/controllers/rb_impediments_controller.rb delete mode 100644 modules/backlogs/app/controllers/rb_queries_controller.rb delete mode 100644 modules/backlogs/app/controllers/rb_tasks_controller.rb delete mode 100644 modules/backlogs/app/controllers/rb_wikis_controller.rb delete mode 100644 modules/backlogs/app/forms/admin/settings/backlogs_settings_form.rb delete mode 100644 modules/backlogs/app/forms/admin/settings/backlogs_settings_model.rb delete mode 100644 modules/backlogs/app/forms/backlogs/backlog_header_form.rb delete mode 100644 modules/backlogs/app/helpers/taskboards_helper.rb delete mode 100644 modules/backlogs/app/models/impediment.rb delete mode 100644 modules/backlogs/app/seeders/basic_data/backlogs/setting_seeder.rb delete mode 100644 modules/backlogs/app/services/impediments/create_service.rb delete mode 100644 modules/backlogs/app/services/impediments/update_service.rb delete mode 100644 modules/backlogs/app/services/tasks/create_service.rb delete mode 100644 modules/backlogs/app/services/tasks/update_service.rb delete mode 100644 modules/backlogs/app/views/rb_impediments/_impediment.html.erb delete mode 100644 modules/backlogs/app/views/rb_master_backlogs/_list.html.erb delete mode 100644 modules/backlogs/app/views/rb_master_backlogs/index.html.erb delete mode 100644 modules/backlogs/app/views/rb_taskboards/show.html.erb delete mode 100644 modules/backlogs/app/views/rb_tasks/_task.html.erb delete mode 100644 modules/backlogs/app/views/rb_tasks/index.html.erb delete mode 100644 modules/backlogs/app/views/shared/_model_errors.html.erb delete mode 100644 modules/backlogs/app/views/shared/_server_variables.js.erb delete mode 100644 modules/backlogs/app/views/shared/not_configured.html.erb delete mode 100644 modules/backlogs/lib/open_project/backlogs/patches/type_patch.rb delete mode 100644 modules/backlogs/lib/open_project/backlogs/patches/versions/row_component_patch.rb delete mode 100644 modules/backlogs/spec/components/backlogs/backlog_component_spec.rb delete mode 100644 modules/backlogs/spec/components/backlogs/backlog_header_component_spec.rb delete mode 100644 modules/backlogs/spec/components/backlogs/backlog_menu_component_spec.rb delete mode 100644 modules/backlogs/spec/controllers/rb_sprints_controller_permissions_spec.rb delete mode 100644 modules/backlogs/spec/features/admin/backlogs_settings_spec.rb delete mode 100644 modules/backlogs/spec/features/backlogs/context_menu_spec.rb delete mode 100644 modules/backlogs/spec/features/backlogs/create_story_spec.rb delete mode 100644 modules/backlogs/spec/features/backlogs_in_backlog_view_spec.rb delete mode 100644 modules/backlogs/spec/features/impediments_spec.rb delete mode 100644 modules/backlogs/spec/features/stories_in_backlog_spec.rb delete mode 100644 modules/backlogs/spec/features/tasks_on_taskboard_spec.rb delete mode 100644 modules/backlogs/spec/forms/admin/settings/backlogs_settings_form_spec.rb delete mode 100644 modules/backlogs/spec/forms/admin/settings/backlogs_settings_model_spec.rb delete mode 100644 modules/backlogs/spec/models/impediment_spec.rb delete mode 100644 modules/backlogs/spec/models/story_spec.rb delete mode 100644 modules/backlogs/spec/models/version_spec.rb delete mode 100644 modules/backlogs/spec/routing/rb_impediments_routing_spec.rb delete mode 100644 modules/backlogs/spec/routing/rb_queries_routing_spec.rb delete mode 100644 modules/backlogs/spec/routing/rb_tasks_routing_spec.rb delete mode 100644 modules/backlogs/spec/routing/rb_wikis_routing_spec.rb delete mode 100644 modules/backlogs/spec/services/impediments/create_services_spec.rb delete mode 100644 modules/backlogs/spec/services/impediments/update_service_spec.rb delete mode 100644 modules/backlogs/spec/support/pages/backlogs.rb delete mode 100644 modules/backlogs/spec/support/pages/taskboard.rb delete mode 100644 modules/backlogs/spec/views/rb_master_backlogs/index.html.erb_spec.rb delete mode 100644 modules/backlogs/spec/views/rb_taskboards/show_spec.rb delete mode 100644 modules/backlogs/spec/views/shared/not_configured_spec.rb delete mode 100644 spec/modules/backlogs/app/seeders/basic_data/backlogs/setting_seeder_spec.rb diff --git a/config/locales/js-en.yml b/config/locales/js-en.yml index c32e868091d..5aeaa76a179 100644 --- a/config/locales/js-en.yml +++ b/config/locales/js-en.yml @@ -551,12 +551,6 @@ en: sidebar_arrow: "Use the return arrow in the top left corner to return to the project’s main menu." welcome: "Take a three-minute introduction tour to learn the most important features.
We recommend completing the steps until the end. You can restart the tour any time." wiki: "Within the wiki you can document and share knowledge together with your team." - backlogs: - overview: "Manage your work in the backlogs 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 epics, user stories, and bugs, prioritize via drag & drop and add them to a sprint." - task_board_arrow: "To see your task board, open the sprint drop-down..." - task_board_select: "...and select the task board entry." - task_board: "The task board visualizes the progress for this sprint. Click on the plus (+) icon next to a user story to add new tasks or impediments.
The status can be updated by drag and drop." boards: overview: "Select boards 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 Kanban board, for example." diff --git a/lib/api/v3/work_packages/schema/work_package_schema_representer.rb b/lib/api/v3/work_packages/schema/work_package_schema_representer.rb index 76511e52e97..153fd8e6305 100644 --- a/lib/api/v3/work_packages/schema/work_package_schema_representer.rb +++ b/lib/api/v3/work_packages/schema/work_package_schema_representer.rb @@ -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 diff --git a/modules/backlogs/app/components/backlogs/backlog_component.html.erb b/modules/backlogs/app/components/backlogs/backlog_component.html.erb deleted file mode 100644 index 5ce7f8ffec2..00000000000 --- a/modules/backlogs/app/components/backlogs/backlog_component.html.erb +++ /dev/null @@ -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 %> diff --git a/modules/backlogs/app/components/backlogs/backlog_component.rb b/modules/backlogs/app/components/backlogs/backlog_component.rb deleted file mode 100644 index 5d17e318a82..00000000000 --- a/modules/backlogs/app/components/backlogs/backlog_component.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/app/components/backlogs/backlog_header_component.html.erb b/modules/backlogs/app/components/backlogs/backlog_header_component.html.erb deleted file mode 100644 index 394c4c6b894..00000000000 --- a/modules/backlogs/app/components/backlogs/backlog_header_component.html.erb +++ /dev/null @@ -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 %> - <%= t(:"backlogs.points_label", count: story_points) %> - <% 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 %> diff --git a/modules/backlogs/app/components/backlogs/backlog_header_component.rb b/modules/backlogs/app/components/backlogs/backlog_header_component.rb deleted file mode 100644 index d8c621ca19c..00000000000 --- a/modules/backlogs/app/components/backlogs/backlog_header_component.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/app/components/backlogs/backlog_menu_component.html.erb b/modules/backlogs/app/components/backlogs/backlog_menu_component.html.erb deleted file mode 100644 index 94a5c78a74c..00000000000 --- a/modules/backlogs/app/components/backlogs/backlog_menu_component.html.erb +++ /dev/null @@ -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 -%> diff --git a/modules/backlogs/app/components/backlogs/backlog_menu_component.rb b/modules/backlogs/app/components/backlogs/backlog_menu_component.rb deleted file mode 100644 index df5443efab6..00000000000 --- a/modules/backlogs/app/components/backlogs/backlog_menu_component.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/app/components/backlogs/sprint_menu_component.html.erb b/modules/backlogs/app/components/backlogs/sprint_menu_component.html.erb index 559543bda47..9e552ff5668 100644 --- a/modules/backlogs/app/components/backlogs/sprint_menu_component.html.erb +++ b/modules/backlogs/app/components/backlogs/sprint_menu_component.html.erb @@ -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| diff --git a/modules/backlogs/app/components/backlogs/sprint_menu_component.rb b/modules/backlogs/app/components/backlogs/sprint_menu_component.rb index 91259fb9e58..c56022bd107 100644 --- a/modules/backlogs/app/components/backlogs/sprint_menu_component.rb +++ b/modules/backlogs/app/components/backlogs/sprint_menu_component.rb @@ -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 diff --git a/modules/backlogs/app/components/backlogs/sprint_page_header_component.rb b/modules/backlogs/app/components/backlogs/sprint_page_header_component.rb index 951e5c472a9..6b752e30b97 100644 --- a/modules/backlogs/app/components/backlogs/sprint_page_header_component.rb +++ b/modules/backlogs/app/components/backlogs/sprint_page_header_component.rb @@ -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 diff --git a/modules/backlogs/app/components/backlogs/story_component.html.erb b/modules/backlogs/app/components/backlogs/story_component.html.erb index 9465c851ea9..708bd07d37f 100644 --- a/modules/backlogs/app/components/backlogs/story_component.html.erb +++ b/modules/backlogs/app/components/backlogs/story_component.html.erb @@ -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 %> diff --git a/modules/backlogs/app/components/backlogs/story_component.rb b/modules/backlogs/app/components/backlogs/story_component.rb index 3e23fb94a32..7ff4cbba1a8 100644 --- a/modules/backlogs/app/components/backlogs/story_component.rb +++ b/modules/backlogs/app/components/backlogs/story_component.rb @@ -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 diff --git a/modules/backlogs/app/components/backlogs/story_menu_list_component.rb b/modules/backlogs/app/components/backlogs/story_menu_list_component.rb index c4bf7b66cc8..9e79dbea2a6 100644 --- a/modules/backlogs/app/components/backlogs/story_menu_list_component.rb +++ b/modules/backlogs/app/components/backlogs/story_menu_list_component.rb @@ -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:) diff --git a/modules/backlogs/app/controllers/backlogs_settings_controller.rb b/modules/backlogs/app/controllers/backlogs_settings_controller.rb index 845c60b6b7f..3f005445d6d 100644 --- a/modules/backlogs/app/controllers/backlogs_settings_controller.rb +++ b/modules/backlogs/app/controllers/backlogs_settings_controller.rb @@ -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 diff --git a/modules/backlogs/app/controllers/rb_application_controller.rb b/modules/backlogs/app/controllers/rb_application_controller.rb index d76147b2eba..6969ce3a870 100644 --- a/modules/backlogs/app/controllers/rb_application_controller.rb +++ b/modules/backlogs/app/controllers/rb_application_controller.rb @@ -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 diff --git a/modules/backlogs/app/controllers/rb_burndown_charts_controller.rb b/modules/backlogs/app/controllers/rb_burndown_charts_controller.rb index 06ae933f3eb..c43b2fe40ea 100644 --- a/modules/backlogs/app/controllers/rb_burndown_charts_controller.rb +++ b/modules/backlogs/app/controllers/rb_burndown_charts_controller.rb @@ -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 } diff --git a/modules/backlogs/app/controllers/rb_impediments_controller.rb b/modules/backlogs/app/controllers/rb_impediments_controller.rb deleted file mode 100644 index 1a95316f107..00000000000 --- a/modules/backlogs/app/controllers/rb_impediments_controller.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/app/controllers/rb_master_backlogs_controller.rb b/modules/backlogs/app/controllers/rb_master_backlogs_controller.rb index 0810fb1f96a..6bed7af924f 100644 --- a/modules/backlogs/app/controllers/rb_master_backlogs_controller.rb +++ b/modules/backlogs/app/controllers/rb_master_backlogs_controller.rb @@ -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) diff --git a/modules/backlogs/app/controllers/rb_queries_controller.rb b/modules/backlogs/app/controllers/rb_queries_controller.rb deleted file mode 100644 index eaf990c0fab..00000000000 --- a/modules/backlogs/app/controllers/rb_queries_controller.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/app/controllers/rb_sprints_controller.rb b/modules/backlogs/app/controllers/rb_sprints_controller.rb index de49f9461fe..c060a96bf78 100644 --- a/modules/backlogs/app/controllers/rb_sprints_controller.rb +++ b/modules/backlogs/app/controllers/rb_sprints_controller.rb @@ -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 diff --git a/modules/backlogs/app/controllers/rb_stories_controller.rb b/modules/backlogs/app/controllers/rb_stories_controller.rb index 6b6e49d8c39..92e9ab99c7d 100644 --- a/modules/backlogs/app/controllers/rb_stories_controller.rb +++ b/modules/backlogs/app/controllers/rb_stories_controller.rb @@ -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 diff --git a/modules/backlogs/app/controllers/rb_taskboards_controller.rb b/modules/backlogs/app/controllers/rb_taskboards_controller.rb index 1a412742028..cc4ed55519c 100644 --- a/modules/backlogs/app/controllers/rb_taskboards_controller.rb +++ b/modules/backlogs/app/controllers/rb_taskboards_controller.rb @@ -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 diff --git a/modules/backlogs/app/controllers/rb_tasks_controller.rb b/modules/backlogs/app/controllers/rb_tasks_controller.rb deleted file mode 100644 index 5182715d05f..00000000000 --- a/modules/backlogs/app/controllers/rb_tasks_controller.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/app/controllers/rb_wikis_controller.rb b/modules/backlogs/app/controllers/rb_wikis_controller.rb deleted file mode 100644 index 7684363cffa..00000000000 --- a/modules/backlogs/app/controllers/rb_wikis_controller.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/app/forms/admin/settings/backlogs_settings_form.rb b/modules/backlogs/app/forms/admin/settings/backlogs_settings_form.rb deleted file mode 100644 index 521a694523f..00000000000 --- a/modules/backlogs/app/forms/admin/settings/backlogs_settings_form.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/app/forms/admin/settings/backlogs_settings_model.rb b/modules/backlogs/app/forms/admin/settings/backlogs_settings_model.rb deleted file mode 100644 index f9575dea693..00000000000 --- a/modules/backlogs/app/forms/admin/settings/backlogs_settings_model.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/app/forms/backlogs/backlog_header_form.rb b/modules/backlogs/app/forms/backlogs/backlog_header_form.rb deleted file mode 100644 index 11b07fffed6..00000000000 --- a/modules/backlogs/app/forms/backlogs/backlog_header_form.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/app/helpers/rb_common_helper.rb b/modules/backlogs/app/helpers/rb_common_helper.rb index cf849e44dd3..2fe48cd69cf 100644 --- a/modules/backlogs/app/helpers/rb_common_helper.rb +++ b/modules/backlogs/app/helpers/rb_common_helper.rb @@ -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 diff --git a/modules/backlogs/app/helpers/taskboards_helper.rb b/modules/backlogs/app/helpers/taskboards_helper.rb deleted file mode 100644 index f5d6cc6e72f..00000000000 --- a/modules/backlogs/app/helpers/taskboards_helper.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/app/models/backlog.rb b/modules/backlogs/app/models/backlog.rb index 59da4f36ab0..115d05a3386 100644 --- a/modules/backlogs/app/models/backlog.rb +++ b/modules/backlogs/app/models/backlog.rb @@ -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) diff --git a/modules/backlogs/app/models/impediment.rb b/modules/backlogs/app/models/impediment.rb deleted file mode 100644 index 8ca3b991970..00000000000 --- a/modules/backlogs/app/models/impediment.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/app/models/sprint.rb b/modules/backlogs/app/models/sprint.rb index cdb61d41a8d..42c24d29633 100644 --- a/modules/backlogs/app/models/sprint.rb +++ b/modules/backlogs/app/models/sprint.rb @@ -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 diff --git a/modules/backlogs/app/models/story.rb b/modules/backlogs/app/models/story.rb index 6948b8dfdbc..c7885bafddc 100644 --- a/modules/backlogs/app/models/story.rb +++ b/modules/backlogs/app/models/story.rb @@ -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 diff --git a/modules/backlogs/app/models/task.rb b/modules/backlogs/app/models/task.rb index a4cf2762b44..5ca78e0653c 100644 --- a/modules/backlogs/app/models/task.rb +++ b/modules/backlogs/app/models/task.rb @@ -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 diff --git a/modules/backlogs/app/seeders/basic_data/backlogs/setting_seeder.rb b/modules/backlogs/app/seeders/basic_data/backlogs/setting_seeder.rb deleted file mode 100644 index b8a64d4ec10..00000000000 --- a/modules/backlogs/app/seeders/basic_data/backlogs/setting_seeder.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/app/services/impediments/create_service.rb b/modules/backlogs/app/services/impediments/create_service.rb deleted file mode 100644 index 14d9429be80..00000000000 --- a/modules/backlogs/app/services/impediments/create_service.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/app/services/impediments/update_service.rb b/modules/backlogs/app/services/impediments/update_service.rb deleted file mode 100644 index 89eb86e7cfb..00000000000 --- a/modules/backlogs/app/services/impediments/update_service.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/app/services/tasks/create_service.rb b/modules/backlogs/app/services/tasks/create_service.rb deleted file mode 100644 index fd34ec2bb97..00000000000 --- a/modules/backlogs/app/services/tasks/create_service.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/app/services/tasks/update_service.rb b/modules/backlogs/app/services/tasks/update_service.rb deleted file mode 100644 index a249626d024..00000000000 --- a/modules/backlogs/app/services/tasks/update_service.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/app/views/rb_burndown_charts/show.html.erb b/modules/backlogs/app/views/rb_burndown_charts/show.html.erb index 18d57f1d6dc..f3c123b33e1 100644 --- a/modules/backlogs/app/views/rb_burndown_charts/show.html.erb +++ b/modules/backlogs/app/views/rb_burndown_charts/show.html.erb @@ -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 %> diff --git a/modules/backlogs/app/views/rb_impediments/_impediment.html.erb b/modules/backlogs/app/views/rb_impediments/_impediment.html.erb deleted file mode 100644 index 4f630693c9d..00000000000 --- a/modules/backlogs/app/views/rb_impediments/_impediment.html.erb +++ /dev/null @@ -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 -%> -
> -
-
<%= work_package_link_or_empty(impediment) %>
-
<%= id_or_empty(impediment) %>
-
-
<%= impediment.subject %>
-
<%= blocks_ids(impediment.blocks_ids) %>
-
-
<%= assignee_name_or_empty(impediment) %>
-
<%= assignee_id_or_empty(impediment) %>
-
-
" - fieldname="remaining_hours" - fieldlabel="<%= WorkPackage.human_attribute_name(:remaining_hours) %>" - field_id="<%= impediment.id %>"><%= remaining_hours(impediment) %>
-
-
-
<%= impediment.parent_id %>
-
<%= impediment.status_id %>
- <%= render(partial: "shared/model_errors", object: errors) if defined?(errors) && !errors.empty? %> -
-
diff --git a/modules/backlogs/app/views/rb_master_backlogs/_list.html.erb b/modules/backlogs/app/views/rb_master_backlogs/_list.html.erb deleted file mode 100644 index 988048d99d0..00000000000 --- a/modules/backlogs/app/views/rb_master_backlogs/_list.html.erb +++ /dev/null @@ -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 %> -
-
- <%= render(Backlogs::BacklogComponent.with_collection(@sprint_backlogs, project: @project)) %> -
-
- <%= render(Backlogs::BacklogComponent.with_collection(@owner_backlogs, project: @project)) %> -
-
- <% end %> -<% end %> diff --git a/modules/backlogs/app/views/rb_master_backlogs/index.html.erb b/modules/backlogs/app/views/rb_master_backlogs/index.html.erb deleted file mode 100644 index bf4222ec02e..00000000000 --- a/modules/backlogs/app/views/rb_master_backlogs/index.html.erb +++ /dev/null @@ -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 %> diff --git a/modules/backlogs/app/views/rb_taskboards/show.html.erb b/modules/backlogs/app/views/rb_taskboards/show.html.erb deleted file mode 100644 index 23dc8eb7a2d..00000000000 --- a/modules/backlogs/app/views/rb_taskboards/show.html.erb +++ /dev/null @@ -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 %> - -
-
- - - - <% @statuses.each do |status| %> - - <% end %> - -
<%= t(:backlogs_story) %><%= status.name %>
- - - - - <% if User.current.allowed_in_project?(:add_work_packages, @project) %> - - <% else %> - - <% end %> - <% @statuses.each do |status| %> - - <% end %> - -
<%= t(:label_sprint_impediments) %>
+" id="impcell_<%= status.id %>"> - <%= render partial: "rb_impediments/impediment", - collection: impediments_by_position_for_status(@sprint, @project, status) %> -
- - - <% @sprint.stories(@project).each do |story| %> - <% tasks_by_status_id = story.tasks.group_by(&:status_id) %> - - - - <% if User.current.allowed_in_project?(:add_work_packages, @project) %> - - <% else %> - - <% end %> - <% @statuses.each do |status| %> - - <% end %> - - <% end %> -
-
-
-
- <%= story.status.name %> -
-
- <%= work_package_link_or_empty(story) %> -
-
-
<%= story.subject %>
- -
-
+" id="<%= story.id %>_<%= status.id %>"> - <%= render partial: "rb_tasks/task", - collection: tasks_by_status_id[status.id] %> -
-
- -
- -
- <%= render partial: "rb_tasks/task", object: Task.new, locals: { project: @project } %> -
-
- <%= render partial: "rb_impediments/impediment", object: Impediment.new, locals: { project: @project } %> -
- -
-
<%= date_string_with_milliseconds((@last_updated.blank? ? Time.zone.now : @last_updated.updated_at)) %>
-
-
-
-
-
-
-
diff --git a/modules/backlogs/app/views/rb_tasks/_task.html.erb b/modules/backlogs/app/views/rb_tasks/_task.html.erb deleted file mode 100644 index 9a1cc0c1c49..00000000000 --- a/modules/backlogs/app/views/rb_tasks/_task.html.erb +++ /dev/null @@ -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 -%> -
> -
-
<%= work_package_link_or_empty(task) %>
-
<%= id_or_empty(task) %>
-
-
<%= task.subject %>
-
-
<%= assignee_name_or_empty(task) %>
-
<%= assignee_id_or_empty(task) %>
-
-
" - fieldname="remaining_hours" - fieldlabel="<%= WorkPackage.human_attribute_name(:remaining_hours) %>"><%= remaining_hours(task) %>
-
-
-
<%= task.parent_id %>
-
<%= task.status_id %>
- <%= render(partial: "shared/model_errors", object: errors) if defined?(errors) && !errors.empty? %> -
-
diff --git a/modules/backlogs/app/views/rb_tasks/index.html.erb b/modules/backlogs/app/views/rb_tasks/index.html.erb deleted file mode 100644 index 8631ae80618..00000000000 --- a/modules/backlogs/app/views/rb_tasks/index.html.erb +++ /dev/null @@ -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. - -++#%> - -
-
<%= date_string_with_milliseconds((@last_updated.blank? ? Time.zone.parse(params[:after]) : @last_updated.updated_at), 0.001) %>
- <%= render partial: "task", collection: @tasks, locals: { include_meta: @include_meta } %> - <%- if @impediments %> - <%= render partial: "impediment", collection: @impediments, locals: { include_meta: @include_meta } %> - <%- end %> -
diff --git a/modules/backlogs/app/views/shared/_model_errors.html.erb b/modules/backlogs/app/views/shared/_model_errors.html.erb deleted file mode 100644 index 328edde8973..00000000000 --- a/modules/backlogs/app/views/shared/_model_errors.html.erb +++ /dev/null @@ -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. - -++#%> - -
- <% model_errors.full_messages.each do |err| %> -
<%= err %>
- <% end %> -
diff --git a/modules/backlogs/app/views/shared/_server_variables.js.erb b/modules/backlogs/app/views/shared/_server_variables.js.erb deleted file mode 100644 index 2c3199bbe29..00000000000 --- a/modules/backlogs/app/views/shared/_server_variables.js.erb +++ /dev/null @@ -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; - }; -}()); diff --git a/modules/backlogs/app/views/shared/not_configured.html.erb b/modules/backlogs/app/views/shared/not_configured.html.erb deleted file mode 100644 index 03689ecfd3b..00000000000 --- a/modules/backlogs/app/views/shared/not_configured.html.erb +++ /dev/null @@ -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 %> diff --git a/modules/backlogs/config/locales/en.yml b/modules/backlogs/config/locales/en.yml index db97608a617..3388143210d 100644 --- a/modules/backlogs/config/locales/en.yml +++ b/modules/backlogs/config/locales/en.yml @@ -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. diff --git a/modules/backlogs/config/routes.rb b/modules/backlogs/config/routes.rb index aa2344ae591..59f315a1936 100644 --- a/modules/backlogs/config/routes.rb +++ b/modules/backlogs/config/routes.rb @@ -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 diff --git a/modules/backlogs/lib/open_project/backlogs/burndown/series_raw_data.rb b/modules/backlogs/lib/open_project/backlogs/burndown/series_raw_data.rb index 74cda7bb4a4..fca4d796f13 100644 --- a/modules/backlogs/lib/open_project/backlogs/burndown/series_raw_data.rb +++ b/modules/backlogs/lib/open_project/backlogs/burndown/series_raw_data.rb @@ -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 diff --git a/modules/backlogs/lib/open_project/backlogs/engine.rb b/modules/backlogs/lib/open_project/backlogs/engine.rb index c194579d974..9fa3d25114c 100644 --- a/modules/backlogs/lib/open_project/backlogs/engine.rb +++ b/modules/backlogs/lib/open_project/backlogs/engine.rb @@ -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 diff --git a/modules/backlogs/lib/open_project/backlogs/list.rb b/modules/backlogs/lib/open_project/backlogs/list.rb index b7a22fb3ae9..d9f3d51f7ac 100644 --- a/modules/backlogs/lib/open_project/backlogs/list.rb +++ b/modules/backlogs/lib/open_project/backlogs/list.rb @@ -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 diff --git a/modules/backlogs/lib/open_project/backlogs/patches/permitted_params_patch.rb b/modules/backlogs/lib/open_project/backlogs/patches/permitted_params_patch.rb index 6ac3546d83c..ac4c649ebbb 100644 --- a/modules/backlogs/lib/open_project/backlogs/patches/permitted_params_patch.rb +++ b/modules/backlogs/lib/open_project/backlogs/patches/permitted_params_patch.rb @@ -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 diff --git a/modules/backlogs/lib/open_project/backlogs/patches/project_patch.rb b/modules/backlogs/lib/open_project/backlogs/patches/project_patch.rb index 9ddee562a10..e0389711fcb 100644 --- a/modules/backlogs/lib/open_project/backlogs/patches/project_patch.rb +++ b/modules/backlogs/lib/open_project/backlogs/patches/project_patch.rb @@ -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 diff --git a/modules/backlogs/lib/open_project/backlogs/patches/type_patch.rb b/modules/backlogs/lib/open_project/backlogs/patches/type_patch.rb deleted file mode 100644 index e3dbfaa34d1..00000000000 --- a/modules/backlogs/lib/open_project/backlogs/patches/type_patch.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/lib/open_project/backlogs/patches/version_patch.rb b/modules/backlogs/lib/open_project/backlogs/patches/version_patch.rb index 0adb1ee5ad3..7d59c6551da 100644 --- a/modules/backlogs/lib/open_project/backlogs/patches/version_patch.rb +++ b/modules/backlogs/lib/open_project/backlogs/patches/version_patch.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH diff --git a/modules/backlogs/lib/open_project/backlogs/patches/versions/row_component_patch.rb b/modules/backlogs/lib/open_project/backlogs/patches/versions/row_component_patch.rb deleted file mode 100644 index 34e1e92b4a6..00000000000 --- a/modules/backlogs/lib/open_project/backlogs/patches/versions/row_component_patch.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/lib/open_project/backlogs/patches/work_package_patch.rb b/modules/backlogs/lib/open_project/backlogs/patches/work_package_patch.rb index 028b67a4a64..d68cff29b97 100644 --- a/modules/backlogs/lib/open_project/backlogs/patches/work_package_patch.rb +++ b/modules/backlogs/lib/open_project/backlogs/patches/work_package_patch.rb @@ -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 diff --git a/modules/backlogs/spec/components/backlogs/backlog_component_spec.rb b/modules/backlogs/spec/components/backlogs/backlog_component_spec.rb deleted file mode 100644 index a963c3dcf30..00000000000 --- a/modules/backlogs/spec/components/backlogs/backlog_component_spec.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/spec/components/backlogs/backlog_header_component_spec.rb b/modules/backlogs/spec/components/backlogs/backlog_header_component_spec.rb deleted file mode 100644 index 40c25c764c3..00000000000 --- a/modules/backlogs/spec/components/backlogs/backlog_header_component_spec.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/spec/components/backlogs/backlog_menu_component_spec.rb b/modules/backlogs/spec/components/backlogs/backlog_menu_component_spec.rb deleted file mode 100644 index df85a03b0dc..00000000000 --- a/modules/backlogs/spec/components/backlogs/backlog_menu_component_spec.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/spec/components/backlogs/inbox_item_component_spec.rb b/modules/backlogs/spec/components/backlogs/inbox_item_component_spec.rb index d6f038344c2..1c1a7dbd3f9 100644 --- a/modules/backlogs/spec/components/backlogs/inbox_item_component_spec.rb +++ b/modules/backlogs/spec/components/backlogs/inbox_item_component_spec.rb @@ -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( diff --git a/modules/backlogs/spec/components/backlogs/sprint_component_spec.rb b/modules/backlogs/spec/components/backlogs/sprint_component_spec.rb index e5225be59e2..349f194b756 100644 --- a/modules/backlogs/spec/components/backlogs/sprint_component_spec.rb +++ b/modules/backlogs/spec/components/backlogs/sprint_component_spec.rb @@ -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") diff --git a/modules/backlogs/spec/components/backlogs/sprint_header_component_spec.rb b/modules/backlogs/spec/components/backlogs/sprint_header_component_spec.rb index d833055c1d8..e28e001ee57 100644 --- a/modules/backlogs/spec/components/backlogs/sprint_header_component_spec.rb +++ b/modules/backlogs/spec/components/backlogs/sprint_header_component_spec.rb @@ -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") diff --git a/modules/backlogs/spec/components/backlogs/sprint_menu_component_spec.rb b/modules/backlogs/spec/components/backlogs/sprint_menu_component_spec.rb index 46a4ce2128b..8cabee08346 100644 --- a/modules/backlogs/spec/components/backlogs/sprint_menu_component_spec.rb +++ b/modules/backlogs/spec/components/backlogs/sprint_menu_component_spec.rb @@ -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 diff --git a/modules/backlogs/spec/components/backlogs/sprint_page_header_component_spec.rb b/modules/backlogs/spec/components/backlogs/sprint_page_header_component_spec.rb index 34c50fd51e8..e26271d4de3 100644 --- a/modules/backlogs/spec/components/backlogs/sprint_page_header_component_spec.rb +++ b/modules/backlogs/spec/components/backlogs/sprint_page_header_component_spec.rb @@ -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 diff --git a/modules/backlogs/spec/components/backlogs/story_component_spec.rb b/modules/backlogs/spec/components/backlogs/story_component_spec.rb index 8871a242cc4..8f9e842113a 100644 --- a/modules/backlogs/spec/components/backlogs/story_component_spec.rb +++ b/modules/backlogs/spec/components/backlogs/story_component_spec.rb @@ -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 diff --git a/modules/backlogs/spec/components/backlogs/story_menu_list_component_spec.rb b/modules/backlogs/spec/components/backlogs/story_menu_list_component_spec.rb index 7d0d9e6d826..eb981472eb7 100644 --- a/modules/backlogs/spec/components/backlogs/story_menu_list_component_spec.rb +++ b/modules/backlogs/spec/components/backlogs/story_menu_list_component_spec.rb @@ -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" ) diff --git a/modules/backlogs/spec/contracts/work_packages/base_contract_spec.rb b/modules/backlogs/spec/contracts/work_packages/base_contract_spec.rb index 9014cb642fd..48edcc96a62 100644 --- a/modules/backlogs/spec/contracts/work_packages/base_contract_spec.rb +++ b/modules/backlogs/spec/contracts/work_packages/base_contract_spec.rb @@ -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 diff --git a/modules/backlogs/spec/controllers/backlogs_settings_controller_spec.rb b/modules/backlogs/spec/controllers/backlogs_settings_controller_spec.rb index 55b83075e89..a6a842b7059 100644 --- a/modules/backlogs/spec/controllers/backlogs_settings_controller_spec.rb +++ b/modules/backlogs/spec/controllers/backlogs_settings_controller_spec.rb @@ -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 diff --git a/modules/backlogs/spec/controllers/rb_sprints_controller_permissions_spec.rb b/modules/backlogs/spec/controllers/rb_sprints_controller_permissions_spec.rb deleted file mode 100644 index d415deb0221..00000000000 --- a/modules/backlogs/spec/controllers/rb_sprints_controller_permissions_spec.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/spec/controllers/rb_sprints_controller_spec.rb b/modules/backlogs/spec/controllers/rb_sprints_controller_spec.rb index 6eeee3d8f9d..cf37c280e6b 100644 --- a/modules/backlogs/spec/controllers/rb_sprints_controller_spec.rb +++ b/modules/backlogs/spec/controllers/rb_sprints_controller_spec.rb @@ -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 diff --git a/modules/backlogs/spec/controllers/rb_stories_controller_spec.rb b/modules/backlogs/spec/controllers/rb_stories_controller_spec.rb index 6b872b941e2..befc0704c9b 100644 --- a/modules/backlogs/spec/controllers/rb_stories_controller_spec.rb +++ b/modules/backlogs/spec/controllers/rb_stories_controller_spec.rb @@ -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 diff --git a/modules/backlogs/spec/controllers/rb_taskboards_controller_spec.rb b/modules/backlogs/spec/controllers/rb_taskboards_controller_spec.rb index 55bc8425ba1..5de66667681 100644 --- a/modules/backlogs/spec/controllers/rb_taskboards_controller_spec.rb +++ b/modules/backlogs/spec/controllers/rb_taskboards_controller_spec.rb @@ -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:) } diff --git a/modules/backlogs/spec/features/admin/backlogs_settings_spec.rb b/modules/backlogs/spec/features/admin/backlogs_settings_spec.rb deleted file mode 100644 index fa02e504783..00000000000 --- a/modules/backlogs/spec/features/admin/backlogs_settings_spec.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/spec/features/backlogs/context_menu_spec.rb b/modules/backlogs/spec/features/backlogs/context_menu_spec.rb deleted file mode 100644 index a9b685a4551..00000000000 --- a/modules/backlogs/spec/features/backlogs/context_menu_spec.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/spec/features/backlogs/create_story_spec.rb b/modules/backlogs/spec/features/backlogs/create_story_spec.rb deleted file mode 100644 index 896cbd758a6..00000000000 --- a/modules/backlogs/spec/features/backlogs/create_story_spec.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/spec/features/backlogs/start_finish_spec.rb b/modules/backlogs/spec/features/backlogs/start_finish_spec.rb index 36aeb964e26..d7035426996 100644 --- a/modules/backlogs/spec/features/backlogs/start_finish_spec.rb +++ b/modules/backlogs/spec/features/backlogs/start_finish_spec.rb @@ -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 diff --git a/modules/backlogs/spec/features/backlogs_in_backlog_view_spec.rb b/modules/backlogs/spec/features/backlogs_in_backlog_view_spec.rb deleted file mode 100644 index ef1b711c06e..00000000000 --- a/modules/backlogs/spec/features/backlogs_in_backlog_view_spec.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/spec/features/empty_backlogs_spec.rb b/modules/backlogs/spec/features/empty_backlogs_spec.rb index 9e4ba698e67..3a3a290ffa0 100644 --- a/modules/backlogs/spec/features/empty_backlogs_spec.rb +++ b/modules/backlogs/spec/features/empty_backlogs_spec.rb @@ -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 diff --git a/modules/backlogs/spec/features/impediments_spec.rb b/modules/backlogs/spec/features/impediments_spec.rb deleted file mode 100644 index cbdd37965a3..00000000000 --- a/modules/backlogs/spec/features/impediments_spec.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/spec/features/stories_in_backlog_spec.rb b/modules/backlogs/spec/features/stories_in_backlog_spec.rb deleted file mode 100644 index a66da2bb661..00000000000 --- a/modules/backlogs/spec/features/stories_in_backlog_spec.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/spec/features/tasks_on_taskboard_spec.rb b/modules/backlogs/spec/features/tasks_on_taskboard_spec.rb deleted file mode 100644 index 052b1a5ce6f..00000000000 --- a/modules/backlogs/spec/features/tasks_on_taskboard_spec.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/spec/features/work_packages/create_work_package_spec.rb b/modules/backlogs/spec/features/work_packages/create_work_package_spec.rb index cb21b602d31..747507100d4 100644 --- a/modules/backlogs/spec/features/work_packages/create_work_package_spec.rb +++ b/modules/backlogs/spec/features/work_packages/create_work_package_spec.rb @@ -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 diff --git a/modules/backlogs/spec/features/work_packages/drag_in_sprint_spec.rb b/modules/backlogs/spec/features/work_packages/drag_in_sprint_spec.rb index 9cb360a42cc..5e6c1185577 100644 --- a/modules/backlogs/spec/features/work_packages/drag_in_sprint_spec.rb +++ b/modules/backlogs/spec/features/work_packages/drag_in_sprint_spec.rb @@ -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 diff --git a/modules/backlogs/spec/features/work_packages/filter_spec.rb b/modules/backlogs/spec/features/work_packages/filter_spec.rb index 417795a41de..1d018872153 100644 --- a/modules/backlogs/spec/features/work_packages/filter_spec.rb +++ b/modules/backlogs/spec/features/work_packages/filter_spec.rb @@ -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, diff --git a/modules/backlogs/spec/features/work_packages/story_points_spec.rb b/modules/backlogs/spec/features/work_packages/story_points_spec.rb index 40100f1693b..62b0a05953a 100644 --- a/modules/backlogs/spec/features/work_packages/story_points_spec.rb +++ b/modules/backlogs/spec/features/work_packages/story_points_spec.rb @@ -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) } diff --git a/modules/backlogs/spec/forms/admin/settings/backlogs_settings_form_spec.rb b/modules/backlogs/spec/forms/admin/settings/backlogs_settings_form_spec.rb deleted file mode 100644 index 244ef87a0d5..00000000000 --- a/modules/backlogs/spec/forms/admin/settings/backlogs_settings_form_spec.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/spec/forms/admin/settings/backlogs_settings_model_spec.rb b/modules/backlogs/spec/forms/admin/settings/backlogs_settings_model_spec.rb deleted file mode 100644 index f8a17c7cb56..00000000000 --- a/modules/backlogs/spec/forms/admin/settings/backlogs_settings_model_spec.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_spec.rb b/modules/backlogs/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_spec.rb index 4a8bace20b3..fd4d7b0fcf0 100644 --- a/modules/backlogs/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_spec.rb +++ b/modules/backlogs/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_spec.rb @@ -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 diff --git a/modules/backlogs/spec/lib/api/v3/work_packages/work_package_representer_rendering_spec.rb b/modules/backlogs/spec/lib/api/v3/work_packages/work_package_representer_rendering_spec.rb index 2185062671b..4374382ebdc 100644 --- a/modules/backlogs/spec/lib/api/v3/work_packages/work_package_representer_rendering_spec.rb +++ b/modules/backlogs/spec/lib/api/v3/work_packages/work_package_representer_rendering_spec.rb @@ -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 diff --git a/modules/backlogs/spec/models/backlog_spec.rb b/modules/backlogs/spec/models/backlog_spec.rb index 3651c976a55..7ff38a76ad8 100644 --- a/modules/backlogs/spec/models/backlog_spec.rb +++ b/modules/backlogs/spec/models/backlog_spec.rb @@ -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 diff --git a/modules/backlogs/spec/models/burndown_spec.rb b/modules/backlogs/spec/models/burndown_spec.rb index ad6a8855578..54e3f20cdee 100644 --- a/modules/backlogs/spec/models/burndown_spec.rb +++ b/modules/backlogs/spec/models/burndown_spec.rb @@ -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 diff --git a/modules/backlogs/spec/models/impediment_spec.rb b/modules/backlogs/spec/models/impediment_spec.rb deleted file mode 100644 index 1e98a9fa984..00000000000 --- a/modules/backlogs/spec/models/impediment_spec.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/spec/models/story_spec.rb b/modules/backlogs/spec/models/story_spec.rb deleted file mode 100644 index 25e3236f0b3..00000000000 --- a/modules/backlogs/spec/models/story_spec.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/spec/models/task_spec.rb b/modules/backlogs/spec/models/task_spec.rb index 7cdeed4ab92..c80f8c010cf 100644 --- a/modules/backlogs/spec/models/task_spec.rb +++ b/modules/backlogs/spec/models/task_spec.rb @@ -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 diff --git a/modules/backlogs/spec/models/version_spec.rb b/modules/backlogs/spec/models/version_spec.rb deleted file mode 100644 index 46435be8a20..00000000000 --- a/modules/backlogs/spec/models/version_spec.rb +++ /dev/null @@ -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 diff --git a/modules/backlogs/spec/models/work_package_spec.rb b/modules/backlogs/spec/models/work_package_spec.rb index 9bd29fdf30f..a33fe51982a 100644 --- a/modules/backlogs/spec/models/work_package_spec.rb +++ b/modules/backlogs/spec/models/work_package_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH diff --git a/modules/backlogs/spec/requests/api/v3/sprints/project_index_resource_spec.rb b/modules/backlogs/spec/requests/api/v3/sprints/project_index_resource_spec.rb index bf139b801a7..5c585e15707 100644 --- a/modules/backlogs/spec/requests/api/v3/sprints/project_index_resource_spec.rb +++ b/modules/backlogs/spec/requests/api/v3/sprints/project_index_resource_spec.rb @@ -70,6 +70,5 @@ RSpec.describe "API v3 Sprint resource on project", content_type: :json do it_behaves_like "unauthorized access" end - end end diff --git a/modules/backlogs/spec/requests/api/v3/sprints/show_resource_spec.rb b/modules/backlogs/spec/requests/api/v3/sprints/show_resource_spec.rb index 94c7202cbcc..c2697842c9c 100644 --- a/modules/backlogs/spec/requests/api/v3/sprints/show_resource_spec.rb +++ b/modules/backlogs/spec/requests/api/v3/sprints/show_resource_spec.rb @@ -72,6 +72,5 @@ RSpec.describe "API v3 Sprint resource", content_type: :json do it_behaves_like "not found" end - end end diff --git a/modules/backlogs/spec/requests/rb_master_backlogs_spec.rb b/modules/backlogs/spec/requests/rb_master_backlogs_spec.rb index 0a85e427627..be69318dada 100644 --- a/modules/backlogs/spec/requests/rb_master_backlogs_spec.rb +++ b/modules/backlogs/spec/requests/rb_master_backlogs_spec.rb @@ -38,17 +38,11 @@ RSpec.describe "RbMasterBacklogs", :skip_csrf, type: :rails_request do shared_let(:user) { create(:admin) } shared_let(:project) { create(:project) } shared_let(:status) { create(:status, name: "status 1", is_default: true) } - shared_let(:sprint) { create(:sprint, project:) } - shared_let(:story) { create(:story, status:, version: sprint, project:) } + shared_let(:sprint) { create(:agile_sprint, project:) } + shared_let(:story) { create(:work_package, status:, sprint:, project:) } 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 #index" do it "is successful" do get "/projects/#{project.identifier}/backlogs" @@ -106,11 +100,6 @@ RSpec.describe "RbMasterBacklogs", :skip_csrf, type: :rails_request do context "with no sprints available" do before do - allow(Backlog) - .to receive(:owner_backlogs) - .with(project) - .and_return([]) - allow(Agile::Sprint) .to receive(:for_project) .with(project) diff --git a/modules/backlogs/spec/routing/rb_impediments_routing_spec.rb b/modules/backlogs/spec/routing/rb_impediments_routing_spec.rb deleted file mode 100644 index da5b89bc6ab..00000000000 --- a/modules/backlogs/spec/routing/rb_impediments_routing_spec.rb +++ /dev/null @@ -1,48 +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 RbImpedimentsController do - describe "routing" do - it { - expect(post("/projects/project_42/sprints/21/impediments")).to route_to(controller: "rb_impediments", - action: "create", - project_id: "project_42", - sprint_id: "21") - } - - it { - expect(put("/projects/project_42/sprints/21/impediments/85")).to route_to(controller: "rb_impediments", - action: "update", - project_id: "project_42", - sprint_id: "21", - id: "85") - } - end -end diff --git a/modules/backlogs/spec/routing/rb_queries_routing_spec.rb b/modules/backlogs/spec/routing/rb_queries_routing_spec.rb deleted file mode 100644 index 90f07a0cde0..00000000000 --- a/modules/backlogs/spec/routing/rb_queries_routing_spec.rb +++ /dev/null @@ -1,40 +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 RbQueriesController do - describe "routing" do - it { - expect(get("/projects/project_42/sprints/21/query")).to route_to(controller: "rb_queries", - action: "show", - project_id: "project_42", - sprint_id: "21") - } - end -end diff --git a/modules/backlogs/spec/routing/rb_sprints_routing_spec.rb b/modules/backlogs/spec/routing/rb_sprints_routing_spec.rb index 891d66cd8af..e1cc8bd04aa 100644 --- a/modules/backlogs/spec/routing/rb_sprints_routing_spec.rb +++ b/modules/backlogs/spec/routing/rb_sprints_routing_spec.rb @@ -32,31 +32,6 @@ require "spec_helper" RSpec.describe RbSprintsController do describe "routing" do - it { - expect(get("/projects/project_42/sprints/21/edit_name")).to route_to( - controller: "rb_sprints", - action: "edit_name", - project_id: "project_42", - id: "21" - ) - } - - it { - expect(get("/projects/project_42/sprints/21/show_name")).to route_to( - controller: "rb_sprints", - action: "show_name", - project_id: "project_42", - id: "21" - ) - } - - it { - expect(put("/projects/project_42/sprints/21")).to route_to(controller: "rb_sprints", - action: "update", - project_id: "project_42", - id: "21") - } - it { expect(get("/projects/project_42/sprints/new_dialog")).to route_to( controller: "rb_sprints", diff --git a/modules/backlogs/spec/routing/rb_stories_routing_spec.rb b/modules/backlogs/spec/routing/rb_stories_routing_spec.rb index f7be891e10a..3e855da4466 100644 --- a/modules/backlogs/spec/routing/rb_stories_routing_spec.rb +++ b/modules/backlogs/spec/routing/rb_stories_routing_spec.rb @@ -32,16 +32,6 @@ require "spec_helper" RSpec.describe RbStoriesController do describe "routing" do - it { - expect(put("/projects/project_42/sprints/21/stories/85/move_legacy")).to route_to( - controller: "rb_stories", - action: "move_legacy", - project_id: "project_42", - sprint_id: "21", - id: "85" - ) - } - it { expect(put("/projects/project_42/sprints/21/stories/85/move")).to route_to( controller: "rb_stories", diff --git a/modules/backlogs/spec/routing/rb_tasks_routing_spec.rb b/modules/backlogs/spec/routing/rb_tasks_routing_spec.rb deleted file mode 100644 index caef12132be..00000000000 --- a/modules/backlogs/spec/routing/rb_tasks_routing_spec.rb +++ /dev/null @@ -1,48 +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 RbTasksController do - describe "routing" do - it { - expect(post("/projects/project_42/sprints/21/tasks")).to route_to(controller: "rb_tasks", - action: "create", - project_id: "project_42", - sprint_id: "21") - } - - it { - expect(put("/projects/project_42/sprints/21/tasks/85")).to route_to(controller: "rb_tasks", - action: "update", - project_id: "project_42", - sprint_id: "21", - id: "85") - } - end -end diff --git a/modules/backlogs/spec/routing/rb_wikis_routing_spec.rb b/modules/backlogs/spec/routing/rb_wikis_routing_spec.rb deleted file mode 100644 index b29b5ed5aa8..00000000000 --- a/modules/backlogs/spec/routing/rb_wikis_routing_spec.rb +++ /dev/null @@ -1,47 +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 RbWikisController do - describe "routing" do - it { - expect(get("/projects/project_42/sprints/21/wiki")).to route_to(controller: "rb_wikis", - action: "show", - project_id: "project_42", - sprint_id: "21") - } - - it { - expect(get("/projects/project_42/sprints/21/wiki/edit")).to route_to(controller: "rb_wikis", - action: "edit", - project_id: "project_42", - sprint_id: "21") - } - end -end diff --git a/modules/backlogs/spec/services/impediments/create_services_spec.rb b/modules/backlogs/spec/services/impediments/create_services_spec.rb deleted file mode 100644 index 21012fa323a..00000000000 --- a/modules/backlogs/spec/services/impediments/create_services_spec.rb +++ /dev/null @@ -1,186 +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::CreateService do - let(:instance) { described_class.new(user:) } - let(:impediment_subject) { "Impediment A" } - - let(:user) { create(:user) } - let(:role) { create(:project_role, permissions: %i(add_work_packages assign_versions work_package_assigned)) } - let(:type_feature) { create(:type_feature) } - let(:type_task) { create(:type_task) } - let(:priority) { create(:priority, is_default: true) } - let(:feature) do - build(:work_package, - type: type_feature, - project:, - author: user, - priority:, - status: status1) - end - let(:version) { create(:version, project:) } - - let(:project) do - project = create(:project, types: [type_feature, type_task]) - - create(:member, principal: user, - project:, - roles: [role]) - - project - end - - let(:status1) { create(:status, name: "status 1", is_default: true) } - - 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 - - shared_examples_for "impediment creation" do - it { expect(subject.subject).to eql impediment_subject } - it { expect(subject.author).to eql User.current } - it { expect(subject.project).to eql project } - it { expect(subject.version).to eql version } - it { expect(subject.priority).to eql priority } - it { expect(subject.status).to eql status1 } - it { expect(subject.type).to eql type_task } - it { expect(subject.assigned_to).to eql user } - end - - shared_examples_for "impediment creation with 1 blocking relationship" do - it_behaves_like "impediment creation" - - it { expect(subject.blocks_relations.size).to eq(1) } - it { expect(subject.blocks_relations[0].to).to eql feature } - end - - shared_examples_for "impediment creation with no blocking relationship" do - it_behaves_like "impediment creation" - - it { expect(subject.blocks_relations.size).to eq(0) } - end - - describe "WITH a blocking relationship to a story" do - describe "WITH the story having the same version" do - subject do - call = instance.call(attributes: { subject: impediment_subject, - assigned_to_id: user.id, - priority_id: priority.id, - blocks_ids: feature.id.to_s, - status_id: status1.id, - version_id: version.id, - project_id: project.id }) - call.result - end - - before do - feature.version = version - feature.save - end - - it_behaves_like "impediment creation with 1 blocking relationship" - it { expect(subject).not_to be_new_record } - it { expect(subject.blocks_relations[0]).not_to be_new_record } - end - - describe "WITH the story having another version" do - subject do - call = instance.call(attributes: { subject: impediment_subject, - assigned_to_id: user.id, - priority_id: priority.id, - blocks_ids: feature.id.to_s, - status_id: status1.id, - version_id: version.id, - project_id: project.id }) - call.result - end - - before do - feature.version = create(:version, project:, name: "another version") - feature.save - end - - it_behaves_like "impediment creation with no blocking relationship" - it { expect(subject).to be_new_record } - - it { - expect(subject.errors.symbols_for(:blocks_ids)) - .to eq [:can_only_contain_work_packages_of_current_sprint] - } - end - - describe "WITH the story being non existent" do - subject do - call = instance.call(attributes: { subject: impediment_subject, - assigned_to_id: user.id, - priority_id: priority.id, - blocks_ids: "0", - status_id: status1.id, - version_id: version.id, - project_id: project.id }) - call.result - end - - it_behaves_like "impediment creation with no blocking relationship" - it { expect(subject).to be_new_record } - - it { - expect(subject.errors.symbols_for(:blocks_ids)) - .to eq [:can_only_contain_work_packages_of_current_sprint] - } - end - end - - describe "WITHOUT a blocking relationship defined" do - subject do - call = instance.call(attributes: { subject: impediment_subject, - assigned_to_id: user.id, - blocks_ids: "", - priority_id: priority.id, - status_id: status1.id, - version_id: version.id, - project_id: project.id }) - call.result - end - - it_behaves_like "impediment creation with no blocking relationship" - it { expect(subject).to be_new_record } - - it { - expect(subject.errors.symbols_for(:blocks_ids)) - .to eq [:must_block_at_least_one_work_package] - } - end -end diff --git a/modules/backlogs/spec/services/impediments/update_service_spec.rb b/modules/backlogs/spec/services/impediments/update_service_spec.rb deleted file mode 100644 index 2a5237e4357..00000000000 --- a/modules/backlogs/spec/services/impediments/update_service_spec.rb +++ /dev/null @@ -1,199 +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::UpdateService, type: :model do - let(:instance) { described_class.new(user:, impediment:) } - - let(:user) { create(:user) } - let(:role) { create(:project_role, permissions: %i(edit_work_packages view_work_packages)) } - let(:type_feature) { create(:type_feature) } - let(:type_task) { create(:type_task) } - let(:priority) { impediment.priority } - let(:task) do - build(:task, type: type_task, - project:, - author: user, - priority:, - status: status1) - end - let(:feature) do - build(:work_package, type: type_feature, - project:, - author: user, - priority:, - status: status1) - end - let(:version) { create(:version, project:) } - - let(:project) do - project = create(:project, types: [type_feature, type_task]) - - create(:member, principal: user, - project:, - roles: [role]) - - project - end - - let(:status1) { create(:status, name: "status 1", is_default: true) } - let(:status2) { create(:status, name: "status 2") } - let(:type_workflow) do - Workflow.create(type_id: type_task.id, - old_status: status1, - new_status: status2, - role:) - end - let(:impediment) do - build(:impediment, author: user, - version:, - assigned_to: user, - project:, - type: type_task, - status: status1) - 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 - - status1.save - project.save - type_workflow.save - - feature.version = version - feature.save - - impediment.blocks_ids = feature.id.to_s - impediment.save - end - - shared_examples_for "impediment update" do - it { expect(subject.author).to eql user } - it { expect(subject.project).to eql project } - it { expect(subject.version).to eql version } - it { expect(subject.priority).to eql priority } - it { expect(subject.status).to eql status2 } - it { expect(subject.type).to eql type_task } - it { expect(subject.blocks_ids).to eql blocks.split(/\D+/).map(&:to_i) } - end - - shared_examples_for "impediment update with changed blocking relationship" do - it_behaves_like "impediment update" - it { expect(subject.relations.size).to eq(1) } - it { expect(subject.relations[0]).not_to be_new_record } - it { expect(subject.relations[0].to).to eql story } - it { expect(subject.relations[0].relation_type).to eql Relation::TYPE_BLOCKS } - end - - shared_examples_for "impediment update with unchanged blocking relationship" do - it_behaves_like "impediment update" - it { expect(subject.relations.size).to eq(1) } - it { expect(subject.relations[0]).not_to be_changed } - it { expect(subject.relations[0].to).to eql feature } - it { expect(subject.relations[0].relation_type).to eql Relation::TYPE_BLOCKS } - end - - subject do - call = instance.call(attributes: { blocks_ids: blocks, - status_id: status2.id.to_s }) - - call.result - end - - describe "WHEN changing the blocking relationship to another story" do - let(:story) do - build(:work_package, - subject: "another story", - type: type_feature, - project:, - author: user, - priority:, - status: status1) - end - let(:blocks) { story.id.to_s } - let(:story_version) { version } - - before do - story.version = story_version - story.save! - end - - describe "WITH the story having the same version" do - it_behaves_like "impediment update with changed blocking relationship" - it { expect(subject).not_to be_changed } - end - - describe "WITH the story having another version" do - let(:story_version) { create(:version, project:, name: "another version") } - - it_behaves_like "impediment update with unchanged blocking relationship" - it "is not saved successfully" do - expect(subject).to be_changed - end - - it { - expect(subject.errors.symbols_for(:blocks_ids)) - .to eq [:can_only_contain_work_packages_of_current_sprint] - } - end - - describe "WITH the story being non existent" do - let(:blocks) { "0" } - - it_behaves_like "impediment update with unchanged blocking relationship" - it "is not saved successfully" do - expect(subject).to be_changed - end - - it { - expect(subject.errors.symbols_for(:blocks_ids)) - .to eq [:can_only_contain_work_packages_of_current_sprint] - } - end - end - - describe "WITHOUT a blocking relationship defined" do - let(:blocks) { "" } - - it_behaves_like "impediment update with unchanged blocking relationship" - it "is not saved successfully" do - expect(subject).to be_changed - end - - it { - expect(subject.errors.symbols_for(:blocks_ids)) - .to eq [:must_block_at_least_one_work_package] - } - end -end diff --git a/modules/backlogs/spec/services/work_packages/rebuild_positions_service_integration_spec.rb b/modules/backlogs/spec/services/work_packages/rebuild_positions_service_integration_spec.rb index ae4cc7f8380..85b509f44ec 100644 --- a/modules/backlogs/spec/services/work_packages/rebuild_positions_service_integration_spec.rb +++ b/modules/backlogs/spec/services/work_packages/rebuild_positions_service_integration_spec.rb @@ -47,13 +47,7 @@ RSpec.describe WorkPackages::RebuildPositionsService, "integration", type: :mode shared_let(:sprint1_wp1) { create_work_package(subject: "Sprint 1 WorkPackage 1", sprint: sprint1, position: nil) } shared_let(:sprint1_wp2) { create_work_package(subject: "Sprint 1 WorkPackage 2", sprint: sprint1, position: 1) } shared_let(:sprint1_wp3) { create_work_package(subject: "Sprint 1 WorkPackage 3", sprint: sprint1, position: 2) } - shared_let(:sprint1_wp4) do - create_work_package(subject: "Sprint 1 WorkPackage 4", sprint: sprint1, position: 2).tap do - # Force wp3 back to position 2 so that wp3 and wp4 are genuinely - # duplicated — the service must break the tie via created_at. - sprint1_wp3.update_column(:position, 2) - end - end + shared_let(:sprint1_wp4) { create_work_package(subject: "Sprint 1 WorkPackage 4", sprint: sprint1, position: 2) } shared_let(:sprint1_wp5) { create_work_package(subject: "Sprint 1 WorkPackage 5", sprint: sprint1, position: nil) } shared_let(:sprint2_wp1) { create_work_package(subject: "Sprint 2 WorkPackage 1", sprint: sprint2, position: 3) } @@ -83,8 +77,8 @@ RSpec.describe WorkPackages::RebuildPositionsService, "integration", type: :mode expect(WorkPackage.where(sprint: sprint1).to_h { [it.subject, it.position] }) .to eql( sprint1_wp2.subject => 1, - sprint1_wp3.subject => 2, - sprint1_wp4.subject => 3, + sprint1_wp4.subject => 2, + sprint1_wp3.subject => 3, sprint1_wp1.subject => 4, sprint1_wp5.subject => 5 ) @@ -119,13 +113,15 @@ RSpec.describe WorkPackages::RebuildPositionsService, "integration", type: :mode it "fixes only the work packages in the other project" do # rubocop:disable Rspec/ExampleLength # sprint1 and sprint2 belong to project1, so their positions are - # unchanged by rebuilding project2. + # unchanged by rebuilding project2. The initial positions reflect + # acts_as_list side effects during shared_let creation (e.g. wp4 + # inserting at position 2 shifts wp3 from 2 to 3). expect(WorkPackage.where(sprint: sprint1).to_h { [it.subject, it.position] }) .to eql( sprint1_wp1.subject => nil, sprint1_wp2.subject => 1, - sprint1_wp3.subject => 2, sprint1_wp4.subject => 2, + sprint1_wp3.subject => 3, sprint1_wp5.subject => nil ) @@ -161,8 +157,8 @@ RSpec.describe WorkPackages::RebuildPositionsService, "integration", type: :mode expect(WorkPackage.where(sprint: sprint1).to_h { [it.subject, it.position] }) .to eql( sprint1_wp2.subject => 1, - sprint1_wp3.subject => 2, - sprint1_wp4.subject => 3, + sprint1_wp4.subject => 2, + sprint1_wp3.subject => 3, sprint1_wp1.subject => 4, sprint1_wp5.subject => 5 ) diff --git a/modules/backlogs/spec/support/pages/backlog.rb b/modules/backlogs/spec/support/pages/backlog.rb index 389748aa6df..8f60b597cc6 100644 --- a/modules/backlogs/spec/support/pages/backlog.rb +++ b/modules/backlogs/spec/support/pages/backlog.rb @@ -44,7 +44,7 @@ module Pages attributes.each do |key, value| details_view .edit_field(key.to_s.camelize(:lower)) - .update(value) # rubocop:disable Rails/SaveBang + .update(value) details_view.expect_and_dismiss_toaster message: "Successful update." end @@ -462,10 +462,6 @@ module Pages test_selector("sprint-#{sprint.id}") end - def backlog_selector(backlog) - "#backlog_#{backlog.id}" - end - def story_selector(story) "#story_#{story.id}" end diff --git a/modules/backlogs/spec/support/pages/backlogs.rb b/modules/backlogs/spec/support/pages/backlogs.rb deleted file mode 100644 index 4d998fe57b0..00000000000 --- a/modules/backlogs/spec/support/pages/backlogs.rb +++ /dev/null @@ -1,241 +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 "support/pages/page" - -module Pages - # This is the legacy backlogs page. It can be removed soon. - class Backlogs < Page - attr_reader :project - - def initialize(project) - super() - @project = project - end - - def enter_edit_backlog_mode(backlog) - within_backlog_menu(backlog) do |menu| - menu.find(:menuitem, "Edit sprint").click - end - end - - def alter_attributes_in_details_view(story, **attributes) - within_details_view(story) do |details_view| - attributes.each do |key, value| - details_view - .edit_field(key.to_s.camelize(:lower)) - .update(value) # rubocop:disable Rails/SaveBang - - details_view.expect_and_dismiss_toaster message: "Successful update." - end - end - end - - def alter_attributes_in_edit_backlog_mode(backlog, **attributes) - within_backlog(backlog) do - attributes.each do |key, value| - case key - when :name - fill_in "Name", with: value - when :start_date - fill_in "Start date", with: value - when :effective_date - fill_in "Finish date", with: value - else - raise NotImplementedError - end - end - end - end - - def save_backlog_from_edit_mode(backlog) - within_backlog(backlog) do - find_field("Name").send_keys :return - end - end - - def edit_backlog(backlog, **attributes) - enter_edit_backlog_mode(backlog) - - alter_attributes_in_edit_backlog_mode(backlog, **attributes) - - save_backlog_from_edit_mode(backlog) - end - - def edit_story_in_details_view(story, **attributes) - click_in_story_menu(story, "Open details view") - - alter_attributes_in_details_view(story, **attributes) - end - - def click_in_backlog_menu(backlog, item_name) - within_backlog_menu(backlog) do |menu| - menu.find(:menuitem, text: item_name).click - end - end - - def click_in_story_menu(story, item_name) - within_story_menu(story) do |menu| - menu.find(:menuitem, text: item_name).click - end - end - - def fold_backlog(backlog) - within_backlog(backlog) do - find(:button, aria: { controls: "backlog_#{backlog.id}-list" }).click - end - end - - def expect_sprint(sprint) - expect(page) - .to have_css("#sprint_backlogs_container #{backlog_selector(sprint)}") - end - - def expect_backlog(sprint) - expect(page) - .to have_css("#owner_backlogs_container #{backlog_selector(sprint)}") - end - - def expect_story_in_backlog(story, backlog) - within_backlog(backlog) do - expect(page) - .to have_selector(story_selector(story).to_s) - end - end - - def expect_story_not_in_backlog(story, backlog) - within_backlog(backlog) do - expect(page) - .to have_no_selector(story_selector(story).to_s) - end - end - - def expect_velocity(backlog, velocity) - within("#backlog_#{backlog.id} .velocity") do - expect(page) - .to have_content(velocity.to_s) - end - end - - def expect_stories_in_order(backlog, *stories) - within_backlog(backlog) do - ids = stories.map { |s| "story_#{s.id}" } - existing_ids_in_order = all(ids.map { |id| "##{id}" }.join(", ")).pluck(:id) - - expect(existing_ids_in_order) - .to eql(ids) - end - end - - def expect_and_dismiss_error(message) - expect(page).to have_content message - - click_on "Cancel" - end - - def path - backlogs_project_backlogs_path(project) - end - - def within_backlog_menu(backlog, &) - within_backlog(backlog) do - button = find(:button, accessible_name: "Backlog actions") - button.click - - within_menu_controlled_by(button, &) - end - end - - def within_story_menu(story, &) - within_story(story) do - button = find(:button, accessible_name: "Story actions") - button.click - - within_menu_controlled_by(button, &) - end - end - - def within_details_view(story, &) - details_view = expect_details_view(story) - - yield details_view - end - - def expect_details_view(story) - details_view = Pages::PrimerizedSplitWorkPackage.new(story) - details_view.expect_tab :overview - details_view.expect_subject - - expect(page).to have_current_path details_backlogs_project_backlogs_path(story.project, story) - wait_for_network_idle - - details_view - end - - private - - def within_story(story, &) - within(story_selector(story), &) - end - - def within_backlog(backlog, &) - within(backlog_selector(backlog), &) - end - - def within_sprint(sprint, &) - within(sprint_selector(sprint), &) - end - - def sprint_selector(sprint) - test_selector("sprint-#{sprint.id}") - end - - def backlog_selector(backlog) - "#backlog_#{backlog.id}" - end - - def story_selector(story) - "#story_#{story.id}" - end - - def work_package_selector(work_package) - test_selector("work-package-#{work_package.id}") - end - - def within_menu_controlled_by(button) - menu_id = button[:controls] || button["aria-controls"] - menu = page.find(:menu, id: menu_id) - - within(menu) do - yield page - end - end - end -end diff --git a/modules/backlogs/spec/support/pages/taskboard.rb b/modules/backlogs/spec/support/pages/taskboard.rb deleted file mode 100644 index 0c9e28d2b66..00000000000 --- a/modules/backlogs/spec/support/pages/taskboard.rb +++ /dev/null @@ -1,131 +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 "support/pages/page" - -module Pages - class Taskboard < Page - attr_reader :project, :sprint - - def initialize(project, sprint) - super() - @project = project - @sprint = sprint - end - - def expect_story(story) - expect(page) - .to have_selector(story_selector(story)) - end - - def expect_task(task) - expect(page) - .to have_css("#work_package_#{task.id}") - end - - def expect_task_in_story_column(task, story, column) - within ".story_#{story.id} td:nth-of-type(#{column + 2})" do - expect(page) - .to have_css("#work_package_#{task.id}") - end - end - - def expect_work_package_not_visible(work_package) - expect(page) - .to have_no_content(work_package.subject) - end - - def expect_color_for_task(hex_color, task) - expect(page) - .to have_css("#work_package_#{task.id}[style='background-color:#{hex_color};']") - end - - def add_task(story, attributes) - find(".story_#{story.id} td.add_new").click - - change_attributes_in_modal(attributes) - - expect(page).to have_no_css(".ui-dialog") - expect(page).to have_no_css("#work_package_") - end - - def update_task(task, attributes) - find("#work_package_#{task.id}").click - - change_attributes_in_modal(attributes) - - expect(page).to have_no_css(".ui-dialog") - - sleep(0.5) - end - - def drag_to_task(dragged_task, target, before_or_after = :before) - moved_element = find("#work_package_#{dragged_task.id}") - target_element = find("#work_package_#{target.id}") - - drag_n_drop_element from: moved_element, - to: target_element, - offset_x: before_or_after == :before ? -40 : +30, - offset_y: 0 - end - - def drag_to_column(dragged_task, story, col_number) - moved_element = find("#work_package_#{dragged_task.id}") - target_element = find(".story_#{story.id} td:nth-of-type(#{col_number + 2})") - - moved_element.drag_to(target_element) - end - - def path - backlogs_project_sprint_taskboard_path(project, sprint) - end - - private - - def story_selector(story) - "#story_#{story.id}" - end - - def change_attributes_in_modal(attributes) - within ".ui-dialog" do - attributes.each do |key, value| - case key - when :subject - fill_in "Subject", with: value - when :assignee - select value, from: "Assignee" - when :remaining_hours - fill_in "Remaining work", with: value - end - end - - click_button "OK" - end - end - end -end diff --git a/modules/backlogs/spec/views/rb_burndown_charts/show_spec.rb b/modules/backlogs/spec/views/rb_burndown_charts/show_spec.rb index 4741e3ce98a..71c76e5262f 100644 --- a/modules/backlogs/spec/views/rb_burndown_charts/show_spec.rb +++ b/modules/backlogs/spec/views/rb_burndown_charts/show_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -38,9 +40,7 @@ RSpec.describe "rb_burndown_charts/show" do let(:role_forbidden) { create(:project_role) } # We need to create these as some view helpers access the database let(:statuses) do - [create(:status), - create(:status), - create(:status)] + create_list(:status, 3) end let(:type_task) { create(:type_task) } @@ -54,32 +54,32 @@ RSpec.describe "rb_burndown_charts/show" do end let(:story_a) do - create(:story, status: statuses[0], - project:, - type: type_feature, - version: sprint, - priority: issue_priority) + create(:work_package, status: statuses[0], + project:, + type: type_feature, + sprint: sprint, + priority: issue_priority) end let(:story_b) do - create(:story, status: statuses[1], - project:, - type: type_feature, - version: sprint, - priority: issue_priority) + create(:work_package, status: statuses[1], + project:, + type: type_feature, + sprint: sprint, + priority: issue_priority) end let(:story_c) do - create(:story, status: statuses[2], - project:, - type: type_feature, - version: sprint, - priority: issue_priority) + create(:work_package, status: statuses[2], + project:, + type: type_feature, + sprint: sprint, + priority: issue_priority) end let(:stories) { [story_a, story_b, story_c] } let(:sprint) do - create(:sprint, project:, start_date: Date.today - 1.week, effective_date: Date.today + 1.week) + create(:agile_sprint, project:, start_date: Time.zone.today - 1.week, finish_date: Time.zone.today + 1.week) end let(:task) do - task = create(:task, project:, status: statuses[0], version: sprint, type: type_task) + task = create(:task, project:, status: statuses[0], sprint: sprint, type: type_task) # This is necessary as for some unknown reason passing the parent directly # leads to the task searching for the parent with 'root_id' is NULL, which # is not the case as the story has its own id as root_id @@ -88,8 +88,6 @@ RSpec.describe "rb_burndown_charts/show" do end before do - allow(Setting).to receive(:plugin_openproject_backlogs).and_return({ "story_types" => [type_feature.id], - "task_type" => type_task.id }) view.extend BurndownChartsHelper # We directly force the creation of stories,statuses by calling the method @@ -100,19 +98,19 @@ RSpec.describe "rb_burndown_charts/show" do it "renders a sprint with dates" do assign(:sprint, sprint) assign(:project, project) - assign(:burndown, sprint.burndown(project)) + assign(:burndown, Burndown.new(sprint, project)) render expect(view).to render_template(partial: "_burndown", count: 1) end - it "renders a version without dates" do + it "renders a sprint without dates" do sprint.start_date = nil - sprint.effective_date = nil + sprint.finish_date = nil sprint.save assign(:sprint, sprint) assign(:project, project) - assign(:burndown, sprint.burndown(project)) + assign(:burndown, nil) render diff --git a/modules/backlogs/spec/views/rb_master_backlogs/index.html.erb_spec.rb b/modules/backlogs/spec/views/rb_master_backlogs/index.html.erb_spec.rb deleted file mode 100644 index ae8002731e9..00000000000 --- a/modules/backlogs/spec/views/rb_master_backlogs/index.html.erb_spec.rb +++ /dev/null @@ -1,91 +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 "rb_master_backlogs/index" do - let(:user) { create(:user) } - let(:role_allowed) do - create(:project_role, - permissions: %i[view_sprints]) - end - let(:statuses) do - [create(:status, is_default: true), - create(:status), - create(:status)] - end - let(:type_task) { create(:type_task) } - let(:type_feature) { create(:type_feature) } - let(:issue_priority) { create(:priority) } - let(:project) do - project = create(:project, types: [type_feature, type_task]) - project.members = [create(:member, principal: user, project:, roles: [role_allowed])] - project - end - let(:story_a) do - create(:story, status: statuses[0], - project:, - type: type_feature, - version: sprint, - priority: issue_priority) - end - let(:story_b) do - create(:story, status: statuses[1], - project:, - type: type_feature, - version: sprint, - priority: issue_priority) - end - let(:story_c) do - create(:story, status: statuses[2], - project:, - type: type_feature, - version: sprint, - priority: issue_priority) - end - let(:stories) { [story_a, story_b, story_c] } - let(:sprint) { create(:sprint, project:) } - - before do - allow(Setting).to receive(:plugin_openproject_backlogs).and_return({ "story_types" => [type_feature.id], - "task_type" => type_task.id }) - view.extend RbCommonHelper - view.extend RbMasterBacklogsHelper - allow(view).to receive(:current_user).and_return(user) - - assign(:project, project) - assign(:sprint, sprint) - assign(:owner_backlogs, Backlog.owner_backlogs(project)) - assign(:sprint_backlogs, Backlog.sprint_backlogs(project)) - - allow(User).to receive(:current).and_return(user) - - # We directly force the creation of stories by calling the method - stories - end -end diff --git a/modules/backlogs/spec/views/rb_taskboards/show_spec.rb b/modules/backlogs/spec/views/rb_taskboards/show_spec.rb deleted file mode 100644 index 6b1cff44e8c..00000000000 --- a/modules/backlogs/spec/views/rb_taskboards/show_spec.rb +++ /dev/null @@ -1,250 +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 "rb_taskboards/show" do - let(:user1) { create(:user) } - let(:user2) { create(:user) } - let(:role_allowed) do - create(:project_role, - permissions: %i[add_work_packages edit_work_packages manage_subtasks]) - end - let(:role_forbidden) { create(:project_role) } - # We need to create these as some view helpers access the database - let(:statuses) { create_list(:status, 3) } - - let(:type_task) { create(:type_task) } - let(:type_feature) { create(:type_feature) } - let(:issue_priority) { create(:priority) } - let(:project) do - project = create(:project, types: [type_feature, type_task]) - project.members = [create(:member, principal: user1, project:, roles: [role_allowed]), - create(:member, principal: user2, project:, roles: [role_forbidden])] - project - end - - let(:story_a) do - create(:story, status: statuses[0], - project:, - type: type_feature, - version: sprint, - priority: issue_priority) - end - let(:story_b) do - create(:story, status: statuses[1], - project:, - type: type_feature, - version: sprint, - priority: issue_priority) - end - let(:story_c) do - create(:story, status: statuses[2], - project:, - type: type_feature, - version: sprint, - priority: issue_priority) - end - let(:stories) { [story_a, story_b, story_c] } - let(:sprint) { create(:sprint, project:) } - let(:task) do - task = create(:task, project:, status: statuses[0], version: sprint, type: type_task) - # This is necessary as for some unknown reason passing the parent directly - # leads to the task searching for the parent with 'root_id' is NULL, which - # is not the case as the story has its own id as root_id - task.parent_id = story_a.id - task - end - let(:impediment) do - create(:impediment, project:, status: statuses[0], version: sprint, blocks_ids: task.id.to_s, - type: type_task) - end - - before do - allow(Setting).to receive(:plugin_openproject_backlogs).and_return({ "story_types" => [type_feature.id], - "task_type" => type_task.id }) - view.extend RbCommonHelper - view.extend TaskboardsHelper - - assign(:project, project) - assign(:sprint, sprint) - assign(:statuses, statuses) - - # We directly force the creation of stories by calling the method - stories - end - - describe "story blocks" do - it "contains the story id" do - render - - stories.each do |story| - expect(rendered).to have_css("#story_#{story.id} .id", text: story.id.to_s) - end - end - - it "has a title containing the story subject" do - render - - stories.each do |story| - expect(rendered).to have_css("#story_#{story.id} .subject", text: story.subject) - end - end - - it "contains the story status" do - render - - stories.each do |story| - expect(rendered).to have_css("#story_#{story.id} .status", text: story.status.name) - end - end - - it "contains the user it is assigned to" do - story_a.update(assigned_to: user1) - story_c.update(assigned_to: user2) - - render - - stories.each do |story| - expected_text = story.assigned_to ? story.assigned_to.name : "Unassigned" - expect(rendered).to have_css("#story_#{story.id} .assigned_to_id", text: expected_text) - end - end - end - - describe "create buttons" do - it "renders clickable + buttons for all stories with the right permissions" do - allow(User).to receive(:current).and_return(user1) - - render - - stories.each do |story| - assert_select "tr.story_#{story.id} td.add_new" do |td| - expect(td.count).to eq 1 - expect(td.first).to have_content "+" - expect(td.first[:class]).to include "clickable" - end - end - end - - it "does not render a clickable + buttons for all stories without the right permissions" do - allow(User).to receive(:current).and_return(user2) - - render - - stories.each do |story| - assert_select "tr.story_#{story.id} td.add_new" do |td| - expect(td.count).to eq 1 - expect(td.first).to have_no_content "+" - expect(td.first[:class]).not_to include "clickable" - end - end - end - - it "renders clickable + buttons for impediments with the right permissions" do - allow(User).to receive(:current).and_return(user1) - - render - - stories.each do |_story| - assert_select "#impediments td.add_new" do |td| - expect(td.count).to eq 1 - expect(td.first).to have_content "+" - expect(td.first[:class]).to include "clickable" - end - end - end - - it "does not render a clickable + buttons for impediments without the right permissions" do - allow(User).to receive(:current).and_return(user2) - - render - - stories.each do |_story| - assert_select "#impediments td.add_new" do |td| - expect(td.count).to eq 1 - expect(td.first).to have_no_content "+" - expect(td.first[:class]).not_to include "clickable" - end - end - end - end - - describe "update tasks or impediments" do - it "allows edit and drag for all tasks with the right permissions" do - allow(User).to receive(:current).and_return(user1) - task - impediment - render - - assert_select ".model.work_package.task" do |task| - expect(task.count).to eq 1 - expect(task.first).to have_no_css ".task.prevent_edit" - end - end - - it "does not allow to edit and drag for all tasks without the right permissions" do - allow(User).to receive(:current).and_return(user2) - task - impediment - - render - - assert_select ".model.work_package.task" do |task| - expect(task.count).to eq 1 - expect(task.first).to have_css ".task.prevent_edit" - end - end - - it "allows edit and drag for all impediments with the right permissions" do - allow(User).to receive(:current).and_return(user1) - task - impediment - - render - - assert_select ".model.work_package.impediment" do |impediment| - expect(impediment.count).to eq 3 # 2 additional for the task and the invisible form - expect(impediment.first).to have_no_css ".impediment.prevent_edit" - end - end - - it "does not allow to edit and drag for all impediments without the right permissions" do - allow(User).to receive(:current).and_return(user2) - task - impediment - - render - - assert_select ".model.work_package.impediment" do |impediment| - expect(impediment.count).to eq 3 # 2 additional for the task and the invisible form - expect(impediment.first).to have_css ".impediment.prevent_edit" - end - end - end -end diff --git a/modules/backlogs/spec/views/shared/not_configured_spec.rb b/modules/backlogs/spec/views/shared/not_configured_spec.rb deleted file mode 100644 index 57d240e138f..00000000000 --- a/modules/backlogs/spec/views/shared/not_configured_spec.rb +++ /dev/null @@ -1,37 +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 "shared/not_configured" do - before { assign(:project, create(:project)) } - - it "renders without errors" do - render - end -end diff --git a/spec/models/type/attribute_groups_spec.rb b/spec/models/type/attribute_groups_spec.rb index 68e51400244..f102ef9c738 100644 --- a/spec/models/type/attribute_groups_spec.rb +++ b/spec/models/type/attribute_groups_spec.rb @@ -222,14 +222,15 @@ RSpec.describe Type do other_group = type.attribute_groups.detect { |g| g.key == :other } expect(other_group).to be_present - expect(other_group.attributes).to eq([custom_field.attribute_name]) + expect(other_group.attributes).to eq(%w[position] + [custom_field.attribute_name]) # It is removed again when resetting it type.reset_attribute_groups expect(type.custom_field_ids).to be_empty other_group = type.attribute_groups.detect { |g| g.key == :other } - expect(other_group).not_to be_present + expect(other_group).to be_present + expect(other_group.attributes).to eq(%w[position]) end end diff --git a/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb b/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb index 037cb463657..b86b0c3240f 100644 --- a/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb +++ b/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb @@ -313,7 +313,7 @@ RSpec.describe WorkPackage::PDFExport::WorkPackageToPdf do "amet", ", consetetur sadipscing elitr.", " ", "@OpenProject Admin", "Image Caption", "Image Redirect", - "Foo", + "Foo" ].flatten.join(" ") expect(result).to eq(expected_result) expect(result).not_to include("DisabledCustomField") @@ -474,7 +474,7 @@ RSpec.describe WorkPackage::PDFExport::WorkPackageToPdf do "My link in table", "https://example.com", "No replacement of:", "workPackageValue:1:assignee", "workPackageLabel:assignee", "workPackageValue:2:assignee workPackageLabel:assignee", - "workPackageValue:3:assignee", "workPackageLabel:assignee", + "workPackageValue:3:assignee", "workPackageLabel:assignee" ] end @@ -505,7 +505,7 @@ RSpec.describe WorkPackage::PDFExport::WorkPackageToPdf do ), embed[1]] end, *expected_description_first, - *expected_description_second, + *expected_description_second ].flatten.join(" ") expect(result).to eq(expected_result) end @@ -634,40 +634,6 @@ RSpec.describe WorkPackage::PDFExport::WorkPackageToPdf do let(:enabled_module_names) { %i[backlogs] } let(:sprint) { create(:agile_sprint, name: "Sprint name for export", project:) } - let(:expected_details) do - [ - "#{type.name} ##{work_package.id} - #{work_package.subject}", - " ", exporter.prawn_badge_text_stuffing(work_package.status.name.downcase), # badge & padding - "People", - "Assignee", user.name, - "Accountable", user.name, - "Estimates and progress", - "Work", "10h", - "Remaining work", "9h", - "% Complete", "25%", - "Spent time", "0h", - # Story points added by the backlogs module: - "Story Points", "1", - "Details", - "Priority", "Normal", - # Sprint added by the backlogs module and feature flag: - "Sprint", work_package.sprint, - "Version", work_package.version, - "Category", work_package.category, - "Project phase", - "Date", "05/30/2024 - 03/13/2025", - "Other", - # Position added by the backlogs module: - "Position", "1", - "Work Package Custom Field Long Text", "foo faa", - "Empty Work Package Custom Field Long Text", - "Work Package Custom Field Boolean", "Yes", - "My Link", "https://example.com", - "Costs", - "Spent units", "Labor costs", "Unit costs", "Overall costs", "Budget" - ] - end - before do work_package.sprint = sprint work_package.save! diff --git a/spec/modules/backlogs/app/seeders/basic_data/backlogs/setting_seeder_spec.rb b/spec/modules/backlogs/app/seeders/basic_data/backlogs/setting_seeder_spec.rb deleted file mode 100644 index 1e31b53c223..00000000000 --- a/spec/modules/backlogs/app/seeders/basic_data/backlogs/setting_seeder_spec.rb +++ /dev/null @@ -1,118 +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 BasicData::Backlogs::SettingSeeder do - subject(:setting_seeder) { described_class.new(basic_seed_data) } - - let(:type_feature) { basic_seed_data.find_reference(:default_type_feature) } - let(:type_epic) { basic_seed_data.find_reference(:default_type_epic) } - let(:type_user_story) { basic_seed_data.find_reference(:default_type_user_story) } - let(:type_bug) { basic_seed_data.find_reference(:default_type_bug) } - let(:type_task) { basic_seed_data.find_reference(:default_type_task) } - - context "with standard edition" do - include_context "with basic seed data", edition: "standard" - - it "configures Setting.plugin_openproject_backlogs" do - setting_seeder.seed! - - expect(Setting.plugin_openproject_backlogs).to match( - "points_burn_direction" => "up", - "story_types" => contain_exactly(type_feature.id, type_epic.id, type_user_story.id, type_bug.id), - "task_type" => type_task.id, - "wiki_template" => "" - ) - end - - it "can run multiple times" do - # run once - setting_seeder.seed! - # run a second time without the references in seed_data - described_class.new(Source::SeedData.new({})).seed! - - expect(Setting.plugin_openproject_backlogs).to match( - "points_burn_direction" => "up", - "story_types" => contain_exactly(type_feature.id, type_epic.id, type_user_story.id, type_bug.id), - "task_type" => type_task.id, - "wiki_template" => "" - ) - end - - shared_examples "sets missing setting to its default value" do |key:, expected_value:| - it "sets #{key.inspect} value to #{expected_value.inspect} if not set" do - Setting.plugin_openproject_backlogs = {} - expect { setting_seeder.seed! } - .to change { Setting.plugin_openproject_backlogs[key] } - .from(nil) - .to(expected_value) - end - - it "keeps #{key.inspect} value if already set" do - Setting.plugin_openproject_backlogs = { key => "already set" } - expect { setting_seeder.seed! } - .not_to change { Setting.plugin_openproject_backlogs[key] } - end - end - - include_examples "sets missing setting to its default value", key: "points_burn_direction", expected_value: "up" - include_examples "sets missing setting to its default value", key: "wiki_template", expected_value: "" - end - - context "with BIM edition" do - include_context "with basic seed data", edition: "bim" - - it "configures Setting.plugin_openproject_backlogs" do - setting_seeder.seed! - - expect(Setting.plugin_openproject_backlogs).to match( - "points_burn_direction" => "up", - "story_types" => [], - "task_type" => type_task.id, - "wiki_template" => "" - ) - end - - it "can run multiple times" do - # run once - setting_seeder.seed! - # run a second time without the references in seed_data - described_class.new(Source::SeedData.new({})).seed! - - expect(Setting.plugin_openproject_backlogs).to match( - "points_burn_direction" => "up", - "story_types" => [], - "task_type" => type_task.id, - "wiki_template" => "" - ) - end - end -end