From f2fc5dcb4756d1c3313b08e63e9446811915fa5e Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Wed, 15 Apr 2026 11:59:09 +0100 Subject: [PATCH 01/11] Improve sprint filter support in WP filter helper Add `clear_filter_value` to clear an ng-select filter's current selection without removing the filter row, allowing a new value to be set in place. Refactor `insert_autocomplete_item` to accept a filter `id` string rather than a pre-captured element, so the ng-select node is re-queried on each call. This avoids stale element references after `clear_filter_value` causes the DOM to re-render. --- spec/support/components/work_packages/filters.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/spec/support/components/work_packages/filters.rb b/spec/support/components/work_packages/filters.rb index 52f6ed27f064..9a822c870783 100644 --- a/spec/support/components/work_packages/filters.rb +++ b/spec/support/components/work_packages/filters.rb @@ -223,6 +223,10 @@ def remove_filter(field) find("#filter_#{field} .advanced-filters--remove-filter-icon").click end + def clear_filter_value(field) + ng_select_clear(page.find("#filter_#{field} ng-select")) + end + def open_autocompleter(id) with_filter_input(id, &:click) end @@ -263,16 +267,16 @@ def set_value(id, value, operator) elsif operator == "between" insert_two_single_dates(id, value) elsif filter_element.has_selector?(".ng-select-container", wait: false) - insert_autocomplete_item(filter_element, value) + insert_autocomplete_item(id, value) else insert_plain_value(id, value) end end end - def insert_autocomplete_item(filter_element, value) + def insert_autocomplete_item(id, value) Array(value).each do |val| - select_autocomplete filter_element.find("ng-select"), + select_autocomplete page.find("#filter_#{id} ng-select"), query: val, results_selector: ".ng-dropdown-panel-items" end From 91f10ca8278e139aa810939ef7563c5cf7b7970c Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 13 Apr 2026 13:56:10 +0100 Subject: [PATCH 02/11] [#73798] Remove scrum_projects feature flag Make Backlogs use the sprint-based behavior unconditionally and remove the old feature-flagged branches from controllers, routes, representers, and supporting helpers. Update the affected Backlogs specs and PDF export expectations to match the permanent sprint model and keep list reordering stable when moving work packages between backlog and sprint scopes. https://community.openproject.org/wp/73798 --- app/components/versions/row_component.rb | 2 +- app/forms/versions/form.rb | 53 -- app/models/permitted_params.rb | 6 +- config/initializers/feature_decisions.rb | 5 - .../settings_header_component.html.erb | 2 +- .../app/controllers/inbox_controller.rb | 1 - .../projects/settings/backlogs_controller.rb | 6 +- .../controllers/rb_application_controller.rb | 23 +- .../rb_burndown_charts_controller.rb | 2 +- .../rb_master_backlogs_controller.rb | 52 +- .../app/controllers/rb_stories_controller.rb | 2 +- .../controllers/rb_taskboards_controller.rb | 9 +- .../backlogs/app/forms/my/backlogs_form.rb | 6 - .../backlogs/app/helpers/rb_common_helper.rb | 26 +- .../work_packages/filter/sprint_filter.rb | 6 +- .../rebuild_positions_service.rb | 2 +- .../app/views/backlogs_settings/show.html.erb | 20 +- modules/backlogs/config/routes.rb | 54 +- .../backlogs_type_representer.rb | 57 -- .../backlogs_type_dependency_representer.rb | 65 -- .../lib/api/v3/sprints/sprints_api.rb | 4 - .../api/v3/sprints/sprints_by_project_api.rb | 2 - .../lib/open_project/backlogs/engine.rb | 36 +- .../lib/open_project/backlogs/list.rb | 105 +--- .../patches/api/work_package_representer.rb | 6 +- .../api/work_package_schema_representer.rb | 3 +- .../patches/set_attributes_service_patch.rb | 50 +- .../backlogs/patches/type_patch.rb | 8 +- .../backlogs/patches/update_service_patch.rb | 76 --- .../patches/versions/row_component_patch.rb | 3 +- .../patches/versions_controller_patch.rb | 75 --- .../backlogs/patches/work_package_patch.rb | 56 +- .../backlogs/work_package_filter.rb | 166 ----- .../spec/controllers/inbox_controller_spec.rb | 2 +- .../backlog_sharings_controller_spec.rb | 2 +- .../controllers/rb_sprints_controller_spec.rb | 250 ++++---- .../controllers/rb_stories_controller_spec.rb | 10 +- .../rb_taskboards_controller_spec.rb | 137 ++-- .../controllers/versions_controller_spec.rb | 82 --- .../features/admin/backlogs_settings_spec.rb | 80 +-- .../spec/features/backlogs/create_spec.rb | 10 +- .../spec/features/backlogs/edit_spec.rb | 2 +- .../features/backlogs/sprint_list_spec.rb | 2 +- .../features/backlogs/start_finish_spec.rb | 3 +- .../spec/features/burndown/show_spec.rb | 2 +- .../spec/features/inbox_column_spec.rb | 2 +- .../settings/backlog_sharing_settings_spec.rb | 11 +- .../work_packages/create_work_package_spec.rb | 2 +- .../work_packages/drag_in_inbox_spec.rb | 3 +- .../work_packages/drag_in_sprint_spec.rb | 2 +- .../features/work_packages/filter_spec.rb | 2 +- .../work_packages/sprints_on_wp_view_spec.rb | 10 +- .../work_package_schema_representer_spec.rb | 44 +- ...work_package_representer_rendering_spec.rb | 60 +- .../open_project/backlogs/permissions_spec.rb | 16 +- .../spec/models/issue_position_spec.rb | 424 ------------- .../filter/sprint_filter_spec.rb | 25 +- .../backlogs/spec/models/work_package_spec.rb | 66 -- .../models/work_packages/position_spec.rb | 3 +- .../api/v3/sprints/index_resource_spec.rb | 6 +- .../v3/sprints/project_index_resource_spec.rb | 5 +- .../api/v3/sprints/show_resource_spec.rb | 5 +- .../spec/requests/rb_master_backlogs_spec.rb | 117 ++-- .../spec/routing/inbox_routing_spec.rb | 2 +- .../settings/backlog_sharings_routing_spec.rb | 35 +- .../spec/routing/rb_sprints_routing_spec.rb | 118 ++-- .../spec/routing/rb_stories_routing_spec.rb | 24 +- .../services/sprints/finish_service_spec.rb | 2 +- ...uild_positions_service_integration_spec.rb | 14 +- ...update_service_sprint_preservation_spec.rb | 94 ++- ...update_service_version_inheritance_spec.rb | 583 ------------------ spec/features/versions/edit_spec.rb | 45 -- .../pdf_export/work_package_to_pdf_spec.rb | 5 +- 73 files changed, 474 insertions(+), 2822 deletions(-) delete mode 100644 modules/backlogs/lib/api/v3/backlogs_types/backlogs_type_representer.rb delete mode 100644 modules/backlogs/lib/api/v3/queries/schemas/backlogs_type_dependency_representer.rb delete mode 100644 modules/backlogs/lib/open_project/backlogs/patches/update_service_patch.rb delete mode 100644 modules/backlogs/lib/open_project/backlogs/patches/versions_controller_patch.rb delete mode 100644 modules/backlogs/lib/open_project/backlogs/work_package_filter.rb delete mode 100644 modules/backlogs/spec/controllers/versions_controller_spec.rb delete mode 100644 modules/backlogs/spec/models/issue_position_spec.rb delete mode 100644 modules/backlogs/spec/services/work_packages/update_service_version_inheritance_spec.rb diff --git a/app/components/versions/row_component.rb b/app/components/versions/row_component.rb index 1a6187596f3a..73dd9cb06dbc 100644 --- a/app/components/versions/row_component.rb +++ b/app/components/versions/row_component.rb @@ -85,7 +85,7 @@ def wiki_page end def button_links - [edit_link, delete_link, backlogs_edit_link].compact + [edit_link, delete_link].compact end private diff --git a/app/forms/versions/form.rb b/app/forms/versions/form.rb index 35f8b6b0533d..7412bdf722dc 100644 --- a/app/forms/versions/form.rb +++ b/app/forms/versions/form.rb @@ -110,29 +110,6 @@ class Form < ApplicationForm end end - if backlogs_enabled? - setting = version_setting_for_project - - f.select_list( - name: "version[version_settings_attributes][][display]", - scope_name_to_model: false, - label: I18n.t(:label_column_in_backlog), - input_width: :small - ) do |list| - position_display_options.each do |label, value| - list.option(label:, value:, selected: setting.display == value) - end - end - - if setting.persisted? - f.hidden( - name: "version[version_settings_attributes][][id]", - value: setting.id, - scope_name_to_model: false - ) - end - end - f.hidden( name: "project_id", value: project.id, @@ -177,35 +154,5 @@ def custom_fields def wiki_pages_disabled? contract.assignable_wiki_pages.none? end - - def backlogs_enabled? - resolved_project.backlogs_enabled? && !OpenProject::FeatureDecisions.scrum_projects_active? - end - - def resolved_project - @project || version.project - end - - def version_setting_for_project - setting = version.version_settings.detect { |vs| vs.project_id == resolved_project.id || vs.project_id.nil? } - setting || version.version_settings.new(display: VersionSetting::DISPLAY_LEFT, project: resolved_project) - end - - def position_display_options - [VersionSetting::DISPLAY_NONE, - VersionSetting::DISPLAY_LEFT, - VersionSetting::DISPLAY_RIGHT].map { |s| [humanize_display_option(s), s] } - end - - def humanize_display_option(option) - case option - when VersionSetting::DISPLAY_NONE - I18n.t("version_settings_display_option_none") - when VersionSetting::DISPLAY_LEFT - I18n.t("version_settings_display_option_left") - when VersionSetting::DISPLAY_RIGHT - I18n.t("version_settings_display_option_right") - end - end end end diff --git a/app/models/permitted_params.rb b/app/models/permitted_params.rb index 3470efc1006a..d0a44db3c3a7 100644 --- a/app/models/permitted_params.rb +++ b/app/models/permitted_params.rb @@ -359,9 +359,6 @@ def category end def version - # `version_settings_attributes` is from a plugin. Unfortunately as it stands - # now it is less work to do it this way than have the plugin override this - # method. We hopefully will change this in the future. permitted_params = params.fetch(:version, {}).permit(:name, :description, :effective_date, @@ -369,8 +366,7 @@ def version :start_date, :wiki_page_title, :status, - :sharing, - version_settings_attributes: %i(id display project_id)) + :sharing) permitted_params.merge(custom_field_values(:version, required: false)) end diff --git a/config/initializers/feature_decisions.rb b/config/initializers/feature_decisions.rb index e8101395c4aa..022d5863785d 100644 --- a/config/initializers/feature_decisions.rb +++ b/config/initializers/feature_decisions.rb @@ -61,11 +61,6 @@ description: "Enables Jira Migration Tool.", force_active: false -OpenProject::FeatureDecisions.add :scrum_projects, - description: "Enables an overhauled version of the backlogs module to " \ - "support Scrum projects with a new sprint planning experience. ", - force_active: true - OpenProject::FeatureDecisions.add :user_working_times, description: "Enables tracking of user working hours and non-working days." diff --git a/modules/backlogs/app/components/projects/settings/backlogs/settings_header_component.html.erb b/modules/backlogs/app/components/projects/settings/backlogs/settings_header_component.html.erb index 985b7d9a585b..40344ff43f15 100644 --- a/modules/backlogs/app/components/projects/settings/backlogs/settings_header_component.html.erb +++ b/modules/backlogs/app/components/projects/settings/backlogs/settings_header_component.html.erb @@ -44,7 +44,7 @@ See COPYRIGHT and LICENSE files for more details. t.with_text { t("backlogs.done_status") } end - if OpenProject::FeatureDecisions.scrum_projects_active? && User.current.allowed_in_project?(:share_sprint, project) + if User.current.allowed_in_project?(:share_sprint, project) tab_nav.with_tab( selected: selected_tab?(:sharing), href: project_settings_backlog_sharing_path(project) diff --git a/modules/backlogs/app/controllers/inbox_controller.rb b/modules/backlogs/app/controllers/inbox_controller.rb index 8b44cfe30f40..3334fba19b70 100644 --- a/modules/backlogs/app/controllers/inbox_controller.rb +++ b/modules/backlogs/app/controllers/inbox_controller.rb @@ -31,7 +31,6 @@ class InboxController < RbApplicationController include OpTurbo::ComponentStream - before_action :not_authorized_on_feature_flag_inactive before_action :load_work_package # Deferred ActionMenu items (Primer include-fragment). diff --git a/modules/backlogs/app/controllers/projects/settings/backlogs_controller.rb b/modules/backlogs/app/controllers/projects/settings/backlogs_controller.rb index 695adaf79801..1d182802c09c 100644 --- a/modules/backlogs/app/controllers/projects/settings/backlogs_controller.rb +++ b/modules/backlogs/app/controllers/projects/settings/backlogs_controller.rb @@ -42,11 +42,7 @@ def update end def rebuild_positions - if OpenProject::FeatureDecisions.scrum_projects_active? - WorkPackages::RebuildPositionsService.new(project: @project).call - else - @project.rebuild_positions - end + WorkPackages::RebuildPositionsService.new(project: @project).call flash[:notice] = I18n.t("backlogs.positions_rebuilt_successfully") redirect_to_backlogs_settings diff --git a/modules/backlogs/app/controllers/rb_application_controller.rb b/modules/backlogs/app/controllers/rb_application_controller.rb index 1b37ec0444b2..d76147b2eba2 100644 --- a/modules/backlogs/app/controllers/rb_application_controller.rb +++ b/modules/backlogs/app/controllers/rb_application_controller.rb @@ -33,7 +33,6 @@ class RbApplicationController < ApplicationController helper :rb_common before_action :load_sprint_and_project, - :check_if_plugin_is_configured, :authorize private @@ -54,25 +53,7 @@ def load_sprint @sprint_id = params.delete(:sprint_id) return unless @sprint_id - @sprint = if OpenProject::FeatureDecisions.scrum_projects_active? - Agile::Sprint.for_project(@project).visible.find(@sprint_id) - else - Sprint.visible.apply_to(@project).find(@sprint_id) - end - end - - def check_if_plugin_is_configured - return if OpenProject::FeatureDecisions.scrum_projects_active? - - settings = Setting.plugin_openproject_backlogs - if settings["story_types"].blank? || settings["task_type"].blank? - respond_to do |format| - format.html { render template: "shared/not_configured" } - end - end - end - - def not_authorized_on_feature_flag_inactive - render_403 unless OpenProject::FeatureDecisions.scrum_projects_active? + @sprint = Agile::Sprint.for_project(@project).visible.find_by(id: @sprint_id) || + Sprint.visible.apply_to(@project).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 d5001d452a77..06ae933f3eb0 100644 --- a/modules/backlogs/app/controllers/rb_burndown_charts_controller.rb +++ b/modules/backlogs/app/controllers/rb_burndown_charts_controller.rb @@ -32,7 +32,7 @@ class RbBurndownChartsController < RbApplicationController helper :burndown_charts def show - @burndown = if OpenProject::FeatureDecisions.scrum_projects_active? + @burndown = if @sprint.is_a?(Agile::Sprint) Burndown.new(@sprint, @project) else @sprint.burndown(@project) diff --git a/modules/backlogs/app/controllers/rb_master_backlogs_controller.rb b/modules/backlogs/app/controllers/rb_master_backlogs_controller.rb index 6fd86af78372..0810fb1f96a1 100644 --- a/modules/backlogs/app/controllers/rb_master_backlogs_controller.rb +++ b/modules/backlogs/app/controllers/rb_master_backlogs_controller.rb @@ -31,13 +31,10 @@ class RbMasterBacklogsController < RbApplicationController include WorkPackages::WithSplitView - # Without the feature flag, there is only the top level menu item, select it - menu_item :backlogs_legacy, only: :index - - # With the feature flag, we have a proper menu, select the correct sub entry - menu_item :backlog, only: %i[backlog details] + current_menu_item [:backlog, :details] do + :backlog + end - before_action :not_authorized_on_feature_flag_inactive, only: :backlog before_action :load_backlogs, only: %i[index backlog] def backlog @@ -50,13 +47,10 @@ def backlog end def index - return redirect_to action: :backlog if OpenProject::FeatureDecisions.scrum_projects_active? - - case turbo_frame_request_id - when "backlogs_container" - render partial: "list", layout: false + if turbo_frame_request? + render partial: "backlog_list", layout: false else - render :index + render :backlog end end @@ -65,39 +59,25 @@ def details render "work_packages/split_view", layout: false else load_backlogs - - if OpenProject::FeatureDecisions.scrum_projects_active? - render :backlog - else - render :index - end + render :backlog end end private def split_view_base_route - if OpenProject::FeatureDecisions.scrum_projects_active? - backlog_backlogs_project_backlogs_path(request.query_parameters) - else - backlogs_project_backlogs_path(request.query_parameters) - end + backlog_backlogs_project_backlogs_path(request.query_parameters) end def load_backlogs @owner_backlogs = Backlog.owner_backlogs(@project) - - if OpenProject::FeatureDecisions.scrum_projects_active? - @sprints = Agile::Sprint.for_project(@project).not_completed.order_by_date - @stories_by_sprint_id = WorkPackage - .where(sprint: @sprints, project: @project) - .includes(:type, :status) - .order_by_position - .group_by(&:sprint_id) - @active_sprint_ids = @sprints.select(&:active?).map(&:id) - @inbox_work_packages = Backlog.inbox_for(project: @project) - else - @sprint_backlogs = Backlog.sprint_backlogs(@project) - end + @sprints = Agile::Sprint.for_project(@project).not_completed.order_by_date + @stories_by_sprint_id = WorkPackage + .where(sprint: @sprints, project: @project) + .includes(:type, :status) + .order_by_position + .group_by(&:sprint_id) + @active_sprint_ids = @sprints.select(&:active?).map(&:id) + @inbox_work_packages = Backlog.inbox_for(project: @project) end end diff --git a/modules/backlogs/app/controllers/rb_stories_controller.rb b/modules/backlogs/app/controllers/rb_stories_controller.rb index 4696e7035153..6b6e49d8c396 100644 --- a/modules/backlogs/app/controllers/rb_stories_controller.rb +++ b/modules/backlogs/app/controllers/rb_stories_controller.rb @@ -216,7 +216,7 @@ def replace_sprint_component_via_turbo_stream(sprint:) def load_story @allowed_stories = - if OpenProject::FeatureDecisions.scrum_projects_active? + if @sprint.is_a?(Agile::Sprint) WorkPackage.visible.where(sprint: @sprint, project: @project) else Story.visible.where(Story.condition(@project, @sprint)) diff --git a/modules/backlogs/app/controllers/rb_taskboards_controller.rb b/modules/backlogs/app/controllers/rb_taskboards_controller.rb index ceeeceeb68f8..1a412742028b 100644 --- a/modules/backlogs/app/controllers/rb_taskboards_controller.rb +++ b/modules/backlogs/app/controllers/rb_taskboards_controller.rb @@ -34,7 +34,7 @@ class RbTaskboardsController < RbApplicationController helper :taskboards def show - if OpenProject::FeatureDecisions.scrum_projects_active? + if @sprint.is_a?(Agile::Sprint) @board = @sprint.task_board_for(@project) return redirect_to(project_work_package_board_path(@project, @board)) if @board @@ -56,10 +56,7 @@ def load_sprint_and_project return unless (@sprint_id = params.delete(:sprint_id)) - @sprint = if OpenProject::FeatureDecisions.scrum_projects_active? - Agile::Sprint.for_project(@project).visible.find(@sprint_id) - else - Sprint.visible.apply_to(@project).find(@sprint_id) - end + @sprint = Agile::Sprint.for_project(@project).visible.find_by(id: @sprint_id) || + Sprint.visible.apply_to(@project).find(@sprint_id) end end diff --git a/modules/backlogs/app/forms/my/backlogs_form.rb b/modules/backlogs/app/forms/my/backlogs_form.rb index 9f3b4ad8d943..9e8cacb45334 100644 --- a/modules/backlogs/app/forms/my/backlogs_form.rb +++ b/modules/backlogs/app/forms/my/backlogs_form.rb @@ -31,12 +31,6 @@ class My::BacklogsForm < ApplicationForm form do |f| f.fieldset_group(title: helpers.t("backlogs.user_preference.header_backlogs"), mt: 4) do |fg| - unless OpenProject::FeatureDecisions.scrum_projects_active? - fg.text_field name: :backlogs_task_color, - label: helpers.t("backlogs.task_color"), - input_width: :xsmall - end - fg.check_box name: :backlogs_versions_default_fold_state, value: DEFAULT_FOLD_STATE, unchecked_value: DEFAULT_EXPAND_STATE, diff --git a/modules/backlogs/app/helpers/rb_common_helper.rb b/modules/backlogs/app/helpers/rb_common_helper.rb index babf00ca127f..cf849e44dd32 100644 --- a/modules/backlogs/app/helpers/rb_common_helper.rb +++ b/modules/backlogs/app/helpers/rb_common_helper.rb @@ -132,13 +132,8 @@ def remaining_hours(item) item.remaining_hours.blank? || item.remaining_hours == 0 ? "" : item.remaining_hours end - def scrum_projects_enabled? - OpenProject::FeatureDecisions.scrum_projects_active? - end - def allow_sprint_creation?(project) - scrum_projects_enabled? && - current_user.allowed_in_project?(:create_sprints, project) && + current_user.allowed_in_project?(:create_sprints, project) && !project.receive_shared_sprints? end @@ -162,24 +157,11 @@ def all_work_package_status end def backlogs_types - return [] if scrum_projects_enabled? - - @backlogs_types ||= begin - backlogs_ids = Setting.plugin_openproject_backlogs["story_types"] - backlogs_ids << Setting.plugin_openproject_backlogs["task_type"] - - Type.where(id: backlogs_ids).order(Arel.sql("position ASC")) - end + [] end def story_types - return [] if scrum_projects_enabled? - - @story_types ||= begin - backlogs_type_ids = Setting.plugin_openproject_backlogs["story_types"].map(&:to_i) - - backlogs_types.select { |t| backlogs_type_ids.include?(t.id) } - end + [] end def get_backlogs_preference(assignee, attr) @@ -187,6 +169,6 @@ def get_backlogs_preference(assignee, attr) end def sprint_board_label - OpenProject::FeatureDecisions.scrum_projects_active? ? t("backlogs.label_sprint_board") : t(:label_task_board) + t("backlogs.label_sprint_board") end end diff --git a/modules/backlogs/app/models/queries/work_packages/filter/sprint_filter.rb b/modules/backlogs/app/models/queries/work_packages/filter/sprint_filter.rb index 3b7ff4ae8247..3ba25a9ca787 100644 --- a/modules/backlogs/app/models/queries/work_packages/filter/sprint_filter.rb +++ b/modules/backlogs/app/models/queries/work_packages/filter/sprint_filter.rb @@ -35,7 +35,7 @@ def allowed_values end def available? - scrum_projects_active? && allowed? + allowed? end def type @@ -71,10 +71,6 @@ def allowed? end end - def scrum_projects_active? - OpenProject::FeatureDecisions.scrum_projects_active? - end - def sprints @sprints ||= begin scope = Agile::Sprint.visible diff --git a/modules/backlogs/app/services/work_packages/rebuild_positions_service.rb b/modules/backlogs/app/services/work_packages/rebuild_positions_service.rb index 0cf950f6968f..3e3c2b93d7c9 100644 --- a/modules/backlogs/app/services/work_packages/rebuild_positions_service.rb +++ b/modules/backlogs/app/services/work_packages/rebuild_positions_service.rb @@ -47,7 +47,7 @@ def call id, ROW_NUMBER() OVER ( PARTITION BY project_id, sprint_id - ORDER BY position, created_at + ORDER BY position, created_at, id ) AS new_position FROM work_packages ) AS mapping diff --git a/modules/backlogs/app/views/backlogs_settings/show.html.erb b/modules/backlogs/app/views/backlogs_settings/show.html.erb index eea296fdfb9f..2cd0672eed25 100644 --- a/modules/backlogs/app/views/backlogs_settings/show.html.erb +++ b/modules/backlogs/app/views/backlogs_settings/show.html.erb @@ -40,22 +40,8 @@ See COPYRIGHT and LICENSE files for more details. %> <%= - if OpenProject::FeatureDecisions.scrum_projects_active? - render(Primer::Beta::Blankslate.new(border: true, spacious: true)) do |blankslate| - blankslate.with_heading(tag: :h2).with_content(t("backlogs.administration_blankslate.title")) - blankslate.with_description_content(t("backlogs.administration_blankslate.text")) - end - else - settings_primer_form_with( - url: admin_backlogs_settings_path, - method: :put, - model: @settings, - scope: :settings, - data: { - controller: "admin--backlogs-settings" - } - ) do |f| - render Admin::Settings::BacklogsSettingsForm.new(f) - end + render(Primer::Beta::Blankslate.new(border: true, spacious: true)) do |blankslate| + blankslate.with_heading(tag: :h2).with_content(t("backlogs.administration_blankslate.title")) + blankslate.with_description_content(t("backlogs.administration_blankslate.text")) end %> diff --git a/modules/backlogs/config/routes.rb b/modules/backlogs/config/routes.rb index f11657e72911..aa2344ae5912 100644 --- a/modules/backlogs/config/routes.rb +++ b/modules/backlogs/config/routes.rb @@ -29,48 +29,46 @@ #++ Rails.application.routes.draw do - constraints(Constraints::FeatureDecision.new(:scrum_projects)) do - # Routes for the new Agile::Sprint - # Scoped under projects for permissions: - resources :projects, only: [] do - resources :sprints, controller: :rb_sprints, only: %i[create] do - collection do - get :new_dialog - get :refresh_form - end - - member do - post :start - post :finish - get :edit_dialog - put :update_agile_sprint - end + # Routes for the new Agile::Sprint + # Scoped under projects for permissions: + resources :projects, only: [] do + resources :sprints, controller: :rb_sprints, only: %i[create] do + collection do + get :new_dialog + get :refresh_form + end - resources :stories, controller: :rb_stories, only: [] do - member do - get :menu - put :move - end - end + member do + post :start + post :finish + get :edit_dialog + put :update_agile_sprint end - resources :inbox, only: [] do + resources :stories, controller: :rb_stories, only: [] do member do get :menu put :move - post :reorder - get :move_to_sprint_dialog end end end - scope "projects/:project_id", as: "project", module: "projects" do - namespace "settings" do - resource :backlog_sharing, only: %i[show update] + resources :inbox, only: [] do + member do + get :menu + put :move + post :reorder + get :move_to_sprint_dialog end end end + scope "projects/:project_id", as: "project", module: "projects" do + namespace "settings" do + resource :backlog_sharing, only: %i[show update] + end + end + # Legacy routes scope "", as: "backlogs" do scope "projects/:project_id", as: "project" do diff --git a/modules/backlogs/lib/api/v3/backlogs_types/backlogs_type_representer.rb b/modules/backlogs/lib/api/v3/backlogs_types/backlogs_type_representer.rb deleted file mode 100644 index e63aacc6ea12..000000000000 --- a/modules/backlogs/lib/api/v3/backlogs_types/backlogs_type_representer.rb +++ /dev/null @@ -1,57 +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 API - module V3 - module BacklogsTypes - class BacklogsTypeRepresenter < ::API::Decorators::Single - self_link path: :backlogs_type, - id_attribute: ->(*) { represented.last }, - title_getter: ->(*) { represented.first } - - property :id, - exec_context: :decorator - - property :name, - exec_context: :decorator - - def _type - "BacklogsType" - end - - def id - represented.last - end - - def name - represented.first - end - end - end - end -end diff --git a/modules/backlogs/lib/api/v3/queries/schemas/backlogs_type_dependency_representer.rb b/modules/backlogs/lib/api/v3/queries/schemas/backlogs_type_dependency_representer.rb deleted file mode 100644 index b8bf4d7631df..000000000000 --- a/modules/backlogs/lib/api/v3/queries/schemas/backlogs_type_dependency_representer.rb +++ /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. -#++ - -module API - module V3 - module Queries - module Schemas - class BacklogsTypeDependencyRepresenter < - FilterDependencyRepresenter - schema_with_allowed_collection :values, - type: ->(*) { type }, - writable: true, - has_default: false, - required: true, - values_callback: ->(*) { - represented.allowed_values - }, - value_representer: BacklogsTypes::BacklogsTypeRepresenter, - link_factory: ->(value) { - { - href: api_v3_paths.backlogs_type(value.last), - title: value.first - } - }, - show_if: ->(*) { - value_required? - } - - def href_callback; end - - private - - def type - "[1]BacklogsType" - end - end - end - end - end -end diff --git a/modules/backlogs/lib/api/v3/sprints/sprints_api.rb b/modules/backlogs/lib/api/v3/sprints/sprints_api.rb index 81c2cd047a52..cac5ab771802 100644 --- a/modules/backlogs/lib/api/v3/sprints/sprints_api.rb +++ b/modules/backlogs/lib/api/v3/sprints/sprints_api.rb @@ -33,10 +33,6 @@ module V3 module Sprints class SprintsAPI < ::API::OpenProjectAPI resources :sprints do - before do - guard_feature_flag(:scrum_projects) - end - get &::API::V3::Utilities::Endpoints::Index .new(model: Agile::Sprint) .mount diff --git a/modules/backlogs/lib/api/v3/sprints/sprints_by_project_api.rb b/modules/backlogs/lib/api/v3/sprints/sprints_by_project_api.rb index 1d2d651acefd..f5acfb5bfd5d 100644 --- a/modules/backlogs/lib/api/v3/sprints/sprints_by_project_api.rb +++ b/modules/backlogs/lib/api/v3/sprints/sprints_by_project_api.rb @@ -34,8 +34,6 @@ module Sprints class SprintsByProjectAPI < ::API::OpenProjectAPI resources :sprints do after_validation do - guard_feature_flag(:scrum_projects) - authorize_in_project(:view_sprints, project: @project) end diff --git a/modules/backlogs/lib/open_project/backlogs/engine.rb b/modules/backlogs/lib/open_project/backlogs/engine.rb index 004587ec6c89..c194579d974e 100644 --- a/modules/backlogs/lib/open_project/backlogs/engine.rb +++ b/modules/backlogs/lib/open_project/backlogs/engine.rb @@ -99,8 +99,7 @@ def self.settings { rb_sprints: %i[start finish] }, permissible_on: :project, require: :member, - dependencies: %i[view_sprints manage_board_views manage_sprint_items], - visible: -> { OpenProject::FeatureDecisions.scrum_projects_active? } + dependencies: %i[view_sprints manage_board_views manage_sprint_items] permission :manage_sprint_items, { rb_stories: %i[move move_legacy reorder], @@ -113,15 +112,13 @@ def self.settings { "projects/settings/backlog_sharings": %i[show update] }, permissible_on: :project, require: :member, - dependencies: :create_sprints, - visible: -> { OpenProject::FeatureDecisions.scrum_projects_active? } + dependencies: :create_sprints end - # Menu items that are there when feature flag is active menu :project_menu, :backlogs, { controller: "/rb_master_backlogs", action: :backlog }, - if: Proc.new { |project| project.module_enabled?(:backlogs) && OpenProject::FeatureDecisions.scrum_projects_active? }, + if: Proc.new { |project| project.module_enabled?(:backlogs) }, caption: :project_module_backlogs, after: :work_packages, icon: "op-backlogs" @@ -129,19 +126,10 @@ def self.settings menu :project_menu, :backlog, { controller: "/rb_master_backlogs", action: :backlog }, - if: Proc.new { |project| project.module_enabled?(:backlogs) && OpenProject::FeatureDecisions.scrum_projects_active? }, + if: Proc.new { |project| project.module_enabled?(:backlogs) }, caption: :label_backlog_and_sprints, parent: :backlogs - # Menu items that are there when feature flag is inactive - menu :project_menu, - :backlogs_legacy, - { controller: "/rb_master_backlogs", action: :index }, - if: Proc.new { |project| project.module_enabled?(:backlogs) && !OpenProject::FeatureDecisions.scrum_projects_active? }, - caption: :project_module_backlogs, - after: :work_packages, - icon: "op-backlogs" - # Menu items that are always present menu :project_menu, :settings_backlogs, @@ -158,12 +146,10 @@ def self.settings Type Project User - VersionsController Version] patch_with_namespace :BasicData, :SettingSeeder patch_with_namespace :DemoData, :ProjectSeeder - patch_with_namespace :WorkPackages, :UpdateService patch_with_namespace :WorkPackages, :SetAttributesService patch_with_namespace :WorkPackages, :BaseContract patch_with_namespace :WorkPackages, :UpdateContract @@ -203,11 +189,6 @@ def self.settings extend_api_response(:v3, :work_packages, :schema, :work_package_schema, &::OpenProject::Backlogs::Patches::API::WorkPackageSchemaRepresenter.extension) - add_api_path :backlogs_type do |id| - # There is no api endpoint for this url - "#{root}/backlogs_types/#{id}" - end - add_api_path :sprint do |id| "#{root}/sprints/#{id}" end @@ -250,20 +231,16 @@ def self.settings config.to_prepare do enabled_backlogs_story = ->(type, project: nil) do if project.present? - project.backlogs_enabled? && (OpenProject::FeatureDecisions.scrum_projects_active? || type.story?) + project.backlogs_enabled? else - # Allow globally configuring the attribute if story - OpenProject::FeatureDecisions.scrum_projects_active? || type.story? + true end end story_and_sprint_permission = ->(_type, project: nil) do - return false unless OpenProject::FeatureDecisions.scrum_projects_active? - project.nil? || User.current.allowed_in_project?(:view_sprints, project) end - # TODO: upon removal of the scrum_projects feature flag, remove these constraints ::Type.add_constraint :position, enabled_backlogs_story ::Type.add_constraint :story_points, enabled_backlogs_story ::Type.add_constraint :sprint, story_and_sprint_permission @@ -274,7 +251,6 @@ def self.settings ::Queries::Register.register(::Query) do filter Queries::WorkPackages::Filter::SprintFilter - filter OpenProject::Backlogs::WorkPackageFilter select OpenProject::Backlogs::QueryBacklogsSelect end diff --git a/modules/backlogs/lib/open_project/backlogs/list.rb b/modules/backlogs/lib/open_project/backlogs/list.rb index 3d7029dd6340..b7a22fb3ae97 100644 --- a/modules/backlogs/lib/open_project/backlogs/list.rb +++ b/modules/backlogs/lib/open_project/backlogs/list.rb @@ -32,64 +32,37 @@ module OpenProject::Backlogs::List extend ActiveSupport::Concern included do - # Once the scrum_projects_active feature flag is removed, - # add - # scope [:project_id, :sprint_id] acts_as_list touch_on_update: false # acts as list adds a before destroy hook which messes # with the parent_id_was value skip_callback(:destroy, :before, :reload) - # Reorder list, if work_package is removed from sprint - # To be removed once the scrum_projects_active feature flag is removed - before_update :fix_other_work_package_positions - # To be removed once the scrum_projects_active feature flag is removed - before_update :fix_own_work_package_position - private # Used by acts_list to limit the list to a certain subset within # the table. - # - # Also sanitize_sql seems to be unavailable in a sensible way. Therefore - # we're using send to circumvent visibility work_packages. def scope_condition - if OpenProject::FeatureDecisions.scrum_projects_active? - { project_id:, sprint_id: } - else - self.class.send(:sanitize_sql, ["project_id = ? AND version_id = ? AND type_id IN (?)", - project_id, version_id, types]) - end + { project_id:, sprint_id: } end - # rubocop:disable Style/ArrayIntersect - # rubocop:disable Performance/InefficientHashSearch - # Copied from acts_as_list. - # To be removed once the scrum_projects_active feature flag is removed + # 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? - return false unless OpenProject::FeatureDecisions.scrum_projects_active? - (scope_condition.keys & changed.map(&:to_sym)).any? end - # Copied from acts_as_list - # To be removed once the scrum_projects_active feature flag is removed + # Copied from acts_as_list to support our custom hash-based scope condition. def destroyed_via_scope? - return false unless OpenProject::FeatureDecisions.scrum_projects_active? return false unless destroyed_by_association foreign_key = destroyed_by_association.foreign_key if foreign_key.is_a?(Array) - # Composite foreign key - check if any keys overlap with scope (scope_condition.keys & foreign_key.map(&:to_sym)).any? else - # Single foreign key scope_condition.keys.include?(foreign_key.to_sym) end end - # rubocop:enable Style/ArrayIntersect - # rubocop:enable Performance/InefficientHashSearch include InstanceMethods end @@ -129,78 +102,10 @@ def set_list_position(new_position, _raise_exception_if_save_fails = false) # ru update_columns(position: new_position) end - def fix_other_work_package_positions # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity - return if OpenProject::FeatureDecisions.scrum_projects_active? - - if changes.slice("project_id", "type_id", "version_id").present? - if changes.slice("project_id", "version_id").blank? and - Story.types.include?(type_id.to_i) and - Story.types.include?(type_id_was.to_i) - return - end - - if version_id_changed? - restore_version_id = true - new_version_id = version_id - self.version_id = version_id_was - end - - if type_id_changed? - restore_type_id = true - new_type_id = type_id - self.type_id = type_id_was - end - - if project_id_changed? - restore_project_id = true - # I've got no idea, why there's a difference between setting the - # project via project= or via project_id=, but there is. - new_project = project - self.project = Project.find(project_id_was) - end - - remove_from_list if is_story? - - if restore_project_id - self.project = new_project - end - - if restore_type_id - self.type_id = new_type_id - end - - if restore_version_id - self.version_id = new_version_id - end - end - end - - def fix_own_work_package_position # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity - return if OpenProject::FeatureDecisions.scrum_projects_active? - - if changes.slice("project_id", "type_id", "version_id").present? - if changes.slice("project_id", "version_id").blank? and - Story.types.include?(type_id.to_i) and - Story.types.include?(type_id_was.to_i) - return - end - - if is_story? and version.present? - assume_bottom_position - else - remove_from_list - end - end - end - def set_default_prev_positions_silently(prev) return if prev.nil? - if prev.is_task? - prev.version.rebuild_task_positions(prev) - else - prev.version.rebuild_story_positions(prev.project) - end + WorkPackages::RebuildPositionsService.new(project: prev.project).call prev.reload.position end diff --git a/modules/backlogs/lib/open_project/backlogs/patches/api/work_package_representer.rb b/modules/backlogs/lib/open_project/backlogs/patches/api/work_package_representer.rb index 0a2af73ba775..6b9783ce1f35 100644 --- a/modules/backlogs/lib/open_project/backlogs/patches/api/work_package_representer.rb +++ b/modules/backlogs/lib/open_project/backlogs/patches/api/work_package_representer.rb @@ -49,8 +49,7 @@ def extension # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity resource :sprint, link_cache_if: ->(*) { represented.project.present? && - current_user.allowed_in_project?(:view_sprints, represented.project) && - OpenProject::FeatureDecisions.scrum_projects_active? + current_user.allowed_in_project?(:view_sprints, represented.project) }, link: ->(*) { if represented.sprint.present? @@ -68,8 +67,7 @@ def extension # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity if embed_links && represented.project.present? && represented.sprint.present? && - current_user.allowed_in_project?(:view_sprints, represented.project) && - OpenProject::FeatureDecisions.scrum_projects_active? + current_user.allowed_in_project?(:view_sprints, represented.project) ::API::V3::Sprints::SprintRepresenter.create(represented.sprint, current_user:) end end, diff --git a/modules/backlogs/lib/open_project/backlogs/patches/api/work_package_schema_representer.rb b/modules/backlogs/lib/open_project/backlogs/patches/api/work_package_schema_representer.rb index 66f70df1d639..92f5a58f949f 100644 --- a/modules/backlogs/lib/open_project/backlogs/patches/api/work_package_schema_representer.rb +++ b/modules/backlogs/lib/open_project/backlogs/patches/api/work_package_schema_representer.rb @@ -54,8 +54,7 @@ def extension required: false, show_if: ->(*) { current_user.allowed_in_project?(:view_sprints, represented.project) && - backlogs_constraint_passed?(:sprint) && - OpenProject::FeatureDecisions.scrum_projects_active? + backlogs_constraint_passed?(:sprint) }, href_callback: ->(*) { api_v3_paths.project_sprints(represented.project_id) diff --git a/modules/backlogs/lib/open_project/backlogs/patches/set_attributes_service_patch.rb b/modules/backlogs/lib/open_project/backlogs/patches/set_attributes_service_patch.rb index f173c4a78a31..caab9d2df53d 100644 --- a/modules/backlogs/lib/open_project/backlogs/patches/set_attributes_service_patch.rb +++ b/modules/backlogs/lib/open_project/backlogs/patches/set_attributes_service_patch.rb @@ -37,64 +37,20 @@ module InstanceMethods def set_attributes(attributes) super - if OpenProject::FeatureDecisions.scrum_projects_active? && moved_to_project_that_has_no_access_to_sprint? + if moved_to_project_that_has_no_access_to_sprint? model.change_by_system do model.sprint = nil end - elsif should_inherit_version_from_parent? - closest = closest_story_or_impediment(work_package.parent_id) - work_package.version_id = closest.version_id if closest end end def moved_to_project_that_has_no_access_to_sprint? - work_package.project_id && + !work_package.new_record? && + work_package.project_id && work_package.project_id_changed? && work_package.sprint_id && !work_package.sprint.visible_to?(work_package.project) end - def should_inherit_version_from_parent? - work_package.parent_id_changed? && - work_package.parent_id && - !work_package.version_id_changed? && - work_package.in_backlogs_type? - end - - def closest_story_or_impediment(parent_id) - return work_package if work_package.is_story? || work_package.is_impediment? - - closest = nil - ancestor_chain(parent_id).each do |i| - # break if we found an element in our chain that is not relevant in backlogs - break unless i.in_backlogs_type? - - if i.is_story? || i.is_impediment? - closest = i - break - end - end - closest - end - - # ancestors array similar to Module#ancestors - # i.e. returns immediate ancestors first - def ancestor_chain(parent_id) - ancestors = [] - unless parent_id.nil? - real_parent = WorkPackage.visible(user).find_by(id: parent_id) - - # Sort immediate ancestors first - ancestors = real_parent - .ancestors - .visible(user) - .includes(project: :enabled_modules) - .order_by_ancestors("desc") - .select("work_packages.*, COALESCE(max_depth.depth, 0)") - - ancestors = [real_parent] + ancestors - end - ancestors - end end 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 index faf5f964ff56..e3dbfaa34d1a 100644 --- a/modules/backlogs/lib/open_project/backlogs/patches/type_patch.rb +++ b/modules/backlogs/lib/open_project/backlogs/patches/type_patch.rb @@ -39,15 +39,11 @@ module ClassMethods module InstanceMethods def story? - return false if OpenProject::FeatureDecisions.scrum_projects_active? - - Story.types.include?(id) + false end def task? - return false if OpenProject::FeatureDecisions.scrum_projects_active? - - Task.type.present? && id == Task.type + false end end end diff --git a/modules/backlogs/lib/open_project/backlogs/patches/update_service_patch.rb b/modules/backlogs/lib/open_project/backlogs/patches/update_service_patch.rb deleted file mode 100644 index 20fd7d173f5a..000000000000 --- a/modules/backlogs/lib/open_project/backlogs/patches/update_service_patch.rb +++ /dev/null @@ -1,76 +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::UpdateServicePatch - def self.included(base) - base.prepend InstanceMethods - end - - module InstanceMethods - def update_descendants(work_package) - super_result = super - - if work_package.in_backlogs_type? && work_package.saved_change_to_version_id? - super_result += inherit_version_to_descendants(work_package) - end - - super_result - end - - def inherit_version_to_descendants(work_package) - all_descendants = sorted_descendants(work_package) - descendant_tasks = descendant_tasks_of(all_descendants) - - attributes = { version_id: work_package.version_id } - - descendant_tasks.map do |task| - # Ensure the parent is already moved to new version so that validation errors are avoided. - task.parent = ([work_package] + all_descendants).detect { |d| d.id == task.parent_id } - set_descendant_attributes(attributes, task) - end - end - - def sorted_descendants(work_package) - work_package - .descendants - .includes(project: :enabled_modules) - .order_by_ancestors("asc") - .select("work_packages.*") - end - - def descendant_tasks_of(descendants) - stop_descendants_ids = [] - - descendants.reject do |t| - if stop_descendants_ids.include?(t.parent_id) || !t.is_task? - stop_descendants_ids << t.id - end - end - end - end -end 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 index 90f0a2fd93a8..34e1e92b4a6e 100644 --- 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 @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -34,7 +36,6 @@ def button_links private def backlogs_edit_link - return if OpenProject::FeatureDecisions.scrum_projects_active? return if version.project == table.project || !table.project.module_enabled?("backlogs") helpers.link_to_if_authorized "", diff --git a/modules/backlogs/lib/open_project/backlogs/patches/versions_controller_patch.rb b/modules/backlogs/lib/open_project/backlogs/patches/versions_controller_patch.rb deleted file mode 100644 index 53e29ab9403b..000000000000 --- a/modules/backlogs/lib/open_project/backlogs/patches/versions_controller_patch.rb +++ /dev/null @@ -1,75 +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::VersionsControllerPatch - def self.included(base) # rubocop:disable Metrics/AbcSize - base.class_eval do - before_action :override_project_from_id, only: %i[edit update] - - append_before_action :add_project_to_version_settings_attributes, only: %i[update create] - append_before_action :whitelist_update_params, only: :update - - private - - def override_project_from_id - # @project is already set by the VersionsController's find_version before action to the version's project - # here we want to add that we always set it to the project from params if present - if params[:project_id].present? - @project = Project.visible.find(params[:project_id]) - end - end - - def whitelist_update_params - if @project != @version.project - # Make sure only the version_settings_attributes - # (column=left|right|none) can be stored when current project does not - # equal the version project (which is valid in inherited versions) - if permitted_params.version.present? && permitted_params.version[:version_settings_attributes].present? - params["version"] = { version_settings_attributes: permitted_params.version[:version_settings_attributes] } - else - # This is an unfortunate hack giving how plugins work at the moment. - # In this else branch we want the `version` to be an empty hash. - permitted_params.define_singleton_method :version, lambda { {} } - end - end - end - - # This forces the current project for the nested version settings in order - # to prevent it from being set through firebug etc. #mass_assignment - def add_project_to_version_settings_attributes - if permitted_params.version["version_settings_attributes"].present? - params["version"]["version_settings_attributes"].each do |attr_hash| - attr_hash["project_id"] = @project.id - end - end - end - end - end -end - -VersionsController.include OpenProject::Backlogs::Patches::VersionsControllerPatch 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 aa9100152633..cdbd75d93f29 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 @@ -53,16 +53,6 @@ def order_by_position order(arel_table[:position].asc.nulls_last) end - def backlogs_types - return [] if OpenProject::FeatureDecisions.scrum_projects_active? - - # Unfortunately, this is not cachable so the following line would be wrong - # @backlogs_types ||= Story.types << Task.type - # Caching like in the line above would prevent the types selected - # for backlogs to be changed without restarting all app server. - (Story.types << Task.type).compact - end - def children_of(ids) where(parent_id: ids) end @@ -73,46 +63,10 @@ def done? project.done_statuses.to_a.include?(status) end - def to_story - Story.find(id) if is_story? - end - - def is_story? - return false if OpenProject::FeatureDecisions.scrum_projects_active? - - backlogs_enabled? && Story.types.include?(type_id) - end - - def to_task - Task.find(id) if is_task? - end - - def is_task? - return false if OpenProject::FeatureDecisions.scrum_projects_active? - - backlogs_enabled? && parent_id && type_id == Task.type && Task.type.present? - end - - def is_impediment? - return false if OpenProject::FeatureDecisions.scrum_projects_active? - - backlogs_enabled? && parent_id.nil? && type_id == Task.type && Task.type.present? - end - - def types - if is_story? - Story.types - elsif is_task? - Task.types - else - [] - end - end - def story - if is_story? + if Story.types.include?(type_id) Story.find(id) - elsif is_task? + elsif Task.type.present? && type_id == Task.type ancestors.where(type_id: Story.types).first end end @@ -127,12 +81,6 @@ def blocks def backlogs_enabled? project&.backlogs_enabled? end - - def in_backlogs_type? - return false if OpenProject::FeatureDecisions.scrum_projects_active? - - backlogs_enabled? && WorkPackage.backlogs_types.include?(type.try(:id)) - end end end diff --git a/modules/backlogs/lib/open_project/backlogs/work_package_filter.rb b/modules/backlogs/lib/open_project/backlogs/work_package_filter.rb deleted file mode 100644 index c08d55e753d5..000000000000 --- a/modules/backlogs/lib/open_project/backlogs/work_package_filter.rb +++ /dev/null @@ -1,166 +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 "story" -require "task" - -module OpenProject::Backlogs - class WorkPackageFilter < ::Queries::WorkPackages::Filter::WorkPackageFilter - def allowed_values - [[I18n.t("backlogs.story"), "story"], - [I18n.t("backlogs.task"), "task"], - [I18n.t("backlogs.impediment"), "impediment"], - [I18n.t("backlogs.any"), "any"]] - end - - def available? - backlogs_enabled? && - backlogs_configured? - end - - def self.key - :backlogs_work_package_type - end - - def where - sql_for_field(values) - end - - def type - :list - end - - def human_name - WorkPackage.human_attribute_name(:backlogs_work_package_type) - end - - def dependency_class - "::API::V3::Queries::Schemas::BacklogsTypeDependencyRepresenter" - end - - def ar_object_filter? - true - end - - def value_objects - available_backlog_types = allowed_values.index_by(&:last) - - values - .filter_map { |backlog_type_id| available_backlog_types[backlog_type_id] } - .map { |value| BacklogsType.new(*value) } - end - - private - - def backlogs_configured? - return false if OpenProject::FeatureDecisions.scrum_projects_active? - - Story.types.present? && Task.type.present? - end - - def backlogs_enabled? - project.nil? || project.module_enabled?(:backlogs) - end - - def sql_for_field(values) - selected_values = if values.include?("any") - ["story", "task"] - else - values - end - - sql_parts = selected_values.map do |val| - case val - when "story" - sql_for_story - when "task" - sql_for_task - when "impediment" - sql_for_impediment - end - end - - case operator - when "=" - sql_parts.join(" OR ") - when "!" - "NOT (" + sql_parts.join(" OR ") + ")" - end - end - - def db_table - WorkPackage.table_name - end - - def sql_for_story - story_types = Story.types.map(&:to_s).join(",") - - "(#{db_table}.type_id IN (#{story_types}))" - end - - def sql_for_task - <<-SQL.squish - (#{db_table}.type_id = #{Task.type} AND - #{db_table}.parent_id IS NOT NULL) - SQL - end - - def sql_for_impediment - <<-SQL.squish - (#{db_table}.type_id = #{Task.type} AND - #{db_table}.id IN (#{blocks_backlogs_type_sql}) - AND #{db_table}.parent_id IS NULL) - SQL - end - - def blocks_backlogs_type_sql - all_types = (Story.types + [Task.type]).map(&:to_s) - - Relation - .blocks - .joins(:to) - .where(work_packages: { type_id: all_types }) - .select(:to_id) - .to_sql - end - end - - # Need to be conformant to the interface required - # by api/v3/queries/filters/query_filter_instance_representer.rb - class BacklogsType - attr_accessor :id, - :name - - def initialize(name, id) - self.id = id - self.name = name - end - end -end diff --git a/modules/backlogs/spec/controllers/inbox_controller_spec.rb b/modules/backlogs/spec/controllers/inbox_controller_spec.rb index 29c7f8cec2ed..26b4ef0ba91d 100644 --- a/modules/backlogs/spec/controllers/inbox_controller_spec.rb +++ b/modules/backlogs/spec/controllers/inbox_controller_spec.rb @@ -30,7 +30,7 @@ require "rails_helper" -RSpec.describe InboxController, with_flag: { scrum_projects_active: true } do +RSpec.describe InboxController do current_user { user } let(:user) { create(:admin) } diff --git a/modules/backlogs/spec/controllers/projects/settings/backlog_sharings_controller_spec.rb b/modules/backlogs/spec/controllers/projects/settings/backlog_sharings_controller_spec.rb index fe43d163b2a7..5f1f9eaddae1 100644 --- a/modules/backlogs/spec/controllers/projects/settings/backlog_sharings_controller_spec.rb +++ b/modules/backlogs/spec/controllers/projects/settings/backlog_sharings_controller_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -RSpec.describe Projects::Settings::BacklogSharingsController, with_flag: { scrum_projects: true } do +RSpec.describe Projects::Settings::BacklogSharingsController do shared_let(:user) { create(:admin) } current_user { user } diff --git a/modules/backlogs/spec/controllers/rb_sprints_controller_spec.rb b/modules/backlogs/spec/controllers/rb_sprints_controller_spec.rb index 452c70ad378c..6eeee3d8f9d4 100644 --- a/modules/backlogs/spec/controllers/rb_sprints_controller_spec.rb +++ b/modules/backlogs/spec/controllers/rb_sprints_controller_spec.rb @@ -174,25 +174,23 @@ end describe "GET #new_dialog" do - context "with the feature flag active", with_flag: { scrum_projects: true } do - it "responds with success", :aggregate_failures do - get :new_dialog, params: { project_id: project.id }, format: :turbo_stream + it "responds with success", :aggregate_failures do + get :new_dialog, params: { project_id: project.id }, format: :turbo_stream - expect(response).to be_successful - expect(response).to have_http_status :ok - expect(response).to have_turbo_stream action: "dialog", target: "backlogs-new-sprint-dialog-component" - expect(assigns(:project)).to eq(project) - end + expect(response).to be_successful + expect(response).to have_http_status :ok + expect(response).to have_turbo_stream action: "dialog", target: "backlogs-new-sprint-dialog-component" + expect(assigns(:project)).to eq(project) + end - context "without the 'create_sprints' permission" do - let(:permissions) { all_permissions - [:create_sprints] } + context "without the 'create_sprints' permission" do + let(:permissions) { all_permissions - [:create_sprints] } - it "responds with forbidden", :aggregate_failures do - get :new_dialog, params: { project_id: project.id }, format: :turbo_stream + it "responds with forbidden", :aggregate_failures do + get :new_dialog, params: { project_id: project.id }, format: :turbo_stream - expect(response).not_to be_successful - expect(response).to have_http_status :forbidden - end + expect(response).not_to be_successful + expect(response).to have_http_status :forbidden end end end @@ -200,60 +198,56 @@ describe "GET #edit_dialog" do let!(:sprint) { create(:agile_sprint, project:) } - context "with the feature flag active", with_flag: { scrum_projects: true } do - it "responds with success", :aggregate_failures do - get :edit_dialog, params: { project_id: project.id, id: sprint.id }, format: :turbo_stream + it "responds with success", :aggregate_failures do + get :edit_dialog, params: { project_id: project.id, 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: "dialog", target: "backlogs-new-sprint-dialog-component" - expect(assigns(:project)).to eq(project) - expect(assigns(:sprint)).to eq(sprint) - end + expect(response).to be_successful + expect(response).to have_http_status :ok + expect(response).to have_turbo_stream action: "dialog", target: "backlogs-new-sprint-dialog-component" + expect(assigns(:project)).to eq(project) + expect(assigns(:sprint)).to eq(sprint) + end - context "without the 'create_sprints' permission" do - let(:permissions) { all_permissions - [:create_sprints] } + context "without the 'create_sprints' permission" do + let(:permissions) { all_permissions - [:create_sprints] } - it "responds with forbidden", :aggregate_failures do - get :edit_dialog, params: { project_id: project.id, id: sprint.id }, format: :turbo_stream + it "responds with forbidden", :aggregate_failures do + get :edit_dialog, params: { project_id: project.id, id: sprint.id }, format: :turbo_stream - expect(response).not_to be_successful - expect(response).to have_http_status :forbidden - end + expect(response).not_to be_successful + expect(response).to have_http_status :forbidden end end end describe "POST #create" do - context "with the feature flag active", with_flag: { scrum_projects: true } do - let(:params) do - { - project_id: project.id, - sprint: { name: "My Sprint", start_date: "2025-10-05", finish_date: "2025-10-15" } - } - end + let(:params) do + { + project_id: project.id, + sprint: { name: "My Sprint", start_date: "2025-10-05", finish_date: "2025-10-15" } + } + end - it "responds with success, creates a sprint, and redirects to backlogs", :aggregate_failures do - post :create, format: :turbo_stream, params: params + it "responds with success, creates a sprint, and redirects to backlogs", :aggregate_failures do + post :create, format: :turbo_stream, params: params - expect(response).to be_successful - expect(response).to have_http_status :ok - expect(response.body).to include("turbo-stream") - expect(response.body).to include("action=\"redirect_to\"") - expect(response.body).to include(backlogs_project_backlogs_path(project)) - expect(project.reload.sprints.last.name).to eq("My Sprint") - expect(flash[:notice]).to eq(I18n.t(:notice_successful_create)) - end + expect(response).to be_successful + expect(response).to have_http_status :ok + expect(response.body).to include("turbo-stream") + expect(response.body).to include("action=\"redirect_to\"") + expect(response.body).to include(backlogs_project_backlogs_path(project)) + expect(project.reload.sprints.last.name).to eq("My Sprint") + expect(flash[:notice]).to eq(I18n.t(:notice_successful_create)) + end - context "without the 'create_sprints' permission" do - let(:permissions) { all_permissions - [:create_sprints] } + context "without the 'create_sprints' permission" do + let(:permissions) { all_permissions - [:create_sprints] } - it "responds with forbidden", :aggregate_failures do - post :create, format: :turbo_stream, params: params + it "responds with forbidden", :aggregate_failures do + post :create, format: :turbo_stream, params: params - expect(response).not_to be_successful - expect(response).to have_http_status :forbidden - end + expect(response).not_to be_successful + expect(response).to have_http_status :forbidden end end end @@ -261,36 +255,34 @@ describe "PUT #update_agile_sprint" do let!(:sprint) { create(:agile_sprint, name: "Original sprint name", project:) } - context "with the feature flag active", with_flag: { scrum_projects: true } do - let(:params) do - { - id: sprint.id, - project_id: project.id, - sprint: { name: "Changed sprint name" } - } - end + let(:params) do + { + id: sprint.id, + project_id: project.id, + sprint: { name: "Changed sprint name" } + } + end - it "responds with success", :aggregate_failures do - put :update_agile_sprint, format: :turbo_stream, params: params + it "responds with success", :aggregate_failures do + put :update_agile_sprint, format: :turbo_stream, params: params - expect(response).to be_successful - expect(response).to have_http_status :ok - expect(response.body).to have_turbo_stream action: "flash" - expect(response.body).to have_turbo_stream action: "update", target: "backlogs-sprint-header-component-#{sprint.id}" - assert_select %(turbo-stream[action="update"][target="backlogs-sprint-header-component-#{sprint.id}"][method="morph"]) - expect(response.body).to include("Successful update.") - expect(sprint.reload.name).to eq("Changed sprint name") - end + expect(response).to be_successful + expect(response).to have_http_status :ok + expect(response.body).to have_turbo_stream action: "flash" + expect(response.body).to have_turbo_stream action: "update", target: "backlogs-sprint-header-component-#{sprint.id}" + assert_select %(turbo-stream[action="update"][target="backlogs-sprint-header-component-#{sprint.id}"][method="morph"]) + expect(response.body).to include("Successful update.") + expect(sprint.reload.name).to eq("Changed sprint name") + end - context "without the 'create_sprints' permission" do - let(:permissions) { all_permissions - [:create_sprints] } + context "without the 'create_sprints' permission" do + let(:permissions) { all_permissions - [:create_sprints] } - it "responds with forbidden", :aggregate_failures do - put :update_agile_sprint, format: :turbo_stream, params: params + it "responds with forbidden", :aggregate_failures do + put :update_agile_sprint, format: :turbo_stream, params: params - expect(response).not_to be_successful - expect(response).to have_http_status :forbidden - end + expect(response).not_to be_successful + expect(response).to have_http_status :forbidden end end end @@ -308,8 +300,7 @@ .and_return(service) end - context "with the feature flag active", with_flag: { scrum_projects: true } do - context "when the sprint is rendered in a receiving project" do + 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) } @@ -356,7 +347,7 @@ end end - context "when a board already exists" do + context "when a board already exists" do let!(:existing_board) do create(:board_grid_with_query, project:, @@ -372,7 +363,7 @@ end end - context "when board creation succeeds" do + 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" } @@ -393,7 +384,7 @@ end end - context "when board creation fails" do + 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 @@ -407,7 +398,7 @@ end end - context "when sprint start fails without an explicit message" do + 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 @@ -419,7 +410,7 @@ end end - context "when another sprint is already active" do + context "when another sprint is already active" do let!(:active_sprint) { create(:agile_sprint, project:, status: "active") } let(:service_result) do ServiceResult.failure( @@ -437,7 +428,7 @@ end end - context "without the 'start_complete_sprint' permission" do + context "without the 'start_complete_sprint' permission" do let(:permissions) { all_permissions - [:start_complete_sprint] } it "responds with forbidden", :aggregate_failures do @@ -448,7 +439,7 @@ end end - context "when the sprint is already active" do + context "when the sprint is already active" do let!(:sprint) { create(:agile_sprint, project:, status: "active") } let(:service_result) { ServiceResult.failure } @@ -460,7 +451,6 @@ expect(service).to have_received(:call) end end - end end describe "POST #finish" do @@ -480,8 +470,7 @@ .and_return(service) end - context "with the feature flag active", with_flag: { scrum_projects: true } do - context "when the sprint is rendered in a receiving project" do + 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") } @@ -517,7 +506,7 @@ end end - it "finishes the sprint and redirects to the backlog via turbo stream", :aggregate_failures do + 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 @@ -527,7 +516,7 @@ expect(service).to have_received(:call) end - context "when finishing fails" do + context "when finishing fails" do let(:service_result) { ServiceResult.failure(message: "something went wrong") } it "redirects back to the backlog", :aggregate_failures do @@ -541,7 +530,7 @@ end end - context "when finishing fails without an explicit message" do + 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 @@ -553,7 +542,7 @@ end end - context "without the 'start_complete_sprint' permission" do + context "without the 'start_complete_sprint' permission" do let(:permissions) { all_permissions - [:start_complete_sprint] } it "responds with forbidden", :aggregate_failures do @@ -564,7 +553,7 @@ end end - context "when the sprint is already completed" do + context "when the sprint is already completed" do let!(:sprint) { create(:agile_sprint, project:, status: "completed") } let(:service_result) { ServiceResult.failure } @@ -577,7 +566,7 @@ end end - context "when moving to the top of the backlog" do + 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" } } it "passes unfinished_action to the service and redirects via turbo stream", :aggregate_failures do @@ -590,7 +579,7 @@ end end - context "when moving to the bottom of the backlog" do + 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" } } it "passes unfinished_action to the service and redirects via turbo stream", :aggregate_failures do @@ -602,15 +591,42 @@ .with(hash_including(unfinished_action: "move_to_bottom_of_backlog")) end end - end end describe "GET #refresh_form" do - context "with the feature flag active", with_flag: { scrum_projects: true } do + let(:params) do + { + project_id: project.id, + sprint: { name: "My Sprint", start_date: "2025-10-05", finish_date: "2025-10-15" } + } + end + + it "responds with success", :aggregate_failures do + get :refresh_form, format: :turbo_stream, params: params + + expect(response).to be_successful + expect(response).to have_http_status :ok + expect(response).to have_turbo_stream action: "update", target: "backlogs-new-sprint-form-component" + expect(assigns(:sprint)).to be_nil + end + + context "without the 'create_sprints' permission" do + let(:permissions) { all_permissions - [:create_sprints] } + + it "responds with forbidden", :aggregate_failures do + get :refresh_form, format: :turbo_stream, params: params + + expect(response).not_to be_successful + expect(response).to have_http_status :forbidden + end + end + + context "when refreshing the form in edit mode by passing a sprint id" do + let!(:sprint) { create(:agile_sprint, project:) } let(:params) do { project_id: project.id, - sprint: { name: "My Sprint", start_date: "2025-10-05", finish_date: "2025-10-15" } + sprint: { id: sprint.id, name: "My Sprint", start_date: "2025-10-05", finish_date: "2025-10-15" } } end @@ -620,36 +636,6 @@ expect(response).to be_successful expect(response).to have_http_status :ok expect(response).to have_turbo_stream action: "update", target: "backlogs-new-sprint-form-component" - expect(assigns(:sprint)).to be_nil - end - - context "without the 'create_sprints' permission" do - let(:permissions) { all_permissions - [:create_sprints] } - - it "responds with forbidden", :aggregate_failures do - get :refresh_form, format: :turbo_stream, params: params - - expect(response).not_to be_successful - expect(response).to have_http_status :forbidden - end - end - - context "when refreshing the form in edit mode by passing a sprint id" do - let!(:sprint) { create(:agile_sprint, project:) } - let(:params) do - { - project_id: project.id, - sprint: { id: sprint.id, name: "My Sprint", start_date: "2025-10-05", finish_date: "2025-10-15" } - } - end - - it "responds with success", :aggregate_failures do - get :refresh_form, format: :turbo_stream, params: params - - expect(response).to be_successful - expect(response).to have_http_status :ok - expect(response).to have_turbo_stream action: "update", target: "backlogs-new-sprint-form-component" - end end end end diff --git a/modules/backlogs/spec/controllers/rb_stories_controller_spec.rb b/modules/backlogs/spec/controllers/rb_stories_controller_spec.rb index 215608223ed1..6b872b941e28 100644 --- a/modules/backlogs/spec/controllers/rb_stories_controller_spec.rb +++ b/modules/backlogs/spec/controllers/rb_stories_controller_spec.rb @@ -58,7 +58,7 @@ format: :html end - context "when scrum_projects flag is inactive", with_flag: { scrum_projects: false } do + context "when loading from a version sprint" do let(:load_story_id) { story.id } let(:requested_sprint) { version_sprint } @@ -78,7 +78,7 @@ end end - context "when scrum_projects flag is active", with_flag: { scrum_projects: true } do + 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 } @@ -243,11 +243,11 @@ end end - describe "PUT #move", with_flag: { scrum_projects: true } do + 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", with_flag: { scrum_projects: true } do + context "with another Agile::Sprint as target" do let(:other_agile_sprint) { create(:agile_sprint, name: "Agile Sprint 2", project:) } it "responds with success and moves story to another Agile::Sprint", :aggregate_failures do @@ -304,7 +304,7 @@ end end - context "with a Sprint (Version) as target", with_flag: { scrum_projects: true } do + 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, diff --git a/modules/backlogs/spec/controllers/rb_taskboards_controller_spec.rb b/modules/backlogs/spec/controllers/rb_taskboards_controller_spec.rb index 33d226de6de4..55bc8425ba10 100644 --- a/modules/backlogs/spec/controllers/rb_taskboards_controller_spec.rb +++ b/modules/backlogs/spec/controllers/rb_taskboards_controller_spec.rb @@ -48,132 +48,89 @@ end describe "GET show" do - context "with the feature flag active", with_flag: { scrum_projects: true } do - let(:sprint) { create(:agile_sprint, project:) } + let(:sprint) { create(:agile_sprint, project:) } - context "when the board exists" do - let!(:other_project) { create(:project) } - let!(:other_board) { create(:board_grid_with_query, project: other_project, linked: sprint) } + context "when the board exists" do + let!(:other_project) { create(:project) } + let!(:other_board) { create(:board_grid_with_query, project: other_project, linked: sprint) } - before do - board.update!(linked: sprint) - end - - context "as a member with view_sprints permission" do - let(:permissions) { %i[view_sprints view_work_packages] } - - before do - get :show, params: { project_id: project.identifier, sprint_id: sprint.id } - end - - it "redirects to the board" do - expect(response).to redirect_to(project_work_package_board_path(project, board)) - end - - it "uses the board for the current project" do - expect(response).to redirect_to(project_work_package_board_path(project, board)) - expect(response).not_to redirect_to(project_work_package_board_path(other_project, other_board)) - end - end + before do + board.update!(linked: sprint) end - context "when the board does not exist" do + context "as a member with view_sprints permission" do let(:permissions) { %i[view_sprints view_work_packages] } before do get :show, params: { project_id: project.identifier, sprint_id: sprint.id } end - it "returns not found" do - expect(response).to have_http_status(:not_found) + it "redirects to the board" do + expect(response).to redirect_to(project_work_package_board_path(project, board)) end - end - context "when the sprint is rendered in a receiving project" do - let(:source_project) { create(:project, sprint_sharing: "share_all_projects") } - let(:project) do - create(:project, - sprint_sharing: "receive_shared", - member_with_permissions: { user => permissions }) - end - let(:permissions) { %i[view_sprints view_work_packages] } - let(:sprint) { create(:agile_sprint, project: source_project) } - - before do - create(:board_grid_with_query, project: source_project, linked: sprint) - get :show, params: { project_id: project.identifier, sprint_id: sprint.id } - end - - it "returns not found when the receiving project has no task board" do - expect(response).to have_http_status(:not_found) + it "uses the board for the current project" do + expect(response).to redirect_to(project_work_package_board_path(project, board)) + expect(response).not_to redirect_to(project_work_package_board_path(other_project, other_board)) end end + end - context "as a member without view_sprints permission" do - let(:permissions) { [:view_project] } - - before do - board.update!(linked: sprint) - get :show, params: { project_id: project.identifier, sprint_id: sprint.id } - end + context "when the board does not exist" do + let(:permissions) { %i[view_sprints view_work_packages] } - it "denies access" do - expect(response).to have_http_status(:not_found) - end + before do + get :show, params: { project_id: project.identifier, sprint_id: sprint.id } end - context "as a non-member" do - current_user { create(:user) } - - before do - board.update!(linked: sprint) - get :show, params: { project_id: project.identifier, sprint_id: sprint.id } - end - - it "denies access" do - expect(response).to have_http_status(:not_found) - end + it "returns not found" do + expect(response).to have_http_status(:not_found) end end - context "with the feature flag inactive", with_flag: { scrum_projects: false } do - let(:sprint) { create(:sprint, project:) } + context "when the sprint is rendered in a receiving project" do + let(:source_project) { create(:project, sprint_sharing: "share_all_projects") } + let(:project) do + create(:project, + sprint_sharing: "receive_shared", + member_with_permissions: { user => permissions }) + end let(:permissions) { %i[view_sprints view_work_packages] } + let(:sprint) { create(:agile_sprint, project: source_project) } before do + create(:board_grid_with_query, project: source_project, linked: sprint) get :show, params: { project_id: project.identifier, sprint_id: sprint.id } end - it "renders the legacy show template" do - expect(response).to be_successful - expect(response).to render_template :show + it "returns not found when the receiving project has no task board" do + expect(response).to have_http_status(:not_found) end + end - context "as a member with view_sprints permission" do - let(:permissions) { %i[view_sprints view_work_packages] } + context "as a member without view_sprints permission" do + let(:permissions) { [:view_project] } - it "grants access" do - expect(response).to be_successful - expect(response).to render_template :show - end + before do + board.update!(linked: sprint) + get :show, params: { project_id: project.identifier, sprint_id: sprint.id } end - context "as a member without view_sprints permission" do - let(:permissions) { [:view_project] } - - it "denies access" do - expect(response).to have_http_status(:not_found) - end + it "denies access" do + expect(response).to have_http_status(:not_found) end + end - context "as a non-member" do - let(:permissions) { [] } + context "as a non-member" do + current_user { create(:user) } - current_user { create(:user) } + before do + board.update!(linked: sprint) + get :show, params: { project_id: project.identifier, sprint_id: sprint.id } + end - it "denies access" do - expect(response).to have_http_status(:not_found) - end + it "denies access" do + expect(response).to have_http_status(:not_found) end end end diff --git a/modules/backlogs/spec/controllers/versions_controller_spec.rb b/modules/backlogs/spec/controllers/versions_controller_spec.rb deleted file mode 100644 index 197ac8866380..000000000000 --- a/modules/backlogs/spec/controllers/versions_controller_spec.rb +++ /dev/null @@ -1,82 +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 VersionsController, "Backlog patches" do - let(:version) do - create(:version, - sharing: "system") - end - - let(:other_project) do - create(:project).tap do |p| - create(:member, - user: current_user, - roles: [create(:project_role, permissions: [:manage_versions])], - project: p) - end - end - - let(:current_user) do - create(:user, - member_with_permissions: { version.project => [:manage_versions] }) - end - - before do - # Create a version assigned to a project - @oldVersionName = version.name - @newVersionName = "NewVersionName" - - # Create params to update version - @params = {} - @params[:id] = version.id - @params[:version] = { name: @newVersionName } - login_as current_user - end - - describe "update" do - it "does not allow to update versions from different projects" do - @params[:project_id] = other_project.id - patch "update", params: @params - version.reload - - expect(response).to redirect_to project_settings_versions_path(other_project) - expect(version.name).to eq(@oldVersionName) - end - - it "allows to update versions from the version project" do - @params[:project_id] = version.project.id - patch "update", params: @params - version.reload - - expect(response).to redirect_to project_settings_versions_path(version.project) - expect(version.name).to eq(@newVersionName) - end - end -end diff --git a/modules/backlogs/spec/features/admin/backlogs_settings_spec.rb b/modules/backlogs/spec/features/admin/backlogs_settings_spec.rb index 8c486888c034..fa02e5047833 100644 --- a/modules/backlogs/spec/features/admin/backlogs_settings_spec.rb +++ b/modules/backlogs/spec/features/admin/backlogs_settings_spec.rb @@ -47,85 +47,7 @@ visit admin_backlogs_settings_path end - scenario "updating story types" do - expect(page).to have_heading "Backlogs" - - story_autocompleter.select_option "Feature", "Story" - - click_on "Save" - - expect_and_dismiss_flash type: :success, message: "Successful update." - - story_autocompleter.expect_selected "Feature", "Story" - end - - scenario "updating task type" do - expect(page).to have_heading "Backlogs" - - task_autocompleter.select_option "Task" - - click_on "Save" - - expect_and_dismiss_flash type: :success, message: "Successful update." - - task_autocompleter.expect_selected "Task" - end - - scenario "ensuring the same type is not selected as story and task type" do - expect(page).to have_heading "Backlogs" - - wait_for_network_idle - - wait_for_autocompleter_options_to_be_loaded - story_autocompleter.expect_blank - task_autocompleter.expect_blank - - # Select a value in the story autocompleter... - story_autocompleter.select_option "Feature" - story_autocompleter.expect_selected "Feature" - story_autocompleter.expect_not_disabled "Story" - story_autocompleter.close_autocompleter - - # ... which is then disabled in the task autocompleter. - task_autocompleter.open_options - task_autocompleter.expect_disabled "Feature" - - # Other way around: Select a value in the task automcompleter... - task_autocompleter.select_option "Story" - task_autocompleter.expect_selected "Story" - task_autocompleter.close_autocompleter - - # ... which will be disabled in the story autocompleter - story_autocompleter.open_options - story_autocompleter.expect_disabled "Story" - story_autocompleter.expect_selected "Feature" - end - - scenario "updating points burn direction" do - expect(page).to have_heading "Backlogs" - - choose "Down", fieldset: "Points burn up/down" - - click_on "Save" - - expect_and_dismiss_flash type: :success, message: "Successful update." - - expect(page).to have_checked_field "Down", fieldset: "Points burn up/down" - end - - scenario "updating template for wiki page" do - expect(page).to have_heading "Backlogs" - - fill_in "Template for sprint wiki page", with: "my_sprint_wiki_page" - - click_on "Save" - - expect_and_dismiss_flash type: :success, message: "Successful update." - - expect(page).to have_field "Template for sprint wiki page", with: "my_sprint_wiki_page" - end - - it "hides configuration on scrum projects feature flag active", with_flag: { scrum_projects: true } do + 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']" diff --git a/modules/backlogs/spec/features/backlogs/create_spec.rb b/modules/backlogs/spec/features/backlogs/create_spec.rb index 1958971dc0d5..10f95b97d346 100644 --- a/modules/backlogs/spec/features/backlogs/create_spec.rb +++ b/modules/backlogs/spec/features/backlogs/create_spec.rb @@ -47,7 +47,7 @@ current_user { create(:user, member_with_permissions: { project => permissions }) } - context "with the feature flag active", with_flag: { scrum_projects: true } do + context "with sprint planning enabled" do it "shows the correct breadcrumb menu" do planning_page.visit! @@ -206,12 +206,4 @@ end end - context "with the feature flag inactive" do - it "is missing the 'new sprint' button" do - planning_page.visit! - - expect(page).to have_no_button "Create" - expect(page).not_to have_test_selector("op-sprints--new-sprint-button") - end - end end diff --git a/modules/backlogs/spec/features/backlogs/edit_spec.rb b/modules/backlogs/spec/features/backlogs/edit_spec.rb index 38963c51ba26..39684f219966 100644 --- a/modules/backlogs/spec/features/backlogs/edit_spec.rb +++ b/modules/backlogs/spec/features/backlogs/edit_spec.rb @@ -80,7 +80,7 @@ planning_page.visit! end - context "with the feature flag active", with_flag: { scrum_projects: true } do + context "with sprint planning enabled" do it "lists all open sprints" do planning_page.expect_sprint_names_in_order(first_sprint.name, second_sprint.name) diff --git a/modules/backlogs/spec/features/backlogs/sprint_list_spec.rb b/modules/backlogs/spec/features/backlogs/sprint_list_spec.rb index fb4b1a233849..1fffdb7962c9 100644 --- a/modules/backlogs/spec/features/backlogs/sprint_list_spec.rb +++ b/modules/backlogs/spec/features/backlogs/sprint_list_spec.rb @@ -31,7 +31,7 @@ require "spec_helper" require_relative "../../support/pages/backlog" -RSpec.describe "Sprint list", :js, with_flag: { scrum_projects: true } do +RSpec.describe "Sprint list", :js do shared_let(:project) { create(:project) } shared_let(:other_project) { create(:project) } shared_let(:user) { create(:user, member_with_permissions: { project => %i[view_sprints view_work_packages] }) } diff --git a/modules/backlogs/spec/features/backlogs/start_finish_spec.rb b/modules/backlogs/spec/features/backlogs/start_finish_spec.rb index de692042e09a..5e26c92980e9 100644 --- a/modules/backlogs/spec/features/backlogs/start_finish_spec.rb +++ b/modules/backlogs/spec/features/backlogs/start_finish_spec.rb @@ -34,8 +34,7 @@ RSpec.describe "Start and finish sprints", :js, - with_ee: %i[board_view], - with_flag: { scrum_projects: true } do + with_ee: %i[board_view] do shared_let(:project) do create(:project, enabled_module_names: %i[backlogs work_package_tracking board_view]) end diff --git a/modules/backlogs/spec/features/burndown/show_spec.rb b/modules/backlogs/spec/features/burndown/show_spec.rb index 16d029564eb8..bf1427d428fa 100644 --- a/modules/backlogs/spec/features/burndown/show_spec.rb +++ b/modules/backlogs/spec/features/burndown/show_spec.rb @@ -31,7 +31,7 @@ require "spec_helper" require_relative "../../support/pages/backlog" -RSpec.describe "Show burndown chart", :js, with_flag: { scrum_projects: true } do +RSpec.describe "Show burndown chart", :js do include Redmine::I18n shared_let(:project) { create(:project, enabled_module_names: %w(backlogs)) } diff --git a/modules/backlogs/spec/features/inbox_column_spec.rb b/modules/backlogs/spec/features/inbox_column_spec.rb index 6a6762cef16e..27e87674e1ae 100644 --- a/modules/backlogs/spec/features/inbox_column_spec.rb +++ b/modules/backlogs/spec/features/inbox_column_spec.rb @@ -31,7 +31,7 @@ require "spec_helper" require_relative "../support/pages/backlog" -RSpec.describe "Inbox column in sprint planning view", :js, with_flag: { scrum_projects: true } do +RSpec.describe "Inbox column in sprint planning view", :js do let(:sprint_sharing) { nil } let!(:project) do create(:project, diff --git a/modules/backlogs/spec/features/projects/settings/backlog_sharing_settings_spec.rb b/modules/backlogs/spec/features/projects/settings/backlog_sharing_settings_spec.rb index b8204087370c..f498dd1422c5 100644 --- a/modules/backlogs/spec/features/projects/settings/backlog_sharing_settings_spec.rb +++ b/modules/backlogs/spec/features/projects/settings/backlog_sharing_settings_spec.rb @@ -30,7 +30,7 @@ require "rails_helper" -RSpec.describe "Backlogs project settings sprint sharing", :js, with_flag: { scrum_projects: true } do +RSpec.describe "Backlogs project settings sprint sharing", :js do let(:project) { create(:project) } let(:permissions) { %i[create_sprints share_sprint select_done_statuses] } @@ -141,13 +141,4 @@ expect(page).to have_text(I18n.t(:notice_not_authorized)) end end - - context "when scrum_projects feature flag is inactive", with_flag: { scrum_projects: false } do - it "does not show the sharing tab" do - visit project_settings_backlogs_path(project) - - expect(page).to have_heading(I18n.t(:label_backlogs)) - expect(page).to have_no_link(I18n.t("backlogs.sharing")) - end - 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 e8641776ca05..cb21b602d31e 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 @@ -31,7 +31,7 @@ require "spec_helper" require_relative "../../support/pages/backlog" -RSpec.describe "Create work package in sprint", :js, with_flag: { scrum_projects: true } do +RSpec.describe "Create work package in sprint", :js do let!(:project) do create(:project, types: [type, type2], diff --git a/modules/backlogs/spec/features/work_packages/drag_in_inbox_spec.rb b/modules/backlogs/spec/features/work_packages/drag_in_inbox_spec.rb index 902a10123229..a8a005b342d9 100644 --- a/modules/backlogs/spec/features/work_packages/drag_in_inbox_spec.rb +++ b/modules/backlogs/spec/features/work_packages/drag_in_inbox_spec.rb @@ -32,7 +32,7 @@ require_relative "../../support/pages/backlog" RSpec.describe "Dragging work packages in the inbox", - :js, with_flag: { scrum_projects: true } do + :js do create_shared_association_defaults_for_work_package_factory shared_let(:project) { create(:project) } @@ -49,7 +49,6 @@ view_work_packages edit_work_packages)) end - # The explicit positioning can be removed once the scrum_projects flag is removed shared_let(:inbox_wp1) { create(:work_package, sprint: nil, project:, position: 1) } shared_let(:inbox_wp2) { create(:work_package, sprint: nil, project:, position: 2) } shared_let(:inbox_wp3) { create(:work_package, sprint: nil, project:, position: 3) } 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 062b8a8dc809..9cb360a42cc4 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 @@ -32,7 +32,7 @@ require_relative "../../support/pages/backlog" RSpec.describe "Dragging work packages in and between sprints", - :js, :settings_reset, with_flag: { scrum_projects: true } do + :js, :settings_reset do let!(:project) do create(:project, types: [type], diff --git a/modules/backlogs/spec/features/work_packages/filter_spec.rb b/modules/backlogs/spec/features/work_packages/filter_spec.rb index 132734d3f353..417795a41dee 100644 --- a/modules/backlogs/spec/features/work_packages/filter_spec.rb +++ b/modules/backlogs/spec/features/work_packages/filter_spec.rb @@ -113,7 +113,7 @@ end end - context "on the sprint", with_flag: { scrum_projects: true } do + context "on the sprint" do shared_examples_for "filtering on sprints" do it "allows filtering by sprint" do filters.open diff --git a/modules/backlogs/spec/features/work_packages/sprints_on_wp_view_spec.rb b/modules/backlogs/spec/features/work_packages/sprints_on_wp_view_spec.rb index 52e938cfd1e7..b1fe96a25904 100644 --- a/modules/backlogs/spec/features/work_packages/sprints_on_wp_view_spec.rb +++ b/modules/backlogs/spec/features/work_packages/sprints_on_wp_view_spec.rb @@ -30,7 +30,7 @@ require "spec_helper" -RSpec.describe "Sprint displayed and selectable on work package view", :js, with_flag: { scrum_projects: true } do +RSpec.describe "Sprint displayed and selectable on work package view", :js do shared_let(:project) { create(:project) } shared_let(:sprint) { create(:agile_sprint, project:) } shared_let(:other_sprint) { create(:agile_sprint, project:) } @@ -55,14 +55,6 @@ wp_page.expect_attributes sprint: other_sprint.name end - context "with the feature flag disabled", with_flag: { scrum_projects: false } do - it "does not show a sprints property" do - wp_page.visit! - - wp_page.expect_no_attribute "Sprint" - end - end - context "when lacking the permission to see sprints" do let(:permissions) { %i(view_work_packages) } 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 360f9989ecd2..4a8bace20b3c 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 @@ -30,7 +30,7 @@ require "spec_helper" -RSpec.describe API::V3::WorkPackages::Schema::WorkPackageSchemaRepresenter, with_flag: { scrum_projects: true } do +RSpec.describe API::V3::WorkPackages::Schema::WorkPackageSchemaRepresenter do include API::V3::Utilities::PathHelper let(:custom_field) { build(:custom_field) } @@ -77,17 +77,7 @@ end end - context "when not a story with the feature flag inactive", with_flag: { scrum_projects: false } do - before do - allow(schema.type).to receive(:story?).and_return(false) - end - - it "does not show story points" do - expect(subject).not_to have_json_path("storyPoints") - end - end - - context "when not a story with the feature flag active" do + context "when not a story" do before do allow(schema.type).to receive(:story?).and_return(false) end @@ -121,17 +111,7 @@ end end - context "when not a story with the feature flag inactive", with_flag: { scrum_projects: false } do - before do - allow(schema.type).to receive(:story?).and_return(false) - end - - it "does not show position" do - expect(subject).not_to have_json_path("position") - end - end - - context "when not a story with the feature flag active" do + context "when not a story" do before do allow(schema.type).to receive(:story?).and_return(false) end @@ -181,23 +161,7 @@ end end - context "when the feature flag is disabled", with_flag: { scrum_projects: false } do - it "has no reference to the sprint" do - expect(subject).not_to have_json_path(path) - end - end - - context "when not a story with the feature flag inactive", with_flag: { scrum_projects: false } do - before do - allow(schema.type).to receive(:story?).and_return(false) - end - - it "does not show sprint" do - expect(subject).not_to have_json_path("sprint") - end - end - - context "when not a story with the feature flag active" do + context "when not a story" do before do allow(schema.type).to receive(:story?).and_return(false) 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 f545b9692b99..45792864eea5 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 @@ -81,25 +81,13 @@ describe "properties" do describe "storyPoints" do - context "when it is a story (without the feature flag on)", with_flag: { scrum_projects: false } do - it_behaves_like "property", :storyPoints do - let(:value) { story_points } - end - end - - context "when it is a story (with the feature flag on)", with_flag: { scrum_projects: true } do + context "when it is a story" do it_behaves_like "property", :storyPoints do let(:value) { story_points } end end - context "when it is a task (without the feature flag on)", with_flag: { scrum_projects: false } do - let(:type) { task_type } - - it_behaves_like "no property", :storyPoints - end - - context "when it is a task (with the feature flag on)", with_flag: { scrum_projects: true } do + context "when it is a task" do let(:type) { task_type } it_behaves_like "property", :storyPoints do @@ -115,19 +103,13 @@ end describe "position" do - context "when it is a story (without the feature flag on)", with_flag: { scrum_projects: false } do - it_behaves_like "property", :position do - let(:value) { position } - end - end - - context "when it is a story (with the feature flag on)", with_flag: { scrum_projects: true } do + context "when it is a story" do it_behaves_like "property", :position do let(:value) { position } end end - context "when it is a task (with the feature flag on)", with_flag: { scrum_projects: true } do + context "when it is a task" do let(:type) { task_type } it_behaves_like "property", :position do @@ -135,12 +117,6 @@ end end - context "when it is a task (without the feature flag on)", with_flag: { scrum_projects: false } do - let(:type) { task_type } - - it_behaves_like "no property", :position - end - context "when backlogs is disabled" do let(:enabled_module_names) { [] } @@ -150,7 +126,7 @@ end describe "links" do - describe "sprint", with_flag: { scrum_projects: true } do + describe "sprint" do let(:link) { "sprint" } let(:href) { api_v3_paths.sprint(sprint.id) } let(:title) { sprint.name } @@ -165,17 +141,7 @@ it_behaves_like "has no link" end - context "when the feature flag is inactive", with_flag: { scrum_projects: false } do - it_behaves_like "has no link" - end - - context "when it is a task with the feature flag off", with_flag: { scrum_projects: false } do - let(:type) { task_type } - - it_behaves_like "has no link" - end - - context "when it is a task with the feature flag on" do + context "when it is a task" do let(:type) { task_type } it_behaves_like "has a titled link" @@ -206,7 +172,7 @@ end describe "embedded" do - describe "sprint", with_flag: { scrum_projects: true } do + describe "sprint" do let(:embedded_path) { "_embedded/sprint" } let(:embedded_resource) { sprint } let(:embedded_resource_type) { "Sprint" } @@ -221,17 +187,7 @@ it_behaves_like "has the resource not embedded" end - context "when the feature flag is inactive", with_flag: { scrum_projects: false } do - it_behaves_like "has the resource not embedded" - end - - context "when it is a type with the feature flag off", with_flag: { scrum_projects: false } do - let(:type) { task_type } - - it_behaves_like "has the resource not embedded" - end - - context "when it is a type with the feature flag on" do + context "when it is a type" do let(:type) { task_type } it_behaves_like "has the resource embedded" diff --git a/modules/backlogs/spec/lib/open_project/backlogs/permissions_spec.rb b/modules/backlogs/spec/lib/open_project/backlogs/permissions_spec.rb index 7aaa9c0605f0..39c9546a71b5 100644 --- a/modules/backlogs/spec/lib/open_project/backlogs/permissions_spec.rb +++ b/modules/backlogs/spec/lib/open_project/backlogs/permissions_spec.rb @@ -70,13 +70,7 @@ expect(subject.controller_actions).to include("rb_sprints/start", "rb_sprints/finish") end - context "when scrum_projects feature flag is active", with_flag: { scrum_projects: true } do - it { is_expected.to be_visible } - end - - context "when scrum_projects feature flag is inactive", with_flag: { scrum_projects: false } do - it { is_expected.to be_hidden } - end + it { is_expected.to be_visible } end describe "share_sprint" do @@ -86,12 +80,6 @@ expect(subject.dependencies).to contain_exactly(:create_sprints) end - context "when scrum_projects feature flag is active", with_flag: { scrum_projects: true } do - it { is_expected.to be_visible } - end - - context "when scrum_projects feature flag is inactive", with_flag: { scrum_projects: false } do - it { is_expected.to be_hidden } - end + it { is_expected.to be_visible } end end diff --git a/modules/backlogs/spec/models/issue_position_spec.rb b/modules/backlogs/spec/models/issue_position_spec.rb deleted file mode 100644 index 5fd1e4c95af6..000000000000 --- a/modules/backlogs/spec/models/issue_position_spec.rb +++ /dev/null @@ -1,424 +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 WorkPackage do - describe "Story positions" do - def build_work_package(options) - build(:work_package, options.reverse_merge(version_id: sprint_1.id, - priority_id: priority.id, - project_id: project.id, - status_id: status.id, - type_id: story_type.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) } - - let(:story_type) { create(:type, name: "Story") } - let(:epic_type) { create(:type, name: "Epic") } - let(:task_type) { create(:type, name: "Task") } - let(:other_type) { create(:type, name: "Feedback") } - - let(:sprint_1) { create(:version, project_id: project.id, name: "Sprint 1") } - let(:sprint_2) { create(:version, project_id: project.id, name: "Sprint 2") } - - let(:work_package_1) { create_work_package(subject: "WorkPackage 1", version_id: sprint_1.id) } - let(:work_package_2) { create_work_package(subject: "WorkPackage 2", version_id: sprint_1.id) } - let(:work_package_3) { create_work_package(subject: "WorkPackage 3", version_id: sprint_1.id) } - let(:work_package_4) { create_work_package(subject: "WorkPackage 4", version_id: sprint_1.id) } - let(:work_package_5) { create_work_package(subject: "WorkPackage 5", version_id: sprint_1.id) } - - let(:work_package_a) { create_work_package(subject: "WorkPackage a", version_id: sprint_2.id) } - let(:work_package_b) { create_work_package(subject: "WorkPackage b", version_id: sprint_2.id) } - let(:work_package_c) { create_work_package(subject: "WorkPackage c", version_id: sprint_2.id) } - - let(:feedback_1) do - create_work_package(subject: "Feedback 1", version_id: sprint_1.id, - type_id: other_type.id) - end - - let(:task_1) do - create_work_package(subject: "Task 1", version_id: sprint_1.id, - type_id: task_type.id) - end - - before do - # We had problems while writing these specs, that some elements kept - # creaping 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" => [story_type.id, epic_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 = [story_type, epic_type, task_type, other_type] - sprint_1 - sprint_2 - - # Create and order work_packages - work_package_1.move_to_bottom - work_package_2.move_to_bottom - work_package_3.move_to_bottom - work_package_4.move_to_bottom - work_package_5.move_to_bottom - - work_package_a.move_to_bottom - work_package_b.move_to_bottom - work_package_c.move_to_bottom - end - - describe "- Creating a work_package in a sprint" do - it "adds it to the bottom of the list" do - new_work_package = create_work_package(subject: "Newest WorkPackage", version_id: sprint_1.id) - - expect(new_work_package).not_to be_new_record - expect(new_work_package).to be_last - end - - it "does not reorder the existing work_packages" do - new_work_package = create_work_package(subject: "Newest WorkPackage", version_id: sprint_1.id) - - expect([work_package_1, work_package_2, work_package_3, work_package_4, - work_package_5].each(&:reload).map(&:position)).to eq([1, 2, 3, 4, 5]) - end - end - - describe "- Removing a work_package from the sprint" do - it "reorders the remaining work_packages" do - work_package_2.version = sprint_2 - work_package_2.save! - - expect(sprint_1.work_packages.order(Arel.sql("id"))).to eq([work_package_1, work_package_3, work_package_4, - work_package_5]) - expect(sprint_1.work_packages.order(Arel.sql("id")).each(&:reload).map(&:position)).to eq([1, 2, 3, 4]) - end - end - - describe "- Adding a work_package to a sprint" do - it "adds it to the bottom of the list" do - work_package_a.version = sprint_1 - work_package_a.save! - - expect(work_package_a).to be_last - end - - it "does not reorder the existing work_packages" do - work_package_a.version = sprint_1 - work_package_a.save! - - expect([work_package_1, work_package_2, work_package_3, work_package_4, - work_package_5].each(&:reload).map(&:position)).to eq([1, 2, 3, 4, 5]) - end - end - - describe "- Deleting a work_package in a sprint" do - it "reorders the existing work_packages" do - work_package_3.destroy - - expect([work_package_1, work_package_2, work_package_4, - work_package_5].each(&:reload).map(&:position)).to eq([1, 2, 3, 4]) - end - end - - describe "- Changing the type" do - describe "by moving a story to another story type" do - it "keeps all positions in the sprint in tact" do - work_package_3.type = epic_type - work_package_3.save! - - expect([work_package_1, work_package_2, work_package_3, work_package_4, - work_package_5].each(&:reload).map(&:position)).to eq([1, 2, 3, 4, 5]) - end - end - - describe "by moving a story to a non-backlogs type" do - it "removes it from any list" do - work_package_3.type = other_type - work_package_3.save! - - expect(work_package_3).not_to be_in_list - end - - it "reorders the remaining stories" do - work_package_3.type = other_type - work_package_3.save! - - expect([work_package_1, work_package_2, work_package_4, - work_package_5].each(&:reload).map(&:position)).to eq([1, 2, 3, 4]) - end - end - - describe "by moving a story to the task type" do - it "removes it from any list" do - work_package_3.type = task_type - work_package_3.save! - - expect(work_package_3).not_to be_in_list - end - - it "reorders the remaining stories" do - work_package_3.type = task_type - work_package_3.save! - - expect([work_package_1, work_package_2, work_package_4, - work_package_5].each(&:reload).map(&:position)).to eq([1, 2, 3, 4]) - end - end - - describe "by moving a task to the story type" do - it "adds it to the bottom of the list" do - task_1.type = story_type - task_1.save! - - expect(task_1).to be_last - end - - it "does not reorder the existing stories" do - task_1.type = story_type - task_1.save! - - expect([work_package_1, work_package_2, work_package_3, work_package_4, work_package_5, - task_1].each(&:reload).map(&:position)).to eq([1, 2, 3, 4, 5, 6]) - end - end - - describe "by moving a non-backlogs work_package to a story type" do - it "adds it to the bottom of the list" do - feedback_1.type = story_type - feedback_1.save! - - expect(feedback_1).to be_last - end - - it "does not reorder the existing stories" do - feedback_1.type = story_type - feedback_1.save! - - expect([work_package_1, work_package_2, work_package_3, work_package_4, work_package_5, - feedback_1].each(&:reload).map(&:position)).to eq([1, 2, 3, 4, 5, 6]) - end - end - end - - describe "- Moving work_packages between projects" do - # N.B.: You cannot move a ticket to another project and change the - # 'version' at the same time. On the other hand, OpenProject tries - # to keep the 'version' if possible (e.g. within project - # hierarchies with shared versions) - - let(:project_wo_backlogs) { create(:project) } - let(:sub_project_wo_backlogs) { create(:project) } - - let(:shared_sprint) do - create(:version, - project_id: project.id, - name: "Shared Sprint", - sharing: "descendants") - end - - let(:version_go_live) do - create(:version, - project_id: project_wo_backlogs.id, - name: "Go-Live") - end - - 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 - project_wo_backlogs.enabled_module_names = project_wo_backlogs.enabled_module_names - ["backlogs"] - sub_project_wo_backlogs.enabled_module_names = sub_project_wo_backlogs.enabled_module_names - ["backlogs"] - - project_wo_backlogs.types = [story_type, task_type, other_type] - sub_project_wo_backlogs.types = [story_type, task_type, other_type] - - sub_project_wo_backlogs.move_to_child_of(project) - - shared_sprint - version_go_live - end - - describe "- Moving an work_package from a project without backlogs to a backlogs_enabled project" do - describe "if the version may not be kept" do - let(:work_package_i) do - create_work_package(subject: "WorkPackage I", - version_id: version_go_live.id, - project_id: project_wo_backlogs.id) - end - - before do - work_package_i - end - - it "sets the version_id to nil" do - result = move_to_project(work_package_i, project) - - expect(result).to be_truthy - - expect(work_package_i.version).to be_nil - end - - it "removes it from any list" do - result = move_to_project(work_package_i, project) - - expect(result).to be_truthy - - expect(work_package_i).not_to be_in_list - end - end - - describe "if the version may be kept" do - let(:work_package_i) do - create_work_package(subject: "WorkPackage I", - version_id: shared_sprint.id, - project_id: sub_project_wo_backlogs.id) - end - - before do - work_package_i - end - - it "keeps the version_id" do - result = move_to_project(work_package_i, project) - - expect(result).to be_truthy - - expect(work_package_i.version).to eq(shared_sprint) - end - - it "adds it to the bottom of the list" do - result = move_to_project(work_package_i, project) - - expect(result).to be_truthy - - expect(work_package_i).to be_first - end - end - end - - describe "- Moving an work_package away from backlogs_enabled project to a project without backlogs" do - describe "if the version may not be kept" do - it "sets the version_id to nil" do - result = move_to_project(work_package_3, project_wo_backlogs) - - expect(result).to be_truthy - - expect(work_package_3.version).to be_nil - end - - it "removes it from any list" do - result = move_to_project(work_package_3, sub_project_wo_backlogs) - - expect(result).to be_truthy - - expect(work_package_3).not_to be_in_list - end - - it "reorders the remaining work_packages" do - result = move_to_project(work_package_3, sub_project_wo_backlogs) - - expect(result).to be_truthy - - expect([work_package_1, work_package_2, work_package_4, - work_package_5].each(&:reload).map(&:position)).to eq([1, 2, 3, 4]) - end - end - - describe "if the version may be kept" do - let(:work_package_i) do - create_work_package(subject: "WorkPackage I", - version_id: shared_sprint.id) - end - let(:work_package_ii) do - create_work_package(subject: "WorkPackage II", - version_id: shared_sprint.id) - end - let(:work_package_iii) do - create_work_package(subject: "WorkPackage III", - version_id: shared_sprint.id) - end - - before do - work_package_i.move_to_bottom - work_package_ii.move_to_bottom - work_package_iii.move_to_bottom - - expect([work_package_i, work_package_ii, work_package_iii].map(&:position)).to eq([1, 2, 3]) - end - - it "keeps the version_id" do - result = move_to_project(work_package_ii, sub_project_wo_backlogs) - - expect(result).to be_truthy - - expect(work_package_ii.version).to eq(shared_sprint) - end - - it "removes it from any list" do - result = move_to_project(work_package_ii, sub_project_wo_backlogs) - - expect(result).to be_truthy - - expect(work_package_ii).not_to be_in_list - end - - it "reorders the remaining work_packages" do - result = move_to_project(work_package_ii, sub_project_wo_backlogs) - - expect(result).to be_truthy - - expect([work_package_i, work_package_iii].each(&:reload).map(&:position)).to eq([1, 2]) - end - end - end - end - end -end diff --git a/modules/backlogs/spec/models/queries/work_packages/filter/sprint_filter_spec.rb b/modules/backlogs/spec/models/queries/work_packages/filter/sprint_filter_spec.rb index 6751bde7117b..7005e1d4d3a5 100644 --- a/modules/backlogs/spec/models/queries/work_packages/filter/sprint_filter_spec.rb +++ b/modules/backlogs/spec/models/queries/work_packages/filter/sprint_filter_spec.rb @@ -30,7 +30,7 @@ require "spec_helper" -RSpec.describe Queries::WorkPackages::Filter::SprintFilter, with_flag: { scrum_projects: true } do +RSpec.describe Queries::WorkPackages::Filter::SprintFilter do let(:scope_class) do Class.new do def for_project(_project); end @@ -78,19 +78,13 @@ def pluck(*_args); end end describe "#available?" do - context "when in a project, scrum projects is active and user has the permission" do + context "when in a project and the user has the permission" do it "is true" do expect(instance).to be_available end end - context "when in a project, scrum projects is inactive and user has the permission", with_flag: { scrum_projects: false } do - it "is false" do - expect(instance).not_to be_available - end - end - - context "when in a project, scrum projects is active and user lacks the permission" do + context "when in a project and the user lacks the permission" do let(:project_permissions) { [] } it "is false" do @@ -98,7 +92,7 @@ def pluck(*_args); end end end - context "when outside a project, scrum projects is active and user has the permission" do + context "when outside a project and the user has the permission" do let(:project) { nil } it "is true" do @@ -106,16 +100,7 @@ def pluck(*_args); end end end - context "when outside a project, scrum projects is inactive and user has the permission", - with_flag: { scrum_projects: false } do - let(:project) { nil } - - it "is false" do - expect(instance).not_to be_available - end - end - - context "when outside a project, scrum projects is active and user lacks the permission" do + context "when outside a project and the user lacks the permission" do let(:project) { nil } let(:project_permissions) { [] } diff --git a/modules/backlogs/spec/models/work_package_spec.rb b/modules/backlogs/spec/models/work_package_spec.rb index f78525392889..9bd29fdf30f9 100644 --- a/modules/backlogs/spec/models/work_package_spec.rb +++ b/modules/backlogs/spec/models/work_package_spec.rb @@ -45,70 +45,4 @@ expect(ordered_positions).to eq([1, 2, nil]) end end - - describe "#backlogs_types" do - it "returns all the ids of types that are configures to be considered backlogs types" do - allow(Setting).to receive(:plugin_openproject_backlogs).and_return({ "story_types" => [1], "task_type" => 2 }) - - expect(described_class.backlogs_types).to contain_exactly(1, 2) - end - - it "returns an empty array if nothing is defined" do - allow(Setting).to receive(:plugin_openproject_backlogs).and_return({}) - - expect(described_class.backlogs_types).to eq([]) - end - - it "reflects changes to the configuration" do - allow(Setting).to receive(:plugin_openproject_backlogs).and_return({ "story_types" => [1], "task_type" => 2 }) - expect(described_class.backlogs_types).to contain_exactly(1, 2) - - allow(Setting).to receive(:plugin_openproject_backlogs).and_return({ "story_types" => [3], "task_type" => 4 }) - expect(described_class.backlogs_types).to contain_exactly(3, 4) - end - end - - describe "#story" do - shared_let(:project) { create(:project) } - shared_let(:status) { create(:status) } - shared_let(:story_type) { create(:type, name: "Story") } - shared_let(:task_type) { create(:type, name: "Task") } - - before do - allow(Setting).to receive(:plugin_openproject_backlogs).and_return({ "story_types" => [story_type.id], - "task_type" => task_type.id }) - end - - context "for a WorkPackage" do - let(:work_package) { build_stubbed(:work_package) } - - it "returns nil" do - expect(work_package.story).to be_nil - end - end - - context "for a Story" do - let(:story) { create(:story, project:, status:, type: story_type) } - - it "returns self" do - expect(story.story).to eq(story) - end - end - - context "for a Task" do - let(:parent_parent_story) { create(:story, project:, status:, type: story_type) } - let(:parent_story) { create(:story, parent: parent_parent_story, project:, status:, type: story_type) } - let(:task) { create(:task, parent: parent_story, project:, status:, type: task_type) } - - it "returns the closest WorkPackage ancestor being a Story" do - expect(task.story).to eq(described_class.find(parent_story.id)) - - # transform the parent_story into a task - parent_story.update(type: task_type) - - # the returned story is now the grand parent - expect(task.story).to eq(described_class.find(parent_parent_story.id)) - end - end - end end diff --git a/modules/backlogs/spec/models/work_packages/position_spec.rb b/modules/backlogs/spec/models/work_packages/position_spec.rb index e67ece7b9cd6..c3c3f678ba00 100644 --- a/modules/backlogs/spec/models/work_packages/position_spec.rb +++ b/modules/backlogs/spec/models/work_packages/position_spec.rb @@ -30,7 +30,7 @@ require "spec_helper" -RSpec.describe WorkPackage, "positions", with_flag: { scrum_projects: true } do # rubocop:disable RSpec/SpecFilePathFormat +RSpec.describe WorkPackage, "positions" do # rubocop:disable RSpec/SpecFilePathFormat def create_work_package(options) create(:work_package, options.reverse_merge(project:, type_id: type.id)) end @@ -40,7 +40,6 @@ def create_work_package(options) shared_let(:sprint1) { create(:agile_sprint, project:, name: "Sprint 1") } shared_let(:sprint2) { create(:agile_sprint, project:, name: "Sprint 2") } - # Once the feature flag is removed, those can be changed into shared_let let!(:sprint1_wp1) { create_work_package(subject: "Sprint 1 WorkPackage 1", sprint: sprint1) } let!(:sprint1_wp2) { create_work_package(subject: "Sprint 1 WorkPackage 2", sprint: sprint1) } let!(:sprint1_wp3) { create_work_package(subject: "Sprint 1 WorkPackage 3", sprint: sprint1) } diff --git a/modules/backlogs/spec/requests/api/v3/sprints/index_resource_spec.rb b/modules/backlogs/spec/requests/api/v3/sprints/index_resource_spec.rb index 75ca16840004..4d725f3aeeda 100644 --- a/modules/backlogs/spec/requests/api/v3/sprints/index_resource_spec.rb +++ b/modules/backlogs/spec/requests/api/v3/sprints/index_resource_spec.rb @@ -52,7 +52,7 @@ }) end - describe "GET /api/v3/sprints", with_flag: :scrum_projects do + describe "GET /api/v3/sprints" do let(:get_path) { api_v3_paths.path_for(:sprints, filters:, page_size:, offset:) } let(:filters) { [] } let(:page_size) { nil } @@ -80,10 +80,6 @@ it_behaves_like "unauthenticated access" end - context "when the feature flag is turned off", with_flag: { scrum_projects: false } do - it_behaves_like "not found" - end - context "with a page_size parameter and offset parameter" do let(:page_size) { 1 } let(:offset) { 2 } 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 4be6d08774e4..bf139b801a7c 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 @@ -52,7 +52,7 @@ }) end - describe "GET /api/v3/projects/:id/sprints", with_flag: :scrum_projects do + describe "GET /api/v3/projects/:id/sprints" do let(:get_path) { api_v3_paths.project_sprints(project.id) } before do @@ -71,8 +71,5 @@ it_behaves_like "unauthorized access" end - context "when the feature flag is turned off", with_flag: { scrum_projects: false } do - it_behaves_like "not found" - 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 40e68c0fb228..94c7202cbcc8 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 @@ -44,7 +44,7 @@ create(:user, member_with_permissions: { project => permissions }) end - describe "GET /api/v3/sprints/:id", with_flag: :scrum_projects do + describe "GET /api/v3/sprints/:id" do let(:get_path) { api_v3_paths.sprint(sprint.id) } before do @@ -73,8 +73,5 @@ it_behaves_like "not found" end - context "when the feature flag is turned off", with_flag: { scrum_projects: false } 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 9562eb918859..0a85e4276272 100644 --- a/modules/backlogs/spec/requests/rb_master_backlogs_spec.rb +++ b/modules/backlogs/spec/requests/rb_master_backlogs_spec.rb @@ -54,93 +54,76 @@ get "/projects/#{project.identifier}/backlogs" expect(response).to have_http_status(:ok) - expect(response).to render_template(:index) + expect(response).to render_template(:backlog) - expect(response).to have_turbo_frame "backlogs_container", src: "/projects/#{project.identifier}/backlogs" + expect(response).to have_turbo_frame "backlogs_container", + src: "/projects/#{project.identifier}/backlogs/backlog?all=false" expect(response).to have_turbo_frame "content-bodyRight" end context "with a Turbo Frame request" do - it "renders the list partial" do + it "renders the backlog list partial" do get "/projects/#{project.identifier}/backlogs", headers: { "Turbo-Frame" => "backlogs_container" } expect(response).to have_http_status(:ok) - expect(response).to render_template("rb_master_backlogs/_list") + expect(response).to render_template("rb_master_backlogs/_backlog_list") expect(response).to have_turbo_frame "backlogs_container" expect(response).to have_no_turbo_frame "content-bodyRight" end end - - context "with the scrum project feature flag active", with_flag: { scrum_projects: true } do - it "redirects to backlog" do - get "/projects/#{project.identifier}/backlogs" - - expect(response).to redirect_to("/projects/#{project.identifier}/backlogs/backlog") - end - end end describe "GET #backlog" do - context "without the scrum project feature flag" do - it "is not successful" do - get "/projects/#{project.identifier}/backlogs/backlog" + it "is successful" do + get "/projects/#{project.identifier}/backlogs/backlog" - expect(response).to have_http_status(:forbidden) - end + expect(response).to have_http_status(:ok) + expect(response).to render_template(:backlog) + expect(response).to have_turbo_frame "backlogs_container", + src: "/projects/#{project.identifier}/backlogs/backlog?all=false" + expect(response).to have_turbo_frame "content-bodyRight" end - context "with the scrum project feature flag active", with_flag: { scrum_projects: true } do - it "is successful" do - get "/projects/#{project.identifier}/backlogs/backlog" + it "passes all=true on the backlog turbo frame when requested" do + get "/projects/#{project.identifier}/backlogs/backlog", params: { all: "1" } - expect(response).to have_http_status(:ok) - expect(response).to render_template(:backlog) - expect(response).to have_turbo_frame "backlogs_container", - src: "/projects/#{project.identifier}/backlogs/backlog?all=false" - expect(response).to have_turbo_frame "content-bodyRight" - end + expect(response).to have_http_status(:ok) + expect(response).to have_turbo_frame "backlogs_container", + src: "/projects/#{project.identifier}/backlogs/backlog?all=true" + end - it "passes all=true on the backlog turbo frame when requested" do - get "/projects/#{project.identifier}/backlogs/backlog", params: { all: "1" } + context "with a Turbo Frame request" do + it "renders the sprint planning list partial" do + get "/projects/#{project.identifier}/backlogs/backlog", headers: { "Turbo-Frame" => "backlogs_container" } expect(response).to have_http_status(:ok) - expect(response).to have_turbo_frame "backlogs_container", - src: "/projects/#{project.identifier}/backlogs/backlog?all=true" - end - - context "with a Turbo Frame request" do - it "renders the sprint planning list partial" do - get "/projects/#{project.identifier}/backlogs/backlog", headers: { "Turbo-Frame" => "backlogs_container" } + expect(response).to render_template("rb_master_backlogs/_backlog_list") - expect(response).to have_http_status(:ok) - expect(response).to render_template("rb_master_backlogs/_backlog_list") + expect(response).to have_turbo_frame "backlogs_container" + expect(response).to have_no_turbo_frame "content-bodyRight" + end - expect(response).to have_turbo_frame "backlogs_container" - expect(response).to have_no_turbo_frame "content-bodyRight" + 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) + .and_return(Agile::Sprint.none) end - 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) - .and_return(Agile::Sprint.none) - end - - it "still renders the sprint planning container for turbo-frame requests" do - get "/projects/#{project.identifier}/backlogs/backlog", - headers: { "Turbo-Frame" => "backlogs_container" } - - expect(response).to have_http_status(:ok) - expect(response.body).to include('id="owner_backlogs_container"') - expect(response.body).to include('id="sprint_backlogs_container"') - end + it "still renders the sprint planning container for turbo-frame requests" do + get "/projects/#{project.identifier}/backlogs/backlog", + headers: { "Turbo-Frame" => "backlogs_container" } + + expect(response).to have_http_status(:ok) + expect(response.body).to include('id="owner_backlogs_container"') + expect(response.body).to include('id="sprint_backlogs_container"') end end end @@ -151,21 +134,13 @@ get "/projects/#{project.identifier}/backlogs/details/#{story.id}" expect(response).to have_http_status(:ok) - expect(response).to render_template(:index) + expect(response).to render_template(:backlog) - expect(response).to have_turbo_frame "backlogs_container", src: "/projects/#{project.identifier}/backlogs" + expect(response).to have_turbo_frame "backlogs_container", + src: "/projects/#{project.identifier}/backlogs/backlog?all=false" expect(response).to have_turbo_frame "content-bodyRight" end - context "with the scrum project feature flag active", with_flag: { scrum_projects: true } do - it "is successful and renders backlog" do - get "/projects/#{project.identifier}/backlogs/details/#{story.id}" - - expect(response).to have_http_status(:ok) - expect(response).to render_template(:backlog) - end - end - context "with a Turbo Frame request" do it "renders the split view" do get "/projects/#{project.identifier}/backlogs/details/#{story.id}", diff --git a/modules/backlogs/spec/routing/inbox_routing_spec.rb b/modules/backlogs/spec/routing/inbox_routing_spec.rb index cfa1e7288daf..0bdca34a520f 100644 --- a/modules/backlogs/spec/routing/inbox_routing_spec.rb +++ b/modules/backlogs/spec/routing/inbox_routing_spec.rb @@ -30,7 +30,7 @@ require "spec_helper" -RSpec.describe InboxController, with_flag: { scrum_projects_active: true } do +RSpec.describe InboxController do describe "routing" do it { expect(put("/projects/project_42/inbox/85/move")).to route_to( diff --git a/modules/backlogs/spec/routing/projects/settings/backlog_sharings_routing_spec.rb b/modules/backlogs/spec/routing/projects/settings/backlog_sharings_routing_spec.rb index f24881f9d032..e104d9351d08 100644 --- a/modules/backlogs/spec/routing/projects/settings/backlog_sharings_routing_spec.rb +++ b/modules/backlogs/spec/routing/projects/settings/backlog_sharings_routing_spec.rb @@ -4,27 +4,20 @@ RSpec.describe Projects::Settings::BacklogSharingsController do describe "routing" do - context "with the feature flag active", with_flag: { scrum_projects: true } do - it { - expect(get("/projects/project_42/settings/backlog_sharing")).to route_to( - controller: "projects/settings/backlog_sharings", - action: "show", - project_id: "project_42" - ) - } + it { + expect(get("/projects/project_42/settings/backlog_sharing")).to route_to( + controller: "projects/settings/backlog_sharings", + action: "show", + project_id: "project_42" + ) + } - it { - expect(patch("/projects/project_42/settings/backlog_sharing")).to route_to( - controller: "projects/settings/backlog_sharings", - action: "update", - project_id: "project_42" - ) - } - end - - context "with the feature flag inactive", with_flag: { scrum_projects: false } do - it { expect(get("/projects/project_42/settings/backlog_sharing")).not_to be_routable } - it { expect(patch("/projects/project_42/settings/backlog_sharing")).not_to be_routable } - end + it { + expect(patch("/projects/project_42/settings/backlog_sharing")).to route_to( + controller: "projects/settings/backlog_sharings", + action: "update", + project_id: "project_42" + ) + } 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 043a3fb646d8..891d66cd8af8 100644 --- a/modules/backlogs/spec/routing/rb_sprints_routing_spec.rb +++ b/modules/backlogs/spec/routing/rb_sprints_routing_spec.rb @@ -57,76 +57,64 @@ id: "21") } - context "with the feature flag active", with_flag: { scrum_projects: true } do - it { - expect(get("/projects/project_42/sprints/new_dialog")).to route_to( - controller: "rb_sprints", - action: "new_dialog", - project_id: "project_42" - ) - } - - it { - expect(get("/projects/project_42/sprints/refresh_form")).to route_to( - controller: "rb_sprints", - action: "refresh_form", - project_id: "project_42" - ) - } + it { + expect(get("/projects/project_42/sprints/new_dialog")).to route_to( + controller: "rb_sprints", + action: "new_dialog", + project_id: "project_42" + ) + } - it { - expect(post("/projects/project_42/sprints")).to route_to( - controller: "rb_sprints", - action: "create", - project_id: "project_42" - ) - } + it { + expect(get("/projects/project_42/sprints/refresh_form")).to route_to( + controller: "rb_sprints", + action: "refresh_form", + project_id: "project_42" + ) + } - it { - expect(get("/projects/project_42/sprints/21/edit_dialog")).to route_to( - controller: "rb_sprints", - action: "edit_dialog", - project_id: "project_42", - id: "21" - ) - } + it { + expect(post("/projects/project_42/sprints")).to route_to( + controller: "rb_sprints", + action: "create", + project_id: "project_42" + ) + } - it { - expect(put("/projects/project_42/sprints/21/update_agile_sprint")).to route_to( - controller: "rb_sprints", - action: "update_agile_sprint", - project_id: "project_42", - id: "21" - ) - } + it { + expect(get("/projects/project_42/sprints/21/edit_dialog")).to route_to( + controller: "rb_sprints", + action: "edit_dialog", + project_id: "project_42", + id: "21" + ) + } - it { - expect(post("/projects/project_42/sprints/21/start")).to route_to( - controller: "rb_sprints", - action: "start", - project_id: "project_42", - id: "21" - ) - } + it { + expect(put("/projects/project_42/sprints/21/update_agile_sprint")).to route_to( + controller: "rb_sprints", + action: "update_agile_sprint", + project_id: "project_42", + id: "21" + ) + } - it { - expect(post("/projects/project_42/sprints/21/finish")).to route_to( - controller: "rb_sprints", - action: "finish", - project_id: "project_42", - id: "21" - ) - } - end + it { + expect(post("/projects/project_42/sprints/21/start")).to route_to( + controller: "rb_sprints", + action: "start", + project_id: "project_42", + id: "21" + ) + } - context "with the feature flag inactive", with_flag: { scrum_projects: false } do - it { expect(get("/projects/project_42/sprints/new_dialog")).not_to be_routable } - it { expect(get("/projects/project_42/sprints/refresh_form")).not_to be_routable } - it { expect(post("/projects/project_42/sprints")).not_to be_routable } - it { expect(get("/projects/project_42/sprints/21/edit_dialog")).not_to be_routable } - it { expect(put("/projects/project_42/sprints/21/update_agile_sprint")).not_to be_routable } - it { expect(post("/projects/project_42/sprints/21/start")).not_to be_routable } - it { expect(post("/projects/project_42/sprints/21/finish")).not_to be_routable } - end + it { + expect(post("/projects/project_42/sprints/21/finish")).to route_to( + controller: "rb_sprints", + action: "finish", + project_id: "project_42", + id: "21" + ) + } end end diff --git a/modules/backlogs/spec/routing/rb_stories_routing_spec.rb b/modules/backlogs/spec/routing/rb_stories_routing_spec.rb index 4bf6b239918d..f7be891e10a0 100644 --- a/modules/backlogs/spec/routing/rb_stories_routing_spec.rb +++ b/modules/backlogs/spec/routing/rb_stories_routing_spec.rb @@ -42,21 +42,15 @@ ) } - context "with the feature flag active", with_flag: { scrum_projects: true } do - it { - expect(put("/projects/project_42/sprints/21/stories/85/move")).to route_to( - controller: "rb_stories", - action: "move", - project_id: "project_42", - sprint_id: "21", - id: "85" - ) - } - end - - context "with the feature flag inactive", with_flag: { scrum_projects: false } do - it { expect(put("/projects/project_42/sprints/21/stories/85/move")).not_to be_routable } - end + it { + expect(put("/projects/project_42/sprints/21/stories/85/move")).to route_to( + controller: "rb_stories", + action: "move", + project_id: "project_42", + sprint_id: "21", + id: "85" + ) + } it { expect(post("/projects/project_42/sprints/21/stories/85/reorder")).to route_to( diff --git a/modules/backlogs/spec/services/sprints/finish_service_spec.rb b/modules/backlogs/spec/services/sprints/finish_service_spec.rb index 44c4a31636b9..b055d6ecccc3 100644 --- a/modules/backlogs/spec/services/sprints/finish_service_spec.rb +++ b/modules/backlogs/spec/services/sprints/finish_service_spec.rb @@ -30,7 +30,7 @@ require "rails_helper" -RSpec.describe Sprints::FinishService, with_flag: { scrum_projects: true } do +RSpec.describe Sprints::FinishService do create_shared_association_defaults_for_work_package_factory shared_let(:project) { create(:project, enabled_module_names: %w[backlogs work_package_tracking]) } 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 69c721c65074..370c4f24df99 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 @@ -77,8 +77,8 @@ def create_work_package(options) 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 ) @@ -116,16 +116,16 @@ def create_work_package(options) .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 ) expect(WorkPackage.where(sprint: sprint2).to_h { [it.subject, it.position] }) .to eql( sprint2_wp3.subject => 1, - sprint2_wp2.subject => 2, - sprint2_wp1.subject => 3 + sprint2_wp2.subject => 3, + sprint2_wp1.subject => 5 ) expect(WorkPackage.where(sprint: nil).to_h { [it.subject, it.position] }) @@ -153,8 +153,8 @@ def create_work_package(options) 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/services/work_packages/update_service_sprint_preservation_spec.rb b/modules/backlogs/spec/services/work_packages/update_service_sprint_preservation_spec.rb index 206adc98a485..f49e2822977c 100644 --- a/modules/backlogs/spec/services/work_packages/update_service_sprint_preservation_spec.rb +++ b/modules/backlogs/spec/services/work_packages/update_service_sprint_preservation_spec.rb @@ -77,21 +77,34 @@ current_user { user } describe "when changing the project" do - context "when scrum_projects feature flag is active", with_flag: { scrum_projects: true } do - context "when the work package has a sprint" do - context "when moving to a project that does NOT have access to the sprint" do - it "nullifies the sprint_id" do - result = instance.call(project: target_project) + context "when the work package has a sprint" do + context "when moving to a project that does NOT have access to the sprint" do + it "nullifies the sprint_id" do + result = instance.call(project: target_project) - expect(result).to be_success - expect(work_package.reload.sprint_id).to be_nil - expect(work_package.project).to eq(target_project) - end + expect(result).to be_success + expect(work_package.reload.sprint_id).to be_nil + expect(work_package.project).to eq(target_project) + end + end + + context "when moving to a project that HAS access to the sprint" do + let(:source_sharing) { "share_all_projects" } + + it "preserves the sprint_id" do + result = instance.call(project: target_project) + + expect(result).to be_success + expect(work_package.reload.sprint_id).to eq(sprint_in_source_project.id) + expect(work_package.project).to eq(target_project) end - context "when moving to a project that HAS access to the sprint" do - let(:source_sharing) { "share_all_projects" } + context "with the manage_sprint_items permission missing" do + let(:source_project_permissions) { project_permissions - %i[manage_sprint_items] } + let(:target_project_permissions) { project_permissions - %i[manage_sprint_items] } + # Usually this should not work without the permission, but since the change is + # performed via `change_by_system`, this is bypassed. it "preserves the sprint_id" do result = instance.call(project: target_project) @@ -99,56 +112,41 @@ expect(work_package.reload.sprint_id).to eq(sprint_in_source_project.id) expect(work_package.project).to eq(target_project) end - - context "with the manage_sprint_items permission missing" do - let(:source_project_permissions) { project_permissions - %i[manage_sprint_items] } - let(:target_project_permissions) { project_permissions - %i[manage_sprint_items] } - - # Usually this should not work without the permission, but since the change is - # performed via `change_by_system`, this is bypassed. - it "preserves the sprint_id" do - result = instance.call(project: target_project) - - expect(result).to be_success - expect(work_package.reload.sprint_id).to eq(sprint_in_source_project.id) - expect(work_package.project).to eq(target_project) - end - end end + end - context "when the work package project is NOT changing" do - it "preserves the sprint_id" do - original_sprint_id = work_package.sprint_id - result = instance.call(subject: "Updated Subject") + context "when the work package project is NOT changing" do + it "preserves the sprint_id" do + original_sprint_id = work_package.sprint_id + result = instance.call(subject: "Updated Subject") - expect(result).to be_success - expect(work_package.reload.sprint_id).to eq(original_sprint_id) - end + expect(result).to be_success + expect(work_package.reload.sprint_id).to eq(original_sprint_id) end end + end - context "when the work package does NOT have a sprint" do - let(:work_package_without_sprint) do - create(:work_package, - subject: "Work Package Without Sprint", - project: source_project, - sprint: nil) - end + context "when the work package does NOT have a sprint" do + let(:work_package_without_sprint) do + create(:work_package, + subject: "Work Package Without Sprint", + project: source_project, + sprint: nil) + end - let(:instance) { described_class.new(user:, model: work_package_without_sprint) } + let(:instance) { described_class.new(user:, model: work_package_without_sprint) } - it "keeps sprint_id nil when moving to another project" do - result = instance.call(project: target_project) + it "keeps sprint_id nil when moving to another project" do + result = instance.call(project: target_project) - expect(result).to be_success - expect(work_package_without_sprint.reload.sprint_id).to be_nil - expect(work_package_without_sprint.project).to eq(target_project) - end + expect(result).to be_success + expect(work_package_without_sprint.reload.sprint_id).to be_nil + expect(work_package_without_sprint.project).to eq(target_project) end end end - describe "Integration with sprint visibility logic", with_flag: { scrum_projects: true } do + describe "Integration with sprint visibility logic" do context "when sprint is owned by the target project" do let(:sprint_in_target_project) do create(:agile_sprint, diff --git a/modules/backlogs/spec/services/work_packages/update_service_version_inheritance_spec.rb b/modules/backlogs/spec/services/work_packages/update_service_version_inheritance_spec.rb deleted file mode 100644 index 4582f5b7b631..000000000000 --- a/modules/backlogs/spec/services/work_packages/update_service_version_inheritance_spec.rb +++ /dev/null @@ -1,583 +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 WorkPackages::UpdateService, "version inheritance", type: :model do - let(:type_feature) { build(:type_feature) } - let(:type_task) { build(:type_task) } - let(:type_bug) { build(:type_bug) } - let(:version1) { project.versions.first } - let(:version2) { project.versions.last } - let(:role) { build(:project_role) } - let(:user) { build(:admin) } - let(:issue_priority) { build(:priority) } - let(:status) { build(:status, name: "status 1", is_default: true) } - - let(:project) do - p = build(:project, - members: [build(:member, - principal: user, - roles: [role])], - types: [type_feature, type_task, type_bug]) - - p.versions << build(:version, name: "Version1", project: p) - p.versions << build(:version, name: "Version2", project: p) - - p - end - - let(:story) do - story = build(:work_package, - subject: "Story", - project:, - type: type_feature, - version: version1, - status:, - author: user, - priority: issue_priority) - story - end - - let(:story2) do - story = build(:work_package, - subject: "Story2", - project:, - type: type_feature, - version: version1, - status:, - author: user, - priority: issue_priority) - story - end - - let(:story3) do - story = build(:work_package, - subject: "Story3", - project:, - type: type_feature, - version: version1, - status:, - author: user, - priority: issue_priority) - story - end - - let(:task) do - build(:work_package, - subject: "Task", - type: type_task, - version: version1, - project:, - status:, - author: user, - priority: issue_priority) - end - - let(:task2) do - build(:work_package, - subject: "Task2", - type: type_task, - version: version1, - project:, - status:, - author: user, - priority: issue_priority) - end - - let(:task3) do - build(:work_package, - subject: "Task3", - type: type_task, - version: version1, - project:, - status:, - author: user, - priority: issue_priority) - end - - let(:task4) do - build(:work_package, - subject: "Task4", - type: type_task, - version: version1, - project:, - status:, - author: user, - priority: issue_priority) - end - - let(:task5) do - build(:work_package, - subject: "Task5", - type: type_task, - version: version1, - project:, - status:, - author: user, - priority: issue_priority) - end - - let(:task6) do - build(:work_package, - subject: "Task6", - type: type_task, - version: version1, - project:, - status:, - author: user, - priority: issue_priority) - end - - let(:bug) do - build(:work_package, - subject: "Bug", - type: type_bug, - version: version1, - project:, - status:, - author: user, - priority: issue_priority) - end - - let(:bug2) do - build(:work_package, - subject: "Bug2", - type: type_bug, - version: version1, - project:, - status:, - author: user, - priority: issue_priority) - end - - let(:bug3) do - build(:work_package, - subject: "Bug3", - type: type_bug, - version: version1, - project:, - status:, - author: user, - priority: issue_priority) - end - - before do - project.save! - - 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 - - def standard_child_layout - # Layout is - # child - # -> task3 - # -> task4 - # -> bug3 - # -> task5 - # -> story3 - # -> task6 - task3.parent_id = child.id - task3.save! - task4.parent_id = child.id - task4.save! - bug3.parent_id = child.id - bug3.save! - story3.parent_id = child.id - story3.save! - - task5.parent_id = bug3.id - task5.save! - task6.parent_id = story3.id - task6.save! - - child.reload - end - - describe "WHEN changing version" do - let(:instance) { described_class.new(user:, model: parent) } - - shared_examples_for "changing parent's version changes child's version" do - it "changes the child's version to the parent's version" do - parent.save! - child.parent_id = parent.id - child.save! - - standard_child_layout - - parent.reload - - call = instance.call(version: version2) - - expect(call).to be_success - - # Because of performance, these assertions are all in one it statement - expect(child.reload.version).to eql version2 - expect(task3.reload.version).to eql version2 - expect(task4.reload.version).to eql version2 - expect(bug3.reload.version).to eql version1 - expect(story3.reload.version).to eql version1 - expect(task5.reload.version).to eql version1 - expect(task6.reload.version).to eql version1 - end - end - - shared_examples_for "changing parent's version does not change child's version" do - it "keeps the child's version" do - parent.save! - child.parent_id = parent.id - child.save! - - standard_child_layout - - parent.reload - - instance.call(version: version2) - - # Because of performance, these assertions are all in one it statement - expect(child.reload.version).to eql version1 - expect(task3.reload.version).to eql version1 - expect(task4.reload.version).to eql version1 - expect(bug3.reload.version).to eql version1 - expect(story3.reload.version).to eql version1 - expect(task5.reload.version).to eql version1 - expect(task6.reload.version).to eql version1 - end - end - - describe "WITH backlogs enabled" do - before do - project.enabled_module_names += ["backlogs"] - end - - describe "WITH a story" do - let(:parent) { story } - - describe "WITH a task as child" do - let(:child) { task2 } - - it_behaves_like "changing parent's version changes child's version" - end - - describe "WITH a non backlogs work_package as child" do - let(:child) { bug2 } - - it_behaves_like "changing parent's version does not change child's version" - end - - describe "WITH a story as a child" do - let(:child) { story2 } - - it_behaves_like "changing parent's version does not change child's version" - end - end - - describe "WITH a task (impediment) without a parent" do - let(:parent) { task } - - describe "WITH a task as child" do - let(:child) { task2 } - - it_behaves_like "changing parent's version changes child's version" - end - - describe "WITH a non backlogs work_package as child" do - let(:child) { bug } - - it_behaves_like "changing parent's version does not change child's version" - end - end - - describe "WITH a non backlogs work_package" do - let(:parent) { bug } - - describe "WITH a task as child" do - let(:child) { task } - - it_behaves_like "changing parent's version does not change child's version" - end - - describe "WITH a non backlogs work_package as child" do - let(:child) { bug2 } - - it_behaves_like "changing parent's version does not change child's version" - end - - describe "WITH a story as a child" do - let(:child) { story } - - it_behaves_like "changing parent's version does not change child's version" - end - end - end - - describe "WITH backlogs disabled" do - before do - project.enabled_module_names = project.enabled_module_names.find_all { |n| n != "backlogs" } - end - - describe "WITH a story" do - let(:parent) { story } - - describe "WITH a task as child" do - let(:child) { task2 } - - it_behaves_like "changing parent's version does not change child's version" - end - - describe "WITH a non backlogs work_package as child" do - let(:child) { bug2 } - - it_behaves_like "changing parent's version does not change child's version" - end - - describe "WITH a story as a child" do - let(:child) { story2 } - - it_behaves_like "changing parent's version does not change child's version" - end - end - - describe "WITH a task" do - before do - bug2.save! - task.parent_id = bug2.id # so that it is considered a task - task.save! - end - - let(:parent) { task } - - describe "WITH a task as child" do - let(:child) { task2 } - - it_behaves_like "changing parent's version does not change child's version" - end - - describe "WITH a non backlogs work_package as child" do - let(:child) { bug } - - it_behaves_like "changing parent's version does not change child's version" - end - end - - describe "WITH a task (impediment) without a parent" do - let(:parent) { task } - - describe "WITH a task as child" do - let(:child) { task2 } - - it_behaves_like "changing parent's version does not change child's version" - end - - describe "WITH a non backlogs work_package as child" do - let(:child) { bug } - - it_behaves_like "changing parent's version does not change child's version" - end - end - - describe "WITH a non backlogs work_package" do - let(:parent) { bug } - - describe "WITH a task as child" do - let(:child) { task } - - it_behaves_like "changing parent's version does not change child's version" - end - - describe "WITH a non backlogs work_package as child" do - let(:child) { bug2 } - - it_behaves_like "changing parent's version does not change child's version" - end - - describe "WITH a story as a child" do - let(:child) { story } - - it_behaves_like "changing parent's version does not change child's version" - end - end - end - end - - describe "WHEN changing the parent_id" do - let(:instance) { described_class.new(user:, model: child) } - - shared_examples_for "changing the child's parent_issue to the parent changes child's version" do - it "changes the child's version to the parent's version" do - child.save! - standard_child_layout - - parent.version = version2 - parent.save! - - instance.call(parent_id: parent.id) - - # Because of performance, these assertions are all in one it statement - expect(child.reload.version).to eql version2 - expect(task3.reload.version).to eql version2 - expect(task4.reload.version).to eql version2 - expect(bug3.reload.version).to eql version1 - expect(story3.reload.version).to eql version1 - expect(task5.reload.version).to eql version1 - expect(task6.reload.version).to eql version1 - end - end - - shared_examples_for "changing the child's parent to the parent leaves child's version" do - it "keeps the child's version" do - child.save! - standard_child_layout - - parent.version = version2 - parent.save! - - instance.call(parent_id: parent.id) - - # Because of performance, these assertions are all in one it statement - expect(child.reload.version).to eql version1 - expect(task3.reload.version).to eql version1 - expect(task4.reload.version).to eql version1 - expect(bug3.reload.version).to eql version1 - expect(story3.reload.version).to eql version1 - expect(task5.reload.version).to eql version1 - expect(task6.reload.version).to eql version1 - end - end - - describe "WITH backogs enabled" do - before do - story.project.enabled_module_names += ["backlogs"] - end - - describe "WITH a story as parent" do - let(:parent) { story } - - describe "WITH a story as child" do - let(:child) { story2 } - - it_behaves_like "changing the child's parent to the parent leaves child's version" - end - - describe "WITH a task as child" do - let(:child) { task2 } - - it_behaves_like "changing the child's parent_issue to the parent changes child's version" - end - - describe "WITH a non-backlogs work_package as child" do - let(:child) { bug2 } - - it_behaves_like "changing the child's parent to the parent leaves child's version" - end - end - - describe "WITH a story as parent " \ - "WITH the story having a non backlogs work_package as parent " \ - "WITH a task as child" do - before do - bug2.save! - story.parent_id = bug2.id - story.save! - end - - let(:parent) { story } - let(:child) { task2 } - - it_behaves_like "changing the child's parent_issue to the parent changes child's version" - end - - describe "WITH a task as parent" do - before do - story.save! - task.parent_id = story.id - task.save! - story.reload - task.reload - end - - # Needs to be the story because it is not possible to change a task's - # 'version_id' - let(:parent) { story } - - describe "WITH a task as child" do - let(:child) { task2 } - - it_behaves_like "changing the child's parent_issue to the parent changes child's version" - end - - describe "WITH a non-backlogs work_package as child" do - let(:child) { bug2 } - - it_behaves_like "changing the child's parent to the parent leaves child's version" - end - end - - describe "WITH an impediment (task) as parent" do - let(:parent) { task } - - describe "WITH a task as child" do - let(:child) { task2 } - - it_behaves_like "changing the child's parent_issue to the parent changes child's version" - end - - describe "WITH a non-backlogs work_package as child" do - let(:child) { bug2 } - - it_behaves_like "changing the child's parent to the parent leaves child's version" - end - end - - describe "WITH a non-backlogs work_package as parent" do - let(:parent) { bug } - - describe "WITH a story as child" do - let(:child) { story2 } - - it_behaves_like "changing the child's parent to the parent leaves child's version" - end - - describe "WITH a task as child" do - let(:child) { task2 } - - it_behaves_like "changing the child's parent to the parent leaves child's version" - end - - describe "WITH a non-backlogs work_package as child" do - let(:child) { bug2 } - - it_behaves_like "changing the child's parent to the parent leaves child's version" - end - end - end - end -end diff --git a/spec/features/versions/edit_spec.rb b/spec/features/versions/edit_spec.rb index 9c9993b29fd8..7640ed0fd190 100644 --- a/spec/features/versions/edit_spec.rb +++ b/spec/features/versions/edit_spec.rb @@ -57,51 +57,6 @@ .to have_content new_version_name end - context "when editing a shared version from a subproject with backlogs enabled", :js do - let(:child_project) do - create(:project, parent: project, enabled_module_names: %w[backlogs work_package_tracking]) - end - let(:permissions) do - { project => %i[manage_versions view_work_packages], - child_project => %i[manage_versions view_work_packages] } - end - - it "persists the version setting scoped to the subproject, not the parent project (Regression#73187)" do - visit edit_version_path(version, project_id: child_project.id) - - select I18n.t(:version_settings_display_option_right), - from: I18n.t(:label_column_in_backlog) - - click_button I18n.t(:button_save) - - expect(page).to have_text(I18n.t(:notice_successful_update)) - - # it creates a version setting for the child project - setting = VersionSetting.find_by(version:, project: child_project) - expect(setting).not_to be_nil - expect(setting).to be_display_right - - # it does not create a version setting for the parent project - expect(VersionSetting.exists?(version:, project:)).to be false - - # visiting the parent project shows the default version setting - visit edit_version_path(version, project_id: project.id) - - expect(page).to have_select( - I18n.t(:label_column_in_backlog), - selected: I18n.t(:version_settings_display_option_left) - ) - - # revisiting the settings page shows the correct version setting - visit edit_version_path(version, project_id: child_project.id) - - expect(page).to have_select( - I18n.t(:label_column_in_backlog), - selected: I18n.t(:version_settings_display_option_right) - ) - end - end - context "with a custom field" do let!(:custom_field) do create(:version_custom_field, :string, 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 20401dd1c5c7..037cb4636576 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 @@ -245,13 +245,16 @@ "Remaining work", "9h", "% Complete", "25%", "Spent time", "0h", + "Story Points", "1", "Details", "Priority", "Normal", + *(work_package.sprint.present? ? ["Sprint", work_package.sprint] : ["Sprint"]), "Version", work_package.version, "Category", work_package.category, "Project phase", "Date", "05/30/2024 - 03/13/2025", "Other", + "Position", "1", "Work Package Custom Field Long Text", "foo faa", "Empty Work Package Custom Field Long Text", "Work Package Custom Field Boolean", "Yes", @@ -627,7 +630,7 @@ def expected_description_second end end - context "with the backlogs module enabled and the feature flag active", with_flag: { scrum_projects: true } do + context "with the backlogs module enabled" do let(:enabled_module_names) { %i[backlogs] } let(:sprint) { create(:agile_sprint, name: "Sprint name for export", project:) } From 5aaa0e9f7c3b544b978389636cb212ee40fc834c Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Tue, 14 Apr 2026 20:39:24 +0100 Subject: [PATCH 03/11] 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 | 2 +- .../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 | 35 +- 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 | 495 +++++++----------- .../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 | 6 +- .../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 | 33 +- .../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 | 4 + .../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, 402 insertions(+), 7991 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 c32e868091d7..143c8a39ff79 100644 --- a/config/locales/js-en.yml +++ b/config/locales/js-en.yml @@ -556,7 +556,7 @@ en: 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." + task_board: "The sprint board shows the board linked to this sprint, so you can follow the sprint's progress there." 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 76511e52e97b..153fd8e63057 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 @@ class WorkPackageSchemaRepresenter < ::API::Decorators::SchemaRepresenter 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 5ce7f8ffec2c..000000000000 --- 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 5d17e318a82f..000000000000 --- 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 394c4c6b8943..000000000000 --- 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 d8c621ca19c6..000000000000 --- 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 94a5c78a74ca..000000000000 --- 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 df5443efab67..000000000000 --- 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 559543bda47c..9e552ff5668a 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 91259fb9e58e..c56022bd107d 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 @@ def initialize(sprint:, project:, current_user: User.current, **system_arguments ) end - def stories - @sprint.work_packages - end - private def show_task_board_link? @@ -68,9 +63,5 @@ def show_burndown_link? 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 951e5c472a99..6b752e30b972 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 @@ def breadcrumb_items 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 9465c851ea9f..708bd07d37f3 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 3e23fb94a323..7ff4cbba1a87 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 @@ def story_points 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 c4bf7b66cc83..9e79dbea2a6f 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 @@ def build_move_item(menu, label:, direction:, icon:) 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 845c60b6b7f9..3f005445d6dc 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 d76147b2eba2..6969ce3a8700 100644 --- a/modules/backlogs/app/controllers/rb_application_controller.rb +++ b/modules/backlogs/app/controllers/rb_application_controller.rb @@ -53,7 +53,6 @@ def load_sprint @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 06ae933f3eb0..c43b2fe40eaa 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 1a95316f107c..000000000000 --- 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 0810fb1f96a1..6bed7af924fc 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 @@ def split_view_base_route 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 eaf990c0fab1..000000000000 --- 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 de49f9461fe4..c060a96bf788 100644 --- a/modules/backlogs/app/controllers/rb_sprints_controller.rb +++ b/modules/backlogs/app/controllers/rb_sprints_controller.rb @@ -135,53 +135,8 @@ def finish 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 @@ def show_finish_sprint_dialog 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 6b6e49d8c396..92e9ab99c7dc 100644 --- a/modules/backlogs/app/controllers/rb_stories_controller.rb +++ b/modules/backlogs/app/controllers/rb_stories_controller.rb @@ -47,26 +47,6 @@ def menu 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 @@ def move 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 @@ def reorder 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 @@ def move_story(move_attributes) 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 @@ def update_story_with_target_and_position(attributes:) .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 @@ def moved_to_inbox ) 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 @@ def moved_to(new_sprint:) ) # 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 @@ def replace_sprint_component_via_turbo_stream(sprint:) 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 1a412742028b..cc4ed55519c6 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) - - 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]) + @board = @sprint.task_board_for(@project) - return unless (@sprint_id = params.delete(:sprint_id)) + return redirect_to(project_work_package_board_path(@project, @board)) if @board - @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 5182715d05ff..000000000000 --- 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 7684363cffac..000000000000 --- 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 521a694523fb..000000000000 --- 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 f9575dea6935..000000000000 --- 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 11b07fffed60..000000000000 --- 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 cf849e44dd32..2fe48cd69cf2 100644 --- a/modules/backlogs/app/helpers/rb_common_helper.rb +++ b/modules/backlogs/app/helpers/rb_common_helper.rb @@ -34,104 +34,6 @@ def format_date_range(dates) 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 @@ def allow_sprint_creation?(project) 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 f5d6cc6e72f9..000000000000 --- 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 59da4f36ab0e..115d05a33866 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 8ca3b991970f..000000000000 --- 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 cdb61d41a8d0..42c24d296332 100644 --- a/modules/backlogs/app/models/sprint.rb +++ b/modules/backlogs/app/models/sprint.rb @@ -94,7 +94,7 @@ def has_wiki_page 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 @@ def self.generate_burndown(only_current = true) 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 6948b8dfdbca..c7885bafddc5 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 @@ def self.types 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 @@ def set_points(p) 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 a4cf2762b447..5ca78e0653c7 100644 --- a/modules/backlogs/app/models/task.rb +++ b/modules/backlogs/app/models/task.rb @@ -30,32 +30,4 @@ 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 b8a64d4ec103..000000000000 --- 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 14d9429be80d..000000000000 --- 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 89eb86e7cfbc..000000000000 --- 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 fd34ec2bb97f..000000000000 --- 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 a249626d024f..000000000000 --- 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 18d57f1d6dcc..f3c123b33e18 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 4f630693c9d5..000000000000 --- 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 988048d99d0a..000000000000 --- 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 bf4222ec02e8..000000000000 --- 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 23dc8eb7a2d9..000000000000 --- 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 9a1cc0c1c492..000000000000 --- 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 8631ae806183..000000000000 --- 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 328edde89736..000000000000 --- 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 2c3199bbe297..000000000000 --- 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 03689ecfd3bd..000000000000 --- 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 999c419c1420..c5f167c28794 100644 --- a/modules/backlogs/config/locales/en.yml +++ b/modules/backlogs/config/locales/en.yml @@ -106,7 +106,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" @@ -129,14 +128,14 @@ en: 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" + 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." + backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." @@ -233,42 +232,26 @@ en: copy_work_package_id: "Copy work package ID" move_menu: "Move" - backlogs_points_burn_direction: "Points burn up/down" backlogs_product_backlog: "Product backlog" backlogs_story: "Story" - backlogs_story_type: "Story types" backlogs_task: "Task" - backlogs_task_type: "Task type" - backlogs_wiki_template: "Template for sprint wiki page" backlogs_empty_title: "No versions are defined yet" backlogs_empty_action_text: "To start using backlogs, please create a version first" - 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_blocks_ids: "IDs of blocked work packages" label_burndown_chart: "Burndown chart" - label_column_in_backlog: "Column in backlog" label_used_as_backlog: "Used as 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" @@ -315,13 +298,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 aa2344ae5912..59f315a19366 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 @@ member do get :menu put :move + post :reorder end end end @@ -84,34 +89,10 @@ 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 @@ 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 74cda7bb4a43..fca4d796f139 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 @@ def data_for_dates 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 @@ def project_id_query "(#{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 @@ def day_query 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 c194579d974e..9fa3d25114cb 100644 --- a/modules/backlogs/lib/open_project/backlogs/engine.rb +++ b/modules/backlogs/lib/open_project/backlogs/engine.rb @@ -54,30 +54,14 @@ def self.settings 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 @@ def self.settings 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 @@ def self.settings 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 @@ def self.settings patches %i[PermittedParams WorkPackage Status - Type Project User Version] @@ -153,7 +135,6 @@ def self.settings 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 @@ def self.settings 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 b7a22fb3ae97..d9f3d51f7ac1 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 @@ def move_after(position: nil, prev_id: nil) 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 6ac3546d83c1..ac4c649ebbb8 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 @@ def update_work_package(args = {}) 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 9ddee562a101..e0389711fcb5 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 e3dbfaa34d1a..000000000000 --- 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 0adb1ee5ad3b..7d59c6551daf 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 34e1e92b4a6e..000000000000 --- 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 cdbd75d93f29..e784f1832f68 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 @@ -63,21 +63,6 @@ def done? 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 a963c3dcf307..000000000000 --- 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 40c25c764c3c..000000000000 --- 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 df85a03b0dc4..000000000000 --- 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 d6f038344c2c..1c1a7dbd3f9a 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 @@ 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 e5225be59e26..349f194b756a 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 @@ 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 @@ def render_component 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 d833055c1d80..e28e001ee57f 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 @@ 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 @@ def render_component(folded: false, active_sprint_ids: nil) 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 46a4ce2128bc..8cabee08346b 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 @@ 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 @@ def menu_items 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 34c50fd51e81..e26271d4de3f 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 @@ 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 @@ def render_component 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 @@ def render_component 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 @@ def render_component 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 8871a242cc42..8f9e842113ad 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 @@ 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 @@ 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 @@ def render_component 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 7d0d9e6d826f..eb981472eb74 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 @@ 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 @@ 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 @@ def render_component(position: 2, max_position: 3) 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 @@ def render_component(position: 2, max_position: 3) 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 @@ def render_component(position: 2, max_position: 3) 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 9014cb642fdf..48edcc96a62a 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 @@ .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 55b83075e895..a6a842b70596 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 d415deb02213..000000000000 --- 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 6eeee3d8f9d4..cf37c280e6b5 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 @@ 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 @@ 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) } - - before do - create(:member, - project: source_project, - principal: user, - roles: [create(:project_role, permissions: source_permissions)]) - end + 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) } - it "starts the sprint and redirects to the board", :aggregate_failures do - post :start, format: :turbo_stream, params: request_params + before do + create(:member, + project: source_project, + principal: user, + roles: [create(:project_role, permissions: source_permissions)]) + end - expect(response).to be_successful - expect(response).to have_turbo_stream(action: "redirect_to") - expect(service).to have_received(:call) - end + it "starts the sprint and redirects to the board", :aggregate_failures do + post :start, format: :turbo_stream, params: request_params - context "without source-project start permission" do - let(:source_permissions) { %i[view_sprints] } + expect(response).to be_successful + expect(response).to have_turbo_stream(action: "redirect_to") + expect(service).to have_received(:call) + end - it "responds with forbidden and does not call the service", :aggregate_failures do - post :start, params: request_params + context "without source-project start permission" do + let(:source_permissions) { %i[view_sprints] } - expect(response).not_to be_successful - expect(response).to have_http_status(:forbidden) - expect(service).not_to have_received(:call) - end + 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] } + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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") } + 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 + 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 + 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 } + let(:service_result) { ServiceResult.failure } - it "redirects back with the default start failure message", :aggregate_failures do - post :start, params: request_params + 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 + 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 + 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 + 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 + 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] } + let(:permissions) { all_permissions - [:start_complete_sprint] } - it "responds with forbidden", :aggregate_failures do - post :start, params: request_params + 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 + 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 } + 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 + 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 + 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 @@ 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] } - - 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 + 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] } - 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 @@ 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 + it "responds with forbidden and does not call the service", :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) + 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 } + 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 "without the 'start_complete_sprint' permission" do - let(:permissions) { all_permissions - [:start_complete_sprint] } + let(:permissions) { all_permissions - [:start_complete_sprint] } - it "responds with forbidden", :aggregate_failures do - post :finish, params: request_params + 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 + 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 6b872b941e28..befc0704c9b2 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 @@ 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 @@ format: :html end - context "when loading from a version sprint" do - let(:load_story_id) { story.id } - let(:requested_sprint) { version_sprint } - - 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:) } + let(:load_story_id) { story.id } - 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 - - 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:) } - - 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 + 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-#{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 + 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 "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 @@ 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 @@ 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 @@ 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 55bc8425ba10..5de666676816 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 @@ 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 fa02e5047833..000000000000 --- 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 a9b685a45511..000000000000 --- 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 896cbd758a6b..000000000000 --- 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 5e26c92980e9..d70354269966 100644 --- a/modules/backlogs/spec/features/backlogs/start_finish_spec.rb +++ b/modules/backlogs/spec/features/backlogs/start_finish_spec.rb @@ -49,7 +49,6 @@ create(:user, member_with_permissions: { project => permissions }) end let(:planning_page) { Pages::Backlog.new(project) } - let(:task_statuses) { Type.find(Task.type).statuses } let(:story_type) { create(:type_feature) } let(:task_type) do type = create(:type_task) @@ -57,6 +56,7 @@ type end + let(:task_statuses) { task_type.statuses } let!(:first_sprint) do create(:agile_sprint, project:, @@ -80,10 +80,6 @@ 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) - create(:workflow, type: task_type, old_status: default_status, new_status: default_status, role: create(:project_role)) planning_page.visit! 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 ef1b711c06e7..000000000000 --- 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 9e4ba698e67d..3a3a290ffa06 100644 --- a/modules/backlogs/spec/features/empty_backlogs_spec.rb +++ b/modules/backlogs/spec/features/empty_backlogs_spec.rb @@ -37,11 +37,6 @@ 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 @@ 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 @@ 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 cbdd37965a3a..000000000000 --- 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 a66da2bb6615..000000000000 --- 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 052b1a5ce6f3..000000000000 --- 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 cb21b602d31e..747507100d47 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 @@ 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 9cb360a42cc4..5e6c11855779 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 @@ 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 417795a41dee..1d018872153a 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 @@ 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 @@ 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 @@ 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 40100f1693b4..62b0a05953ae 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 @@ 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 244ef87a0d51..000000000000 --- 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 f8a17c7cb56e..000000000000 --- 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 4a8bace20b3c..fd4d7b0fcf00 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 @@ 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 @@ 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 @@ 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 @@ 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 45792864eea5..5d1dde84e80d 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 @@ -67,11 +67,6 @@ 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 3651c976a552..7ff38a76ad8b 100644 --- a/modules/backlogs/spec/models/backlog_spec.rb +++ b/modules/backlogs/spec/models/backlog_spec.rb @@ -32,9 +32,6 @@ 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 @@ 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 4cecac0c8d81..54e3f20cdeeb 100644 --- a/modules/backlogs/spec/models/burndown_spec.rb +++ b/modules/backlogs/spec/models/burndown_spec.rb @@ -64,13 +64,6 @@ def set_attribute_journalized(story, attribute, value, day) let(:version) { create(:version, project:) } let(:sprint) { Sprint.find(version.id) } - 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 }) - end - 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 } @@ -95,6 +88,32 @@ def set_attribute_journalized(story, attribute, value, day) 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 + 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 + + 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 + describe "WITH 1 story assigned to the sprint" do let(:story) do build(:story, subject: "Story 1", diff --git a/modules/backlogs/spec/models/impediment_spec.rb b/modules/backlogs/spec/models/impediment_spec.rb deleted file mode 100644 index 1e98a9fa9848..000000000000 --- 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 25e3236f0b34..000000000000 --- 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 7cdeed4ab920..c80f8c010cff 100644 --- a/modules/backlogs/spec/models/task_spec.rb +++ b/modules/backlogs/spec/models/task_spec.rb @@ -39,12 +39,6 @@ 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 46435be8a209..000000000000 --- 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 9bd29fdf30f9..a33fe51982a1 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 bf139b801a7c..5c585e15707f 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 @@ 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 94c7202cbcc8..c2697842c9c8 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 @@ 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 0a85e4276272..be69318dada3 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 @@ 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 @@ 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 da5b89bc6ab7..000000000000 --- 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 90f07a0cde0b..000000000000 --- 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 891d66cd8af8..e1cc8bd04aab 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 @@ 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 f7be891e10a0..3e855da44669 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 @@ 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 caef12132bed..000000000000 --- 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 b29b5ed5aa81..000000000000 --- 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 21012fa323a0..000000000000 --- 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 2a5237e43570..000000000000 --- 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 370c4f24df99..85b509f44ec5 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 @@ -112,6 +112,10 @@ def create_work_package(options) end 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. 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, diff --git a/modules/backlogs/spec/support/pages/backlog.rb b/modules/backlogs/spec/support/pages/backlog.rb index 389748aa6dfb..8f60b597cc6b 100644 --- a/modules/backlogs/spec/support/pages/backlog.rb +++ b/modules/backlogs/spec/support/pages/backlog.rb @@ -44,7 +44,7 @@ def alter_attributes_in_details_view(story, **attributes) 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 @@ 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 diff --git a/modules/backlogs/spec/support/pages/backlogs.rb b/modules/backlogs/spec/support/pages/backlogs.rb deleted file mode 100644 index 4d998fe57b02..000000000000 --- 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 0c9e28d2b667..000000000000 --- 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 a950505fdf6e..e2b0b6c99f00 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 @@ 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 @@ 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 @@ 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 @@ it "renders a version 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 ae8002731e94..000000000000 --- 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 6b1cff44e8c4..000000000000 --- 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 57d240e138ff..000000000000 --- 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 68e51400244f..f102ef9c7385 100644 --- a/spec/models/type/attribute_groups_spec.rb +++ b/spec/models/type/attribute_groups_spec.rb @@ -222,14 +222,15 @@ 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 037cb4636576..b86b0c3240f2 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 @@ def remove_pdf_page_footers(strings, nr_of_pages) "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 @@ def expected_description_first "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 @@ def expected_description_second ), 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 @@ def expected_description_second 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 1e31b53c2238..000000000000 --- 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 From 5d378fbd1bd344fe16658e73b9665a55afa1d496 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Tue, 14 Apr 2026 23:05:51 +0100 Subject: [PATCH 04/11] Strip dead methods from Backlogs legacy models Strips all scopes and methods from `Sprint < Version`, including wiki page helpers, leaving a bare class shell. `Agile::Sprint` is the active model going forward. Removes `Story.types` (settings-driven type list), `Story#tasks`, and `Story#set_points`. Removes `VersionSetting` display predicates and setters (constants retained), and dead `Backlog` instance methods (`updated_at`, `owner_backlog?`, `sprint_backlog?`). Slims `VersionPatch` to just the `has_many :version_settings` association. Simplifies `Burndown#make_date_series` to the `Agile::Sprint` path only. Removes the dead locale key and stale engine permission stubs. --- modules/backlogs/app/models/backlog.rb | 15 +- modules/backlogs/app/models/burndown.rb | 10 +- modules/backlogs/app/models/sprint.rb | 140 --------- modules/backlogs/app/models/story.rb | 31 -- .../backlogs/app/models/version_setting.rb | 24 -- modules/backlogs/config/locales/en.yml | 4 - .../lib/open_project/backlogs/engine.rb | 5 +- .../backlogs/patches/version_patch.rb | 68 ----- .../lib/open_project/backlogs/engine_spec.rb | 44 +++ modules/backlogs/spec/models/burndown_spec.rb | 150 --------- modules/backlogs/spec/models/sprint_spec.rb | 286 ------------------ .../spec/models/version_setting_spec.rb | 26 -- .../v3/work_packages/form_resource_spec.rb | 4 - 13 files changed, 48 insertions(+), 759 deletions(-) create mode 100644 modules/backlogs/spec/lib/open_project/backlogs/engine_spec.rb delete mode 100644 modules/backlogs/spec/models/sprint_spec.rb diff --git a/modules/backlogs/app/models/backlog.rb b/modules/backlogs/app/models/backlog.rb index 115d05a33866..464a281dfeb8 100644 --- a/modules/backlogs/app/models/backlog.rb +++ b/modules/backlogs/app/models/backlog.rb @@ -42,22 +42,9 @@ def self.inbox_for(project:) .order(WorkPackage.arel_table[:position].asc.nulls_last, WorkPackage.arel_table[:id].asc) end - def initialize(sprint:, stories:, owner_backlog: false) + def initialize(sprint:, stories:) @sprint = sprint @stories = stories - @owner_backlog = owner_backlog - end - - def updated_at - @stories.max_by(&:updated_at).try(:updated_at) - end - - def owner_backlog? - !!@owner_backlog - end - - def sprint_backlog? - !owner_backlog? end def to_key diff --git a/modules/backlogs/app/models/burndown.rb b/modules/backlogs/app/models/burndown.rb index 266188310d6d..b0d2434c6b47 100644 --- a/modules/backlogs/app/models/burndown.rb +++ b/modules/backlogs/app/models/burndown.rb @@ -52,12 +52,10 @@ def series(_select = :active) private def make_date_series(sprint) - @days = if sprint.is_a?(Agile::Sprint) && sprint.start_date && sprint.finish_date + @days = if sprint.start_date && sprint.finish_date Day.working.from_range(from: sprint.start_date, to: sprint.finish_date).map(&:date) - elsif sprint.is_a?(Agile::Sprint) - [] else - sprint.days + [] end end @@ -101,8 +99,4 @@ def determine_max hours: @available_series.values.select { |s| s.unit == :hours }.flatten.compact.reject(&:nan?).max || 0.0 } end - - def to_h(keys, values) - Hash[*keys.zip(values).flatten] - end end diff --git a/modules/backlogs/app/models/sprint.rb b/modules/backlogs/app/models/sprint.rb index 42c24d296332..a28e9d3c3130 100644 --- a/modules/backlogs/app/models/sprint.rb +++ b/modules/backlogs/app/models/sprint.rb @@ -26,145 +26,5 @@ # See COPYRIGHT and LICENSE files for more details. #++ -require "date" - class Sprint < Version - scope :open_sprints, lambda { |project| - where(["versions.status = 'open' and versions.project_id = ?", project.id]) - .order_by_date - } - - # null last ordering - scope :order_by_date, -> { - reorder(Arel.sql("start_date ASC NULLS LAST, effective_date ASC NULLS LAST")) - } - - scope :apply_to, lambda { |project| - where("#{Version.table_name}.project_id = #{project.id}" + - " OR (#{Project.table_name}.active = #{true} AND (" + - " #{Version.table_name}.sharing = 'system'" + - " OR (#{Project.table_name}.lft >= #{project.root.lft} AND #{Project.table_name}.rgt <= #{project.root.rgt} AND #{Version.table_name}.sharing = 'tree')" + - " OR (#{Project.table_name}.lft < #{project.lft} AND #{Project.table_name}.rgt > #{project.rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" + - " OR (#{Project.table_name}.lft > #{project.lft} AND #{Project.table_name}.rgt < #{project.rgt} AND #{Version.table_name}.sharing = 'hierarchy')" + - "))") - .includes(:project) - .references(:projects) - } - - scope :displayed_left, lambda { |project| - joins(sanitize_sql_array([ - "LEFT OUTER JOIN (SELECT * from #{VersionSetting.table_name}" + - " WHERE project_id = ? ) version_settings" + - " ON version_settings.version_id = versions.id", - project.id - ])) - .where([ - "(version_settings.project_id = ? AND version_settings.display = ?)" + - " OR (version_settings.project_id is NULL)", - project.id, - VersionSetting::DISPLAY_LEFT - ]) - .joins(" - LEFT OUTER JOIN (SELECT * FROM #{VersionSetting.table_name}) AS vs - ON vs.version_id = #{Version.table_name}.id AND vs.project_id = #{Version.table_name}.project_id - ") # next take only those versions which define 'display left' in their home project or the given project (or don't define anything) - .where( - "(version_settings.display = ? OR vs.display = ? OR vs.display IS NULL)", - VersionSetting::DISPLAY_LEFT, - VersionSetting::DISPLAY_LEFT - ) - } - - scope :displayed_right, lambda { |project| - where(["version_settings.project_id = ? AND version_settings.display = ?", - project.id, VersionSetting::DISPLAY_RIGHT]) - .includes(:version_settings) - .references(:version_settings) - } - - def stories(project, options = {}) - Story.sprint_backlog(project, self, options) - end - - def points - stories.inject(0) { |sum, story| sum + story.story_points.to_i } - end - - def has_wiki_page - return false if wiki_page_title.blank? - - page = project.wiki.find_page(wiki_page_title) - return false unless page - - template = project.wiki.find_page(Setting.plugin_openproject_backlogs["wiki_template"]) - return false if template && page.text == template.text - - true - end - - def wiki_page - return "" unless project.wiki - - create_wiki_page(name) unless project.wiki.find_page(name) - update_attribute(:wiki_page_title, name) if wiki_page_title.blank? - - wiki_page_title - end - - def days(cutoff = nil, alldays = false) - # TODO: Assumes mon-fri are working days, sat-sun are not. This assumption - # is not globally right, we need to make this configurable. - cutoff = effective_date if cutoff.nil? - - (start_date..cutoff).select { |d| alldays || (d.wday > 0 and d.wday < 6) } - end - - def has_burndown? - !!(effective_date and start_date) - end - - def activity - bd = burndown("up") - return false if bd.blank? - - # Assume a sprint is active if it's only 2 days old - return true if bd.remaining_hours.size <= 2 - - WorkPackage.exists?(["version_id = ? and ((updated_at between ? and ?) or (created_at between ? and ?))", - id, -2.days.from_now, Time.now, -2.days.from_now, Time.now]) - end - - def burndown(project, burn_direction = nil) - return nil unless has_burndown? - - @cached_burndown ||= Burndown.new(self, project, burn_direction) - end - - def self.generate_burndown(only_current = true) - conditions = if only_current - ["? BETWEEN start_date AND effective_date", Date.today] - else - "1 = 1" - end - - Version.where(conditions).each(&:burndown) - end - - def settings(project) - version_settings.find { it.project_id == project.id || it.project_id.nil? } - end - - private - - def create_wiki_page(page_title, author: User.current) - template = project.wiki.find_page(Setting.plugin_openproject_backlogs["wiki_template"]) - page_text = if template - "h1. #{name}\n\n#{template.text}" - else - "h1. #{name}" - end - - page = project.wiki.pages.build(title: page_title, text: page_text, author:) - page.save! - end end diff --git a/modules/backlogs/app/models/story.rb b/modules/backlogs/app/models/story.rb index c7885bafddc5..dd7378f1ae52 100644 --- a/modules/backlogs/app/models/story.rb +++ b/modules/backlogs/app/models/story.rb @@ -28,35 +28,4 @@ class Story < WorkPackage extend OpenProject::Backlogs::Mixins::PreventIssueSti - - def self.types - types = Setting.plugin_openproject_backlogs["story_types"] - return [] if types.blank? - - types.map { |type| Integer(type) } - end - - def tasks - Task.children_of(id).order(:position) - end - - def set_points(p) - init_journal(User.current) - - if p.blank? || p == "-" - update_attribute(:story_points, nil) - return - end - - if p.downcase == "s" - update_attribute(:story_points, 0) - return - end - - p = Integer(p) - if p >= 0 - update_attribute(:story_points, p) - nil - end - end end diff --git a/modules/backlogs/app/models/version_setting.rb b/modules/backlogs/app/models/version_setting.rb index 9187eea8f05a..717dbd0b4d7b 100644 --- a/modules/backlogs/app/models/version_setting.rb +++ b/modules/backlogs/app/models/version_setting.rb @@ -35,28 +35,4 @@ class VersionSetting < ApplicationRecord DISPLAY_NONE = 1 DISPLAY_LEFT = 2 DISPLAY_RIGHT = 3 - - def display_right? - display == DISPLAY_RIGHT - end - - def display_right! - self.display = DISPLAY_RIGHT - end - - def display_left? - display == DISPLAY_LEFT - end - - def display_left! - self.display = DISPLAY_LEFT - end - - def display_none? - display == DISPLAY_NONE - end - - def display_none! - self.display = DISPLAY_NONE - end end diff --git a/modules/backlogs/config/locales/en.yml b/modules/backlogs/config/locales/en.yml index c5f167c28794..7afd81b248be 100644 --- a/modules/backlogs/config/locales/en.yml +++ b/modules/backlogs/config/locales/en.yml @@ -94,9 +94,6 @@ en: models: sprint: "Sprint" - attributes: - task_type: "Task type" - backlogs: any: "any" caption_sprints_default_fold_state: "Sprints will not be expanded by default when viewing the 'Backlog and sprints' page. Each one has to be manually expanded." @@ -202,7 +199,6 @@ en: new_story: "New story" stories_tasks: "Stories/Tasks" task_board: "Task board" - wiki: "Wiki" properties: "Properties" finish_sprint_dialog_component: diff --git a/modules/backlogs/lib/open_project/backlogs/engine.rb b/modules/backlogs/lib/open_project/backlogs/engine.rb index 9fa3d25114cb..2af787cb4908 100644 --- a/modules/backlogs/lib/open_project/backlogs/engine.rb +++ b/modules/backlogs/lib/open_project/backlogs/engine.rb @@ -39,10 +39,7 @@ class Engine < ::Rails::Engine def self.settings { default: { - "story_types" => nil, - "task_type" => nil, - "points_burn_direction" => "up", - "wiki_template" => "" + "points_burn_direction" => "up" }, menu_item: :backlogs_settings } 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 7d59c6551daf..5619807a47a3 100644 --- a/modules/backlogs/lib/open_project/backlogs/patches/version_patch.rb +++ b/modules/backlogs/lib/open_project/backlogs/patches/version_patch.rb @@ -32,74 +32,6 @@ module OpenProject::Backlogs::Patches::VersionPatch def self.included(base) base.class_eval do has_many :version_settings, dependent: :destroy - accepts_nested_attributes_for :version_settings - - include InstanceMethods - end - end - - module InstanceMethods - def used_as_backlog?(project = self.project) - return false unless project.backlogs_enabled? - - settings = version_settings&.where(project:)&.first - - !!settings&.display_right? - end - - def rebuild_story_positions(project = self.project) - return unless project.backlogs_enabled? - - WorkPackage.transaction do - # Remove position from all non-stories - WorkPackage.where(["project_id = ? AND type_id NOT IN (?) AND position IS NOT NULL", project, Story.types]) - .update_all(position: nil) - - rebuild_positions(work_packages.where(project_id: project), Story.types) - end - - nil - end - - def rebuild_task_positions(task) - return unless task.project.backlogs_enabled? - - WorkPackage.transaction do - # Add work_packages w/o position to the top of the list and add - # work_packages, that have a position, at the end - rebuild_positions(task.story.children.where(project_id: task.project), Task.type) - end - - nil - end - - def ==(other) - super || - (other.is_a?(self.class) && - id.present? && - other.id == id) - end - - def eql?(other) - self == other - end - - delegate :hash, to: :id - - def rebuild_positions(scope, type_ids) - wo_position = scope - .where(type_id: type_ids, - position: nil) - .order(Arel.sql("id")) - - w_position = scope - .where(type_id: type_ids) - .where.not(position: nil) - .order(Arel.sql("COALESCE(position, 0), id")) - - (w_position + wo_position).each_with_index do |work_package, index| - work_package.update_column(:position, index + 1) - end end end end diff --git a/modules/backlogs/spec/lib/open_project/backlogs/engine_spec.rb b/modules/backlogs/spec/lib/open_project/backlogs/engine_spec.rb new file mode 100644 index 000000000000..c35b020a474f --- /dev/null +++ b/modules/backlogs/spec/lib/open_project/backlogs/engine_spec.rb @@ -0,0 +1,44 @@ +# 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 OpenProject::Backlogs::Engine do + describe ".settings" do + it "keeps only the burn direction plugin setting" do + expect(described_class.settings).to eq( + default: { + "points_burn_direction" => "up" + }, + menu_item: :backlogs_settings + ) + end + end +end diff --git a/modules/backlogs/spec/models/burndown_spec.rb b/modules/backlogs/spec/models/burndown_spec.rb index 54e3f20cdeeb..48b7eca804fe 100644 --- a/modules/backlogs/spec/models/burndown_spec.rb +++ b/modules/backlogs/spec/models/burndown_spec.rb @@ -60,156 +60,6 @@ def set_attribute_journalized(story, attribute, value, day) subject(:burndown) { described_class.new(sprint, project) } - describe "for a version sprint" do - let(:version) { create(:version, project:) } - let(:sprint) { Sprint.find(version.id) } - - 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 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 - - 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 - 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 - - 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 - - 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 - - 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 { 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 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 - stories.each_with_index do |s, _i| - set_attribute_journalized s, :story_points=, 10, version.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, 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:) } diff --git a/modules/backlogs/spec/models/sprint_spec.rb b/modules/backlogs/spec/models/sprint_spec.rb deleted file mode 100644 index 2430780717dc..000000000000 --- a/modules/backlogs/spec/models/sprint_spec.rb +++ /dev/null @@ -1,286 +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 Sprint do - let(:sprint) { build(:sprint) } - let(:project) { build(:project) } - - describe "Class Methods" do - describe "#displayed_left" do - describe "WITH display set to left" do - before do - sprint.version_settings = [build(:version_setting, project:, - display: VersionSetting::DISPLAY_LEFT)] - sprint.project = project - sprint.save! - end - - it { - expect(Sprint.displayed_left(project)).to contain_exactly(sprint) - } - end - - describe "WITH a version setting defined for another project" do - before do - another_project = build(:project, name: "another project", - identifier: "another project") - - sprint.version_settings = [build(:version_setting, project: another_project, - display: VersionSetting::DISPLAY_RIGHT)] - sprint.project = project - sprint.save - end - - it { expect(Sprint.displayed_left(project)).to contain_exactly(sprint) } - end - - describe "WITH no version setting defined" do - before do - sprint.project = project - sprint.save! - end - - it { expect(Sprint.displayed_left(project)).to contain_exactly(sprint) } - end - - context "WITH a shared version from another project" do - let!(:parent_project) { create(:project, identifier: "parent", name: "Parent") } - - let!(:home_project) do - create(:project, identifier: "home", name: "Home").tap do |p| - p.parent = parent_project - p.save! - end - end - - let!(:sister_project) do - create(:project, identifier: "sister", name: "Sister").tap do |p| - p.parent = parent_project - p.save! - end - end - - let!(:version) { create(:version, name: "Shared Version", sharing: "tree", project: home_project) } - - let(:displayed) { Sprint.apply_to(sister_project).displayed_left(sister_project) } - - describe "WITH no version settings" do - it "includes the shared version by default" do - expect(displayed).to contain_exactly(version) - end - end - - describe "WITH display = left in home project" do - before do - VersionSetting.create version:, project: home_project, display: VersionSetting::DISPLAY_LEFT - end - - it "includes the shared version" do - expect(displayed).to contain_exactly(version) - end - end - - describe "WITH display = none in home project" do - before do - VersionSetting.create version:, project: home_project, display: VersionSetting::DISPLAY_NONE - end - - it "includes the shared version" do - expect(displayed).to be_empty - end - end - - describe "WITH display = left in sister project" do - before do - VersionSetting.create version:, project: sister_project, display: VersionSetting::DISPLAY_LEFT - end - - it "includes the shared version" do - expect(displayed).to contain_exactly(version) - end - end - - describe "WITH display = none in sister project" do - before do - VersionSetting.create version:, project: sister_project, display: VersionSetting::DISPLAY_NONE - end - - it "does not include the shared version" do - expect(displayed).to be_empty - end - end - - describe "WITH display = left in home project and display = left in sister project" do - before do - VersionSetting.create version:, project: home_project, display: VersionSetting::DISPLAY_LEFT - VersionSetting.create version:, project: sister_project, display: VersionSetting::DISPLAY_LEFT - end - - it "includes the shared version" do - expect(displayed).to contain_exactly(version) - end - end - - describe "WITH display = left in home project and display = none in sister project" do - before do - VersionSetting.create version:, project: home_project, display: VersionSetting::DISPLAY_LEFT - VersionSetting.create version:, project: sister_project, display: VersionSetting::DISPLAY_NONE - end - - it "does not include the shared version" do - expect(displayed).to be_empty - end - end - - describe "WITH display = none in home project and display = left in sister project" do - before do - VersionSetting.create version:, project: home_project, display: VersionSetting::DISPLAY_NONE - VersionSetting.create version:, project: sister_project, display: VersionSetting::DISPLAY_LEFT - end - - it "includes the shared version" do - expect(displayed).to contain_exactly(version) - end - end - - describe "WITH display = none in home project and display = none in sister project" do - before do - VersionSetting.create version:, project: home_project, display: VersionSetting::DISPLAY_NONE - VersionSetting.create version:, project: sister_project, display: VersionSetting::DISPLAY_NONE - end - - it "does not include the shared version" do - expect(displayed).to be_empty - end - end - end - end - - describe "#displayed_right" do - before do - sprint.version_settings = [build(:version_setting, project:, display: VersionSetting::DISPLAY_RIGHT)] - sprint.project = project - sprint.save! - end - - it { expect(Sprint.displayed_right(project)).to contain_exactly(sprint) } - end - - describe "#order_by_date" do - before do - @sprint1 = create(:sprint, name: "sprint1", project:, start_date: Date.today + 2.days) - @sprint2 = create(:sprint, name: "sprint2", project:, start_date: Date.today + 1.day, - effective_date: Date.today + 3.days) - @sprint3 = create(:sprint, name: "sprint3", project:, start_date: Date.today + 1.day, - effective_date: Date.today + 2.days) - end - - it "sorts the dates correctly", :aggregate_failures do - expect(Sprint.order_by_date[0]).to eql @sprint3 - expect(Sprint.order_by_date[1]).to eql @sprint2 - expect(Sprint.order_by_date[2]).to eql @sprint1 - end - end - - describe "#apply_to" do - before do - project.save - @other_project = create(:project) - end - - describe "WITH the version being shared system wide" do - before do - @version = create(:sprint, name: "systemwide", project: @other_project, sharing: "system") - end - - it { expect(Sprint.apply_to(project).size).to eq(1) } - it { expect(Sprint.apply_to(project)[0]).to eql(@version) } - end - - describe "WITH the version being shared from a parent project" do - before do - project.update(parent: @other_project) - project.reload - @version = create(:sprint, name: "descended", project: @other_project, sharing: "descendants") - end - - it { expect(Sprint.apply_to(project).size).to eq(1) } - it { expect(Sprint.apply_to(project)[0]).to eql(@version) } - end - - describe "WITH the version being shared within the tree" do - before do - @parent_project = create(:project) - @other_project.update(parent: @parent_project) - project.update(parent: @parent_project) - project.reload - @version = create(:sprint, name: "treed", project: @other_project, sharing: "tree") - end - - it { expect(Sprint.apply_to(project).size).to eq(1) } - it { expect(Sprint.apply_to(project)[0]).to eql(@version) } - end - - describe "WITH the version being shared within the tree" do - before do - @descendant_project = create(:project, parent: project) - project.reload - @version = create(:sprint, name: "hierar", project: @descendant_project, sharing: "hierarchy") - end - - it { expect(Sprint.apply_to(project).size).to eq(1) } - it { expect(Sprint.apply_to(project)[0]).to eql(@version) } - end - end - end - - describe "#wiki_page" do - let(:wiki) { create :wiki, project: } - let(:wiki_page) { WikiPage.where(wiki:, title: sprint.wiki_page).first } - let(:user) { create :user } - - before do - sprint.project = project - sprint.save! - end - - subject do - login_as user - - sprint.wiki_page - end - - it "creates a new wiki page if none is present" do - expect { subject }.not_to raise_error - - expect(wiki_page.text).to start_with("h1. #{sprint.name}") - end - end -end diff --git a/modules/backlogs/spec/models/version_setting_spec.rb b/modules/backlogs/spec/models/version_setting_spec.rb index 11d4cf6775d3..925d7b706d0c 100644 --- a/modules/backlogs/spec/models/version_setting_spec.rb +++ b/modules/backlogs/spec/models/version_setting_spec.rb @@ -34,30 +34,4 @@ it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:version) } it { expect(VersionSetting.column_names).to include("display") } - - describe "Instance Methods" do - describe "WITH display set to left" do - before do - version_setting.display_left! - end - - it { expect(version_setting.display_left?).to be_truthy } - end - - describe "WITH display set to right" do - before do - version_setting.display_right! - end - - it { expect(version_setting.display_right?).to be_truthy } - end - - describe "WITH display set to none" do - before do - version_setting.display_none! - end - - it { expect(version_setting.display_none?).to be_truthy } - end - end end diff --git a/modules/backlogs/spec/requests/api/v3/work_packages/form_resource_spec.rb b/modules/backlogs/spec/requests/api/v3/work_packages/form_resource_spec.rb index 190f239aa874..90e38e25d6ba 100644 --- a/modules/backlogs/spec/requests/api/v3/work_packages/form_resource_spec.rb +++ b/modules/backlogs/spec/requests/api/v3/work_packages/form_resource_spec.rb @@ -38,10 +38,6 @@ let(:authorized_user) { create(:user, member_with_permissions: { project => %i[view_work_packages edit_work_packages] }) } let(:unauthorized_user) { create(:user) } - before do - allow(Story).to receive(:types).and_return([work_package.type_id]) - end - describe "#post" do shared_examples_for "valid payload" do subject { response.body } From 28c0ab7aa9263f33b4307232ca6b27dae638e573 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Tue, 14 Apr 2026 23:15:46 +0100 Subject: [PATCH 05/11] Redirect backlog index Restore `#backlog` as the canonical Backlogs route by redirecting all `#index` requests to it. --- .../rb_master_backlogs_controller.rb | 6 +----- .../spec/requests/rb_master_backlogs_spec.rb | 17 ++++------------- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/modules/backlogs/app/controllers/rb_master_backlogs_controller.rb b/modules/backlogs/app/controllers/rb_master_backlogs_controller.rb index 6bed7af924fc..be4d2940f718 100644 --- a/modules/backlogs/app/controllers/rb_master_backlogs_controller.rb +++ b/modules/backlogs/app/controllers/rb_master_backlogs_controller.rb @@ -47,11 +47,7 @@ def backlog end def index - if turbo_frame_request? - render partial: "backlog_list", layout: false - else - render :backlog - end + redirect_to action: :backlog end def details diff --git a/modules/backlogs/spec/requests/rb_master_backlogs_spec.rb b/modules/backlogs/spec/requests/rb_master_backlogs_spec.rb index be69318dada3..116851e41feb 100644 --- a/modules/backlogs/spec/requests/rb_master_backlogs_spec.rb +++ b/modules/backlogs/spec/requests/rb_master_backlogs_spec.rb @@ -44,26 +44,17 @@ current_user { user } describe "GET #index" do - it "is successful" do + it "redirects to backlog" do get "/projects/#{project.identifier}/backlogs" - expect(response).to have_http_status(:ok) - expect(response).to render_template(:backlog) - - expect(response).to have_turbo_frame "backlogs_container", - src: "/projects/#{project.identifier}/backlogs/backlog?all=false" - expect(response).to have_turbo_frame "content-bodyRight" + expect(response).to redirect_to("/projects/#{project.identifier}/backlogs/backlog") end context "with a Turbo Frame request" do - it "renders the backlog list partial" do + it "redirects to backlog" do get "/projects/#{project.identifier}/backlogs", headers: { "Turbo-Frame" => "backlogs_container" } - expect(response).to have_http_status(:ok) - expect(response).to render_template("rb_master_backlogs/_backlog_list") - - expect(response).to have_turbo_frame "backlogs_container" - expect(response).to have_no_turbo_frame "content-bodyRight" + expect(response).to redirect_to("/projects/#{project.identifier}/backlogs/backlog") end end end From 91e73b8acdc18aab36c10f26a46af50cc9540f65 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Tue, 14 Apr 2026 23:20:40 +0100 Subject: [PATCH 06/11] Remove unused BacklogsSettings Stimulus controller --- .../admin/backlogs-settings.controller.ts | 144 ------------------ 1 file changed, 144 deletions(-) delete mode 100644 frontend/src/stimulus/controllers/dynamic/admin/backlogs-settings.controller.ts diff --git a/frontend/src/stimulus/controllers/dynamic/admin/backlogs-settings.controller.ts b/frontend/src/stimulus/controllers/dynamic/admin/backlogs-settings.controller.ts deleted file mode 100644 index 8482d0d0c322..000000000000 --- a/frontend/src/stimulus/controllers/dynamic/admin/backlogs-settings.controller.ts +++ /dev/null @@ -1,144 +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. - * ++ - */ - -import { Controller } from '@hotwired/stimulus'; -import { - NgOption, - NgSelectComponent, -} from '@ng-select/ng-select'; - -/** - * Stimulus Controller adding behavior to Admin > Backlogs page. - * Ensures that story types and task types are mutually exclusive. - */ -export default class BacklogsSettings extends Controller { - static targets = ['storyTypes', 'taskType']; - - declare readonly storyTypesTarget:HTMLElement; - declare readonly taskTypeTarget:HTMLElement; - declare readonly hasStoryTypesTarget:boolean; - declare readonly hasTaskTypeTarget:boolean; - - private isUpdating = false; - - storyTypesTargetConnected(target:HTMLElement) { - target.addEventListener('change', this.onStoryTypesChanged); - } - - storyTypesTargetDisconnected(target:HTMLElement) { - target.removeEventListener('change', this.onStoryTypesChanged); - } - - taskTypeTargetConnected(target:HTMLElement) { - target.addEventListener('change', this.onTaskTypeChanged); - } - - taskTypeTargetDisconnected(target:HTMLElement) { - target.removeEventListener('change', this.onTaskTypeChanged); - } - - private onStoryTypesChanged = () => { - if (this.isUpdating || !this.hasTaskTypeTarget) return; - - this.syncDisabledOptions(this.storyTypesTarget, this.taskTypeTarget); - }; - - private onTaskTypeChanged = () => { - if (this.isUpdating || !this.hasStoryTypesTarget) return; - - this.syncDisabledOptions(this.taskTypeTarget, this.storyTypesTarget); - }; - - /** - * Syncs disabled options between two autocompleters. - * Selected values in the source autocompleter will be disabled in the target. - * - * @param sourceTarget The autocompleter whose selections should disable options in the target - * @param targetTarget The autocompleter whose options should be disabled - */ - private syncDisabledOptions(sourceTarget:HTMLElement, targetTarget:HTMLElement) { - this.isUpdating = true; - try { - const sourceNgSelect = this.getNgSelectComponent(sourceTarget); - const targetNgSelect = this.getNgSelectComponent(targetTarget); - - if (!sourceNgSelect || !targetNgSelect) { - return; - } - - this.syncAutocompleters(sourceNgSelect, targetNgSelect); - } finally { - this.isUpdating = false; - } - } - - /** - * Gets the NgSelectComponent instance from an op-autocompleter element. - */ - private getNgSelectComponent(target:HTMLElement):NgSelectComponent|null { - // Access the ng-select instance stored by op-autocompleter component - // eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-member-access - return (target as any).ngSelectComponentInstance ?? null; - } - - /** - * Syncs two ng-select autocompleters - ensuring selections are mutually exclusive. - * - * @param source source autocompleter - * @param target target autocompleter - */ - private syncAutocompleters(source:NgSelectComponent, target:NgSelectComponent) { - const sourceSelectedIds = new Set( - source.selectedItems - .map((item) => item.value.id) - .filter((id) => id != null) - ); - - // Directly mutate the items array to ensure ng-select updates properly - let hasChanges = false; - target.itemsList.items.forEach((targetItem:NgOption) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const itemId = targetItem.value?.id; - - if (!itemId) return; - - const shouldBeDisabled = sourceSelectedIds.has(itemId); - if (targetItem.disabled !== shouldBeDisabled) { - targetItem.disabled = shouldBeDisabled; - hasChanges = true; - } - }); - - // Force ng-select to re-render if we made changes - if (hasChanges) { - target.detectChanges(); - } - } -} From a93e5102b327ef785d3a4c69b5be459774b3ded8 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Tue, 14 Apr 2026 23:23:11 +0100 Subject: [PATCH 07/11] Remove unused jqplot styles --- frontend/src/assets/sass/backlogs/_index.sass | 1 - frontend/src/assets/sass/backlogs/_jqplot.css | 185 ------------------ 2 files changed, 186 deletions(-) delete mode 100644 frontend/src/assets/sass/backlogs/_jqplot.css diff --git a/frontend/src/assets/sass/backlogs/_index.sass b/frontend/src/assets/sass/backlogs/_index.sass index af0bd069623f..3c4b15b8a7c1 100644 --- a/frontend/src/assets/sass/backlogs/_index.sass +++ b/frontend/src/assets/sass/backlogs/_index.sass @@ -41,7 +41,6 @@ @import global @import global_print -@import jqplot @import statistics @import master_backlog @import taskboard diff --git a/frontend/src/assets/sass/backlogs/_jqplot.css b/frontend/src/assets/sass/backlogs/_jqplot.css deleted file mode 100644 index d9467564c3a6..000000000000 --- a/frontend/src/assets/sass/backlogs/_jqplot.css +++ /dev/null @@ -1,185 +0,0 @@ -/*-- copyright -OpenProject Backlogs Plugin - -Copyright (C) the OpenProject GmbH -Copyright (C)2011 Stephan Eckardt, Tim Felgentreff, Marnen Laibow-Koser, Sandro Munda -Copyright (C)2010-2011 friflaj -Copyright (C)2010 Maxime Guilbot, Andrew Vit, Joakim Kolsjö, ibussieres, Daniel Passos, Jason Vasquez, jpic, Emiliano Heyns -Copyright (C)2009-2010 Mark Maglana -Copyright (C)2009 Joe Heck, Nate Lowrie - -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 Backlogs is a derivative work based on ChiliProject Backlogs. -The copyright follows: -Copyright (C) 2010-2011 - Emiliano Heyns, Mark Maglana, friflaj -Copyright (C) 2011 - Jens Ulferts, Gregor Schmidt - Finn GmbH - Berlin, Germany - -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. - -++*/ - -.jqplot-target { - position: relative; - color: #666; - font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; - font-size: 1em; -} - -.jqplot-axis { - font-size: .75em; -} - -.jqplot-xaxis { - margin-top: 10px; -} - -.jqplot-x2axis { - margin-bottom: 10px; -} - -.jqplot-yaxis { - margin-right: 10px; -} - -.jqplot-y2axis, .jqplot-y3axis, .jqplot-y4axis, .jqplot-y5axis, .jqplot-y6axis, .jqplot-y7axis, .jqplot-y8axis, .jqplot-y9axis { - margin-left: 10px; - margin-right: 10px; -} - -.jqplot-axis-tick, .jqplot-xaxis-tick, .jqplot-yaxis-tick, .jqplot-x2axis-tick, .jqplot-y2axis-tick, .jqplot-y3axis-tick, .jqplot-y4axis-tick, .jqplot-y5axis-tick, .jqplot-y6axis-tick, .jqplot-y7axis-tick, .jqplot-y8axis-tick, .jqplot-y9axis-tick { - position: absolute; -} - -.jqplot-xaxis-tick { - top: 0; - left: 15px; - vertical-align: top; -} - -.jqplot-x2axis-tick { - bottom: 0; - left: 15px; - vertical-align: bottom; -} - -.jqplot-yaxis-tick { - right: 0; - top: 15px; - text-align: right; -} - -.jqplot-y2axis-tick, .jqplot-y3axis-tick, .jqplot-y4axis-tick, .jqplot-y5axis-tick, .jqplot-y6axis-tick, .jqplot-y7axis-tick, .jqplot-y8axis-tick, .jqplot-y9axis-tick { - left: 0; - top: 15px; - text-align: left; -} - -.jqplot-xaxis-label { - margin-top: 10px; - font-size: 11pt; - position: absolute; -} - -.jqplot-x2axis-label { - margin-bottom: 10px; - font-size: 11pt; - position: absolute; -} - -.jqplot-yaxis-label { - margin-right: 10px; - font-size: 11pt; - position: absolute; -} - -.jqplot-y2axis-label, .jqplot-y3axis-label, .jqplot-y4axis-label, .jqplot-y5axis-label, .jqplot-y6axis-label, .jqplot-y7axis-label, .jqplot-y8axis-label, .jqplot-y9axis-label { - font-size: 11pt; - position: absolute; -} - -table.jqplot-table-legend, table.jqplot-cursor-legend { - background-color: rgba(255, 255, 255, 0.6); - border: 1px solid #ccc; - position: absolute; - font-size: .75em; -} - -td.jqplot-table-legend { - vertical-align: middle; -} - -td.jqplot-table-legend > div { - border: 1px solid #ccc; - padding: .2em; -} - -div.jqplot-table-legend-swatch { - width: 0; - height: 0; - border-top-width: .35em; - border-bottom-width: .35em; - border-left-width: .6em; - border-right-width: .6em; - border-top-style: solid; - border-bottom-style: solid; - border-left-style: solid; - border-right-style: solid; -} - -.jqplot-title { - top: 0; - left: 0; - padding-bottom: .5em; - font-size: 1.2em; -} - -table.jqplot-cursor-tooltip { - border: 1px solid #ccc; - font-size: .75em; -} - -.jqplot-cursor-tooltip { - border: 1px solid #ccc; - font-size: .75em; - white-space: nowrap; - background: rgba(208, 208, 208, 0.5); - padding: 1px; -} - -.jqplot-highlighter-tooltip { - border: 1px solid #ccc; - font-size: .75em; - white-space: nowrap; - background: rgba(208, 208, 208, 0.5); - padding: 1px; -} - -.jqplot-point-label { - font-size: .75em; -} - -td.jqplot-cursor-legend-swatch { - vertical-align: middle; - text-align: center; -} - -div.jqplot-cursor-legend-swatch { - width: 1.2em; - height: .7em; -} From 8a7ee9e18016d67be6dea0c44eb10be90f931dfa Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Tue, 14 Apr 2026 23:32:04 +0100 Subject: [PATCH 08/11] Remove legacy jQuery-based taskboard frontend Deletes the jQuery Taskboard Sass and JS chain. Keeps the Backlogs task board route and links redirecting into Boards unchanged. --- .../src/assets/sass/backlogs/_dialogues.sass | 9 - frontend/src/assets/sass/backlogs/_global.css | 66 --- .../assets/sass/backlogs/_global_print.css | 39 -- frontend/src/assets/sass/backlogs/_index.sass | 5 - .../src/assets/sass/backlogs/_statistics.css | 48 -- .../src/assets/sass/backlogs/_taskboard.sass | 329 ------------ .../controllers/dynamic/backlogs/common.ts | 140 ----- .../dynamic/backlogs/editable_inplace.ts | 66 --- .../dynamic/backlogs/impediment.ts | 91 ---- .../controllers/dynamic/backlogs/model.ts | 486 ------------------ .../controllers/dynamic/backlogs/show_main.ts | 48 -- .../controllers/dynamic/backlogs/sprint.ts | 81 --- .../controllers/dynamic/backlogs/task.ts | 127 ----- .../backlogs/taskboard-legacy.controller.ts | 17 - .../controllers/dynamic/backlogs/taskboard.ts | 209 -------- .../dynamic/backlogs/work_package.ts | 57 -- 16 files changed, 1818 deletions(-) delete mode 100644 frontend/src/assets/sass/backlogs/_dialogues.sass delete mode 100644 frontend/src/assets/sass/backlogs/_global.css delete mode 100644 frontend/src/assets/sass/backlogs/_global_print.css delete mode 100644 frontend/src/assets/sass/backlogs/_statistics.css delete mode 100644 frontend/src/assets/sass/backlogs/_taskboard.sass delete mode 100644 frontend/src/stimulus/controllers/dynamic/backlogs/common.ts delete mode 100644 frontend/src/stimulus/controllers/dynamic/backlogs/editable_inplace.ts delete mode 100644 frontend/src/stimulus/controllers/dynamic/backlogs/impediment.ts delete mode 100644 frontend/src/stimulus/controllers/dynamic/backlogs/model.ts delete mode 100644 frontend/src/stimulus/controllers/dynamic/backlogs/show_main.ts delete mode 100644 frontend/src/stimulus/controllers/dynamic/backlogs/sprint.ts delete mode 100644 frontend/src/stimulus/controllers/dynamic/backlogs/task.ts delete mode 100644 frontend/src/stimulus/controllers/dynamic/backlogs/taskboard-legacy.controller.ts delete mode 100644 frontend/src/stimulus/controllers/dynamic/backlogs/taskboard.ts delete mode 100644 frontend/src/stimulus/controllers/dynamic/backlogs/work_package.ts diff --git a/frontend/src/assets/sass/backlogs/_dialogues.sass b/frontend/src/assets/sass/backlogs/_dialogues.sass deleted file mode 100644 index 4d05872a31c4..000000000000 --- a/frontend/src/assets/sass/backlogs/_dialogues.sass +++ /dev/null @@ -1,9 +0,0 @@ -/* Hide the close button since we do no longer include the necessary image for the close icon -.controller-rb_master_backlogs, -.controller-rb_taskboards - .ui-dialog-titlebar-close - display: none - - .ui-dialog - background: var(--body-background) - border: 1px solid var(--borderColor-default) diff --git a/frontend/src/assets/sass/backlogs/_global.css b/frontend/src/assets/sass/backlogs/_global.css deleted file mode 100644 index 5e3cc7d66ba2..000000000000 --- a/frontend/src/assets/sass/backlogs/_global.css +++ /dev/null @@ -1,66 +0,0 @@ -/*-- copyright -OpenProject Backlogs Plugin - -Copyright (C) the OpenProject GmbH -Copyright (C)2011 Stephan Eckardt, Tim Felgentreff, Marnen Laibow-Koser, Sandro Munda -Copyright (C)2010-2011 friflaj -Copyright (C)2010 Maxime Guilbot, Andrew Vit, Joakim Kolsjö, ibussieres, Daniel Passos, Jason Vasquez, jpic, Emiliano Heyns -Copyright (C)2009-2010 Mark Maglana -Copyright (C)2009 Joe Heck, Nate Lowrie - -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 Backlogs is a derivative work based on ChiliProject Backlogs. -The copyright follows: -Copyright (C) 2010-2011 - Emiliano Heyns, Mark Maglana, friflaj -Copyright (C) 2011 - Jens Ulferts, Gregor Schmidt - Finn GmbH - Berlin, Germany - -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. - -++*/ - -#rb .meta { - display:none; -} -#rb #helpers { - display:none; -} -/* - .editor is the classname for field editors of sprint, - story, task, impediment. These field editors get created - at runtime whenever any of the above models are edited. -*/ -#rb .editors { - display:none; -} -#rb .ui-dialog .editor { - display:block; -} - -/* dialog */ -.ui-dialog .ui-dialog-title { float:left; margin-right:0; } -.ui-dialog.ui-widget-content { border:none; } -.ui-dialog .ui-dialog-buttonpane.ui-widget-content { border:none; } - -.subject-input { - width: 99%; -} - -th { - font-weight: var(--base-text-weight-bold); -} diff --git a/frontend/src/assets/sass/backlogs/_global_print.css b/frontend/src/assets/sass/backlogs/_global_print.css deleted file mode 100644 index 20931d7bd725..000000000000 --- a/frontend/src/assets/sass/backlogs/_global_print.css +++ /dev/null @@ -1,39 +0,0 @@ -/*-- copyright -OpenProject Backlogs Plugin - -Copyright (C) the OpenProject GmbH -Copyright (C)2011 Stephan Eckardt, Tim Felgentreff, Marnen Laibow-Koser, Sandro Munda -Copyright (C)2010-2011 friflaj -Copyright (C)2010 Maxime Guilbot, Andrew Vit, Joakim Kolsjö, ibussieres, Daniel Passos, Jason Vasquez, jpic, Emiliano Heyns -Copyright (C)2009-2010 Mark Maglana -Copyright (C)2009 Joe Heck, Nate Lowrie - -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 Backlogs is a derivative work based on ChiliProject Backlogs. -The copyright follows: -Copyright (C) 2010-2011 - Emiliano Heyns, Mark Maglana, friflaj -Copyright (C) 2011 - Jens Ulferts, Gregor Schmidt - Finn GmbH - Berlin, Germany - -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. - -++*/ - -#toolbar .links{ - display:none !important; -} diff --git a/frontend/src/assets/sass/backlogs/_index.sass b/frontend/src/assets/sass/backlogs/_index.sass index 3c4b15b8a7c1..bfd07ffb3933 100644 --- a/frontend/src/assets/sass/backlogs/_index.sass +++ b/frontend/src/assets/sass/backlogs/_index.sass @@ -39,9 +39,4 @@ @import "../../../global_styles/openproject/_variables.sass" @import "../../../global_styles/openproject/_mixins.sass" -@import global -@import global_print -@import statistics @import master_backlog -@import taskboard -@import dialogues diff --git a/frontend/src/assets/sass/backlogs/_statistics.css b/frontend/src/assets/sass/backlogs/_statistics.css deleted file mode 100644 index de64988edaf7..000000000000 --- a/frontend/src/assets/sass/backlogs/_statistics.css +++ /dev/null @@ -1,48 +0,0 @@ -/*-- copyright -OpenProject Backlogs Plugin - -Copyright (C) the OpenProject GmbH -Copyright (C)2011 Stephan Eckardt, Tim Felgentreff, Marnen Laibow-Koser, Sandro Munda -Copyright (C)2010-2011 friflaj -Copyright (C)2010 Maxime Guilbot, Andrew Vit, Joakim Kolsjö, ibussieres, Daniel Passos, Jason Vasquez, jpic, Emiliano Heyns -Copyright (C)2009-2010 Mark Maglana -Copyright (C)2009 Joe Heck, Nate Lowrie - -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 Backlogs is a derivative work based on ChiliProject Backlogs. -The copyright follows: -Copyright (C) 2010-2011 - Emiliano Heyns, Mark Maglana, friflaj -Copyright (C) 2011 - Jens Ulferts, Gregor Schmidt - Finn GmbH - Berlin, Germany - -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. - -++*/ - -.score { text-align: center; width: 1.5em; font-size: large; display: inline-block; } -.score_0 { background-color: #FF0000; } -.score_1 { background-color: #FF5300; } -.score_2 { background-color: #FF8100; } -.score_3 { background-color: #FFA100; } -.score_4 { background-color: #FFBB00; } -.score_5 { background-color: #FFD300; } -.score_6 { background-color: #FFEC00; } -.score_7 { background-color: #E9FB00; } -.score_8 { background-color: #B1F100; } -.score_9 { background-color: #74E600; } -.score_10 { background-color: #00CC00; } diff --git a/frontend/src/assets/sass/backlogs/_taskboard.sass b/frontend/src/assets/sass/backlogs/_taskboard.sass deleted file mode 100644 index 733973ed5200..000000000000 --- a/frontend/src/assets/sass/backlogs/_taskboard.sass +++ /dev/null @@ -1,329 +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. ++ - */ - -@mixin story-header - background-color: #FFFFFF - font-size: 1rem - rem-calc(5px) - opacity: 0.8 - filter: alpha(opacity = 80) - overflow: hidden - padding-bottom: 1px - padding-right: 3px - -@mixin story-footer - float: left - font-size: 1rem - rem-calc(5px) - width: 85% - margin-top: 4px - padding: 2px - padding-top: 0 - -@mixin ellipsis - overflow: hidden - white-space: nowrap - text-overflow: ellipsis - -#rb .task - color: #484848 - line-height: inherit - white-space: inherit - -#rb #taskboard - overflow-x: auto - #assigned_to_id_options - display: none - .swimlane - min-width: 107px - /* width + (2*margin) + (2*padding) of .work_package + (2*border) of cell */ - padding: 5px - width: 107px - /* Must be the same as min-width */ - #board_header - background-color: var(--body-background) - color: var(--body-font-color) - border: 1px solid var(--borderColor-default) - margin-bottom: 0 - margin-right: 10px - td - border-right: 1px dotted var(--borderColor-default) - font-weight: var(--base-text-weight-bold) - text-align: center - vertical-align: middle - padding-top: 0 - padding-bottom: 0 - line-height: 30px - &:first-child - min-width: 241px - width: 241px - - .board - background-color: var(--overlay-bgColor) - border: 1px solid var(--borderColor-default) - border-top: none - margin-right: 10px - /* IE7 table fix */ - table-layout: fixed - border-collapse: collapse - empty-cells: show - tr:hover - background-color: var(--control-transparent-bgColor-hover) - td - border-right: 1px dotted #CFCFCF - border-bottom: 1px dotted #CFCFCF - vertical-align: top - &:first-child - min-width: 210px - padding: 5px - width: 210px - tr:last-child td - border-bottom: none - .add_new - margin: 0 - min-width: 30px - padding: 0 - text-align: center - vertical-align: middle - width: 30px - &.clickable:hover - cursor: pointer - background-color: var(--highlight-neutral-bgColor) - .story, .label_sprint_impediments - background-color: var(--display-lemon-bgColor-muted) - color: var(--fgColor-muted) - border: none - display: block - min-height: 100px - margin: 5px - padding: 5px - position: relative - width: 190px - .story - .subject - height: 42px - line-height: 13px - margin-top: 0 - overflow: hidden - padding: 2px - width: 180px - &.closed .subject - text-decoration: line-through - .work_package, .placeholder - background-color: #AFAFAF - color: var(--color-ansi-black) - border: none - cursor: move - display: block - font-size: 10px - height: 85px - padding: 5px - margin: 5px 0px - position: relative - width: 85px - .work_package.prevent_edit - cursor: default - .placeholder - background-color: #FFFF00 - border: 1px dashed #333300 - height: 78px - width: 83px - .work_package - &.closed .subject.editable - text-decoration: line-through - .v - display: none - .remaining_hours.editable - border: 2px solid #FFFFFF - background-color: #EE0000 - bottom: -5px - color: #FFFFFF - font-size: 9px - height: 18px - padding-left: 5px - padding-right: 5px - position: absolute - right: -5px - .blocks, .remaining_hours.editable.empty - display: none - .indicator - display: none - - &.error .indicator - background: none - border: none - - &.error.icon-bug:before - position: absolute - top: 30px - left: 28px - color: red - - - .editors - display: none - -/* - * swimlane class is used by: - * - #board_header - * - .board - * - * Also use by the Column Width preference to determine the unit width of the - * swimlanes. See RB.Taskboard.initialize() - -/* status labels */ - -/* shared #impediments and #tasks */ - -/* item styles used by .task and .impediment */ - -/* dialog */ - -.task_editor_dialog.ui-dialog - .ui-widget-header - background-color: var(--bgColor-muted) - filter: alpha(opacity = 50) - .ui-dialog-title - float: right - margin-right: 0 - color: var(--body-font-color) - &.ui-widget-content - background: none - border: none - .ui-dialog-buttonpane.ui-widget-content - background: none - background-color: none - border: none - -.dark - #task_editor label, .subject, .assigned_to_id, div - color: #FFFFFF - option - color: var(--body-font-color) - -.light - #task_editor label, .subject, .assigned_to_id, div - color: var(--body-font-color) - -/* item editor */ - -#task_editor - label:first-letter - text-transform: capitalize - label - display: block - font-size: 11px - width: 100% - .editor - font-size: 11px - margin-bottom: 10px - width: 100% - .subject - height: 65px - width: 272px - .remaining_hours, .blocks - width: 268px - -/* compact view */ - -#rb - .compact - .story, .label_sprint_impediments - height: 15px - .story .subject - display: none - .work_package - height: 21px - padding: 0 - width: 21px - * - display: none - .placeholder - background-color: #FFFF00 - border: 1px dashed #333300 - height: 19px - width: 19px - #impediment_template, #task_template - display: none - -/* others */ - -.story - .story-bar - @include story-header - text-align: right - width: 180px - clear: both - .id - float: right - .status - float: left - -.story, -.label_sprint_impediments - font-size: 1rem - rem-calc(3px) - -.work_package - .id - @include story-header - text-align: right - width: 75px - a - opacity: 1.0 - filter: alpha(opacity = 100) - .editable:hover - background-color: transparent - .subject.editable - font-size: 1rem - rem-calc(3px) - height: 42px - line-height: 13px - margin-top: 0 - overflow: hidden - padding: 2px - width: 81px - -.story - .story-footer - .assigned_to_id - @include story-footer - @include ellipsis - .story-points - margin-top: 2px - float: right -.work_package - .assigned_to_id.editable - @include story-footer - .t - @include ellipsis - -/* Toolbar modifications (no support for labels form the component) */ - -#toolbar - label[for=col_width_input] - padding-top: rem-calc(20px) - - #col_width_input - max-width: 60px diff --git a/frontend/src/stimulus/controllers/dynamic/backlogs/common.ts b/frontend/src/stimulus/controllers/dynamic/backlogs/common.ts deleted file mode 100644 index 12937fef187b..000000000000 --- a/frontend/src/stimulus/controllers/dynamic/backlogs/common.ts +++ /dev/null @@ -1,140 +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. -//++ - -import 'jquery.cookie'; - -// @ts-expect-error TS(2339): Property 'RB' does not exist on type 'Window & typ... Remove this comment to see the full error message -if (window.RB === null || window.RB === undefined) { - // @ts-expect-error TS(2339): Property 'RB' does not exist on type 'Window & typ... Remove this comment to see the full error message - window.RB = {}; -} - -(function ($) { - let object:any; - let Factory; - let Dialog; - let UserPreferences; - - object = { - // Douglas Crockford's technique for object extension - // http://javascript.crockford.com/prototypal.html - create() { - let obj; - let i; - let methods; - let methodName; - - function F() { - } - - F.prototype = arguments[0]; - // @ts-expect-error TS(7009): 'new' expression, whose target lacks a construct s... Remove this comment to see the full error message - obj = new F(); - - // Add all the other arguments as mixins that - // 'write over' any existing methods - for (i = 1; i < arguments.length; i += 1) { - methods = arguments[i]; - if (typeof methods === 'object') { - for (methodName in methods) { - if (methods.hasOwnProperty(methodName)) { - obj[methodName] = methods[methodName]; - } - } - } - } - return obj; - }, - }; - - // Object factory for chiliproject_backlogs - Factory = object.create({ - - initialize(objType:any, el:any) { - let obj; - - obj = object.create(objType); - obj.initialize(el); - return obj; - }, - - }); - - // Utilities - Dialog = object.create({ - msg(msg:any) { - let dialog; - let baseClasses; - - baseClasses = 'ui-button ui-widget ui-state-default ui-corner-all'; - - if ($('#msgBox').length === 0) { - dialog = $('
').appendTo('body'); - } else { - dialog = $('#msgBox'); - } - - dialog.html(msg); - dialog.dialog({ - title: 'Backlogs Plugin', - buttons: [ - { - text: 'OK', - class: 'button -primary', - click() { - $(this).dialog('close'); - }, - }], - modal: true, - }); - $('.button').removeClass(baseClasses); - $('.ui-icon-closethick').prop('title', 'close'); - }, - }); - - // Abstract the user preference from the rest of the RB objects - // so that we can change the underlying implementation as needed - UserPreferences = object.create({ - get(key:any) { - return $.cookie(key); - }, - - set(key:any, value:any) { - $.cookie(key, value, { expires: 365 * 10 }); - }, - }); - - // @ts-expect-error TS(2304): Cannot find name 'RB'. - RB.Object = object; - // @ts-expect-error TS(2304): Cannot find name 'RB'. - RB.Factory = Factory; - // @ts-expect-error TS(2304): Cannot find name 'RB'. - RB.Dialog = Dialog; - // @ts-expect-error TS(2304): Cannot find name 'RB'. - RB.UserPreferences = UserPreferences; -}(jQuery)); diff --git a/frontend/src/stimulus/controllers/dynamic/backlogs/editable_inplace.ts b/frontend/src/stimulus/controllers/dynamic/backlogs/editable_inplace.ts deleted file mode 100644 index 22c947f995b4..000000000000 --- a/frontend/src/stimulus/controllers/dynamic/backlogs/editable_inplace.ts +++ /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. -//++ - -// @ts-expect-error TS(2304): Cannot find name 'RB'. -RB.EditableInplace = (function ($) { - // @ts-expect-error TS(2304): Cannot find name 'RB'. - return RB.Object.create(RB.Model, { - - displayEditor(editor:any) { - this.$.addClass('editing'); - editor.find('.editor').bind('keydown', this.handleKeydown); - }, - - getEditor() { - // Create the model editor container if it does not yet exist - let editor = this.$.children('.editors'); - - if (editor.length === 0) { - editor = $("
").appendTo(this.$); - } else if (!editor.hasClass('permanent')) { - editor.first().html(''); - } - return editor; - }, - - // For detecting Enter and ESC - handleKeydown(e:any) { - let j; - let that; - - j = $(this).parents('.model').first(); - that = j.data('this'); - - if (e.key === 'Enter') { - that.saveEdits(); - } else if (e.key === 'Escape') { - that.cancelEdit(); - } - }, - }); -}(jQuery)); diff --git a/frontend/src/stimulus/controllers/dynamic/backlogs/impediment.ts b/frontend/src/stimulus/controllers/dynamic/backlogs/impediment.ts deleted file mode 100644 index 9393425dafbe..000000000000 --- a/frontend/src/stimulus/controllers/dynamic/backlogs/impediment.ts +++ /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. -//++ - -/************************************** - IMPEDIMENT -***************************************/ - -// @ts-expect-error TS(2304): Cannot find name 'RB'. -RB.Impediment = (function ($) { - // @ts-expect-error TS(2304): Cannot find name 'RB'. - return RB.Object.create(RB.Task, { - - initialize(el:any) { - let j; // This ensures that we use a local 'j' variable, not a global one. - - this.$ = j = $(el); - this.el = el; - - j.addClass('impediment'); // If node is based on #task_template, it doesn't have the impediment class yet - - // Associate this object with the element for later retrieval - j.data('this', this); - - j.on('mouseup', '.editable', this.handleClick); - }, - - // Override saveDirectives of RB.Task - saveDirectives() { - let j; - let prev; - let statusID; - - let method; - let url; - let data; - - j = this.$; - prev = this.$.prev(); - statusID = j.parent('td').first().attr('id').split('_')[1]; - - data = `${j.find('.editor').serialize() - }&is_impediment=true` - // @ts-expect-error TS(2304): Cannot find name 'RB'. - + `&version_id=${RB.constants.sprint_id - }&status_id=${statusID - }&prev=${prev.length === 1 ? prev.data('this').getID() : '' - }${this.isNew() ? '' : `&id=${j.children('.id').text()}`}`; - - if (this.isNew()) { - // @ts-expect-error TS(2304): Cannot find name 'RB'. - url = RB.urlFor('create_impediment', { sprint_id: RB.constants.sprint_id }); - method = 'post'; - } else { - // @ts-expect-error TS(2304): Cannot find name 'RB'. - url = RB.urlFor('update_impediment', { id: this.getID(), sprint_id: RB.constants.sprint_id }); - method = 'put'; - } - - return { - url, - method, - data, - }; - }, - }); -}(jQuery)); diff --git a/frontend/src/stimulus/controllers/dynamic/backlogs/model.ts b/frontend/src/stimulus/controllers/dynamic/backlogs/model.ts deleted file mode 100644 index b77e59ad5806..000000000000 --- a/frontend/src/stimulus/controllers/dynamic/backlogs/model.ts +++ /dev/null @@ -1,486 +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. -//++ - -import { FetchRequest, FetchResponse } from '@rails/request.js'; - -/*************************************** - MODEL - Common methods for sprint, work_package, - story, task, and impediment -***************************************/ - -// @ts-expect-error TS(2304): Cannot find name 'RB'. -RB.Model = (function ($) { - // @ts-expect-error TS(2304): Cannot find name 'RB'. - return RB.Object.create({ - - initialize(el:any) { - this.$ = $(el); - this.el = el; - }, - - afterCreate(data:string, response:FetchResponse) { - // Do nothing. Child objects may optionally override this - }, - - afterSave(data:string, response:FetchResponse) { - let isNew; - let result; - - isNew = this.isNew(); - // @ts-expect-error TS(2304): Cannot find name 'RB'. - result = RB.Factory.initialize(RB.Model, data); - - this.unmarkSaving(); - this.refresh(result); - - if (isNew) { - const id = result.$.filter('.model').attr('id'); - this.$.attr('id', id); - - this.afterCreate(data, response); - } else { - this.afterUpdate(data, response); - } - }, - - afterUpdate(data:string, response:FetchResponse) { - // Do nothing. Child objects may optionally override this - }, - - beforeSave() { - // Do nothing. Child objects may or may not override this method - }, - - cancelEdit() { - this.endEdit(); - if (this.isNew()) { - this.$.hide('blind'); - } - }, - - close() { - this.$.addClass('closed'); - }, - - copyFromDialog() { - let editors; - - if (this.$.find('.editors').length === 0) { - editors = $("
").appendTo(this.$); - } else { - editors = this.$.find('.editors').first(); - } - editors.html(''); - editors.append($(`#${this.getType().toLowerCase()}_editor`).children('.editor')); - this.saveEdits(); - }, - - displayEditor(editor:any) { - const self = this; - let baseClasses; - - baseClasses = 'ui-button ui-widget ui-state-default ui-corner-all'; - - editor.dialog({ - buttons: [ - { - text: 'OK', - class: 'button -primary', - click() { - self.copyFromDialog(); - $(this).dialog('close'); - }, - }, - { - text: 'Cancel', - class: 'button', - click() { - self.cancelEdit(); - $(this).dialog('close'); - }, - }, - ], - close(e:any, ui:any) { - if (e.type === 'click' || (e.type === 'keydown' && e.key === 'Escape')) { - self.cancelEdit(); - } - }, - dialogClass: `${this.getType().toLowerCase()}_editor_dialog`, - modal: true, - position: { my: 'center', at: 'center', of: window }, - resizable: false, - title: (this.isNew() ? this.newDialogTitle() : this.editDialogTitle()), - }); - editor.find('.editor').first().focus(); - $('.button').removeClass(baseClasses); - $('.ui-icon-closethick').prop('title', 'close'); - }, - - edit() { - const editor = this.getEditor(); - const self = this; - let maxTabIndex = 0; - - $('.stories .editors .editor').each(function (index) { - let value; - - // @ts-expect-error TS(2345): Argument of type 'string | undefined' is not assig... Remove this comment to see the full error message - value = parseInt($(this).attr('tabindex'), 10); - - if (maxTabIndex < value) { - maxTabIndex = value; - } - }); - - if (!editor.hasClass('permanent')) { - this.$.find('.editable').each(function (this:any, index:any) { - const field = $(this); - const fieldId = field.attr('field_id'); - const fieldName = field.attr('fieldname'); - const fieldLabel = field.attr('fieldlabel'); - // @ts-expect-error TS(2345): Argument of type 'string | undefined' is not assig... Remove this comment to see the full error message - const fieldOrder = parseInt(field.attr('fieldorder'), 10); - const fieldEditable = field.attr('fieldeditable') || 'true'; - const fieldType = field.attr('fieldtype') || 'input'; - let typeId; - let statusId; - let input:any; - - if (fieldType === 'select') { - // Special handling for status_id => they are dependent of type_id - if (fieldName === 'status_id') { - typeId = $.trim(self.$.find('.type_id .v').html()); - // when creating stories we need to query the select directly - if (typeId === '') { - typeId = $('#type_id_options').val(); - } - statusId = $.trim(self.$.find('.status_id .v').html()); - input = self.findFactory(typeId, statusId, fieldName); - } else if (fieldName === 'type_id') { - input = $(`#${fieldName}_options`).clone(true); - // if the type changes the status dropdown has to be modified - input.change(function () { - // @ts-expect-error TS(2683): 'this' implicitly has type 'any' because it does n... Remove this comment to see the full error message - typeId = $(this).val(); - statusId = $.trim(self.$.find('.status_id .v').html()); - let newInput = self.findFactory(typeId, statusId, 'status_id'); - newInput = self.prepareInputFromFactory(newInput, fieldId, 'status_id', fieldOrder, maxTabIndex); - // @ts-expect-error TS(2683): 'this' implicitly has type 'any' because it does n... Remove this comment to see the full error message - newInput = self.replaceStatusForNewType(input, newInput, $(this).parent().find('.status_id').val(), editor); - }); - } else { - input = $(`#${fieldName}_options`).clone(true); - } - } else { - input = $(document.createElement(fieldType)); - } - - input = self.prepareInputFromFactory(input, fieldId, fieldName, fieldOrder, maxTabIndex, fieldEditable); - - // Copy the value in the field to the input element - input.val(fieldType === 'select' ? field.children('.v').first().text() : field.text()); - - // Record in the model's root element which input field had the last focus. We will - // use this information inside RB.Model.refresh() to determine where to return the - // focus after the element has been refreshed with info from the server. - input.focus(function (this:any) { - self.$.data('focus', $(this).attr('name')); - }); - - input.blur(() => { - self.$.data('focus', ''); - }); - - $('