Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<%#-- 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::OpenProject::DangerDialog.new(title:, test_selector: TEST_SELECTOR, form_arguments:)) do |dialog|
dialog.with_confirmation_message do |message|
message.with_heading(tag: :h2) { title }
message.with_description_content(details)
end
end
%>
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# 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 BacklogBucketDestroyModalComponent < ApplicationComponent
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers

TEST_SELECTOR = "backlog-bucket-destroy-modal-dialog"

attr_reader :backlog_bucket

def initialize(backlog_bucket:)
super()
@backlog_bucket = backlog_bucket
end

private

def title
t(".title")
end

def details
t(".details", name: backlog_bucket.name)
end

def form_arguments
{
action: project_backlogs_backlog_bucket_path(backlog_bucket.project,
backlog_bucket,
helpers.all_backlogs_params),
method: :delete
}
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,8 @@ See COPYRIGHT and LICENSE files for more details.
id: dom_target(backlog_bucket, :menu, :delete_backlog_bucket),
label: t(".action_menu.delete_backlog_bucket"),
scheme: :danger,
href: project_backlogs_backlog_bucket_path(project, backlog_bucket, all_backlogs_params),
form_arguments: {
method: :delete,
data: { turbo_confirm: t("text_are_you_sure") }
}
href: destroy_dialog_project_backlogs_backlog_bucket_path(project, backlog_bucket, all_backlogs_params),
content_arguments: { data: { controller: "async-dialog" } }
) do |item|
item.with_leading_visual_icon(icon: :trash)
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@
#++

module WorkPackages
# Contract used for moving work packages to the product backlog (sprint = nil)
# at the end of a sprint. It does not enforce permissions as this change is
# carried out in the background.
# Contract used for moving work packages to the product backlog (sprint = nil, backlog_bucket = nil):
# * at the end of a sprint
# * upon bucket deletion
# It does not enforce permissions as this change is carried out in the background.
class MoveToBacklogContract < ModelContract
attribute :sprint
attribute :backlog_bucket
attribute :position
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class BacklogBucketsController < BaseController
include OpTurbo::ComponentStream

before_action :check_feature_flag
before_action :find_backlog_bucket, only: %i[edit_dialog update destroy]
before_action :find_backlog_bucket, only: %i[edit_dialog destroy_dialog update destroy]

def new_dialog
backlog_bucket = Agile::BacklogBucket.new(project: @project)
Expand All @@ -45,6 +45,10 @@ def edit_dialog
respond_with_dialog Backlogs::NewBacklogBucketDialogComponent.new(backlog_bucket: @backlog_bucket, state: :edit)
end

def destroy_dialog
respond_with_dialog Backlogs::BacklogBucketDestroyModalComponent.new(backlog_bucket: @backlog_bucket)
end

def create
call = ::BacklogBuckets::CreateService
.new(user: current_user)
Expand Down
15 changes: 15 additions & 0 deletions modules/backlogs/app/services/backlog_buckets/delete_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,19 @@
#++

class BacklogBuckets::DeleteService < BaseServices::Delete
private

def after_validate(service_call)
move_to_backlog.each { |result| service_call.add_dependent!(result) }

service_call
end

def move_to_backlog
model.work_packages.order(position: :asc).map do |wp|
WorkPackages::UpdateService
.new(user:, model: wp, contract_class: WorkPackages::MoveToBacklogContract)
.call(backlog_bucket: nil)
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def call
SELECT
id,
ROW_NUMBER() OVER (
PARTITION BY project_id, sprint_id
PARTITION BY project_id, backlog_bucket_id, sprint_id
ORDER BY position, created_at
) AS new_position
FROM work_packages
Expand Down
4 changes: 4 additions & 0 deletions modules/backlogs/config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ en:
blankslate_title: "Backlog bucket is empty"
blankslate_description: "Drag items here to add them."

backlog_bucket_destroy_modal_component:
title: "Delete backlog bucket?"
details: "The backlog bucket '%{name}' will be deleted and all work packages will be moved to the backlog inbox. No work package will be deleted."

backlog_bucket_item_component:
label_actions: "Work package actions"

Expand Down
1 change: 1 addition & 0 deletions modules/backlogs/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@

member do
get :edit_dialog
get :destroy_dialog
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ class RepositionWorkPackages < ActiveRecord::Migration[8.1]
def change
reversible do |direction|
direction.up do
# Copied 1:1 from modules/backlogs/app/services/work_packages/rebuild_positions_service.rb.
# The service could also have been called. But this way, there is no dependency between the two.
# Used to be copied 1:1 from modules/backlogs/app/services/work_packages/rebuild_positions_service.rb.
# In the meantime, the implementation of the service changed to accommodate backlog buckets.
# But those did not exist at the time this migration represents.
execute <<~SQL.squish
UPDATE work_packages
SET position = mapping.new_position
Expand Down
2 changes: 1 addition & 1 deletion modules/backlogs/lib/open_project/backlogs/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def self.settings
require: :member

permission :create_sprints,
{ "backlogs/backlog_buckets": %i[new_dialog create edit_dialog update destroy],
{ "backlogs/backlog_buckets": %i[new_dialog create edit_dialog update destroy_dialog destroy],
"backlogs/sprints": %i[new_dialog refresh_form create edit_dialog update] },
permissible_on: :project,
require: :member,
Expand Down
16 changes: 9 additions & 7 deletions modules/backlogs/spec/features/backlog_buckets/delete_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,11 @@
end
shared_let(:bucket) { create(:backlog_bucket, project:, name: "Deprecated bucket") }

shared_let(:bucket_wp1) { create(:work_package, project:, backlog_bucket: bucket, position: 1) }
shared_let(:bucket_wp2) { create(:work_package, project:, backlog_bucket: bucket, position: 2) }
shared_let(:inbox_wp1) { create(:work_package, project:) }
shared_let(:inbox_wp2) { create(:work_package, project:) }

shared_let(:bucket_wp1) { create(:work_package, project:, backlog_bucket: bucket) }
shared_let(:bucket_wp2) { create(:work_package, project:, backlog_bucket: bucket) }

let(:backlogs_page) { Pages::Backlog.new(project) }

Expand All @@ -57,15 +60,14 @@
backlogs_page.visit!
backlogs_page.expect_bucket_names_in_order("Deprecated bucket")

accept_confirm do
sleep 0.5
backlogs_page.click_in_backlog_bucket_menu(bucket, "Delete backlog bucket")
end
backlogs_page.click_in_backlog_bucket_menu(bucket, "Delete backlog bucket")

backlogs_page.expect_and_confirm_backlog_bucket_delete_modal

expect_and_dismiss_flash type: :success, exact_message: "Successful deletion."
backlogs_page.expect_no_backlog_bucket(bucket)

backlogs_page.expect_work_packages_in_backlog_inbox_in_order(work_packages: [bucket_wp1, bucket_wp2])
backlogs_page.expect_work_packages_in_backlog_inbox_in_order(work_packages: [inbox_wp1, inbox_wp2, bucket_wp1, bucket_wp2])

expect(Agile::BacklogBucket.where(id: bucket.id)).to be_empty
expect(bucket_wp1.reload.backlog_bucket_id).to be_nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,9 @@
backlogs_page.expect_no_inbox_show_more

# Delete backlog bucket
accept_confirm do
sleep 0.5
backlogs_page.click_in_backlog_bucket_menu(bucket, "Delete backlog bucket")
end
backlogs_page.click_in_backlog_bucket_menu(bucket, "Delete backlog bucket")

backlogs_page.expect_and_confirm_backlog_bucket_delete_modal

expect_and_dismiss_flash type: :success, exact_message: "Successful deletion."
backlogs_page.expect_no_inbox_show_more
Expand Down
Loading
Loading