diff --git a/app/components/workflows/matrix_form_component.html.erb b/app/components/workflows/matrix_form_component.html.erb new file mode 100644 index 00000000000..e37cff823b3 --- /dev/null +++ b/app/components/workflows/matrix_form_component.html.erb @@ -0,0 +1,88 @@ +<%#-- 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 do %> + <% if @statuses.any? %> + <%= form_tag( + { action: :update }, + id: "workflow_form", + method: :patch, + autocomplete: "off", + data: { + controller: "admin--workflow-checkbox-state", + "admin--workflow-checkbox-state-has-status-changes-value": @has_status_changes, + turbo_temporary: true + } + ) do %> + <%= hidden_field_tag "type_id", @type.id %> + <%= hidden_field_tag "role_id", @role.id %> + <%= hidden_field_tag "tab", @tab %> + + <%= helpers.render_tabs helpers.workflow_tabs(@type) %> + +
+ <%= + render Primer::Beta::Button.new(scheme: :primary, type: :submit, mt: 3, ml: 3, mb: 4) do + t(:button_save) + end + %> +
+ + <%= + render( + Primer::OpenProject::FeedbackDialog.new( + title: t("admin.workflows.leave_confirmation.title"), + data: { + "admin--workflow-checkbox-state-target": "confirmationDialog", + } + ) + ) do |dialog| + dialog.with_feedback_message(icon_arguments: { icon: :none }) do |message| + message.with_heading(tag: :h2) { t("admin.workflows.leave_confirmation.title") } + message.with_description_content(t("admin.workflows.leave_confirmation.description")) + end + + dialog.with_footer do + component_collection do |footer| + footer.with_component( + Primer::Beta::Button.new(scheme: :danger, data: { "admin--workflow-checkbox-state-target": "ignoreButton" }) + ) { t("admin.workflows.leave_confirmation.ignore") } + + footer.with_component( + Primer::Beta::Button.new(scheme: :primary, data: { "admin--workflow-checkbox-state-target": "saveButton" }) + ) { t("admin.workflows.leave_confirmation.save") } + end + end + end + %> + <% end %> + <% else %> + <%= render Workflows::BlankslateComponent.new(role: @role, type: @type, tab: @tab) %> + <% end %> +<% end %> diff --git a/app/components/workflows/matrix_form_component.rb b/app/components/workflows/matrix_form_component.rb new file mode 100644 index 00000000000..965410005a5 --- /dev/null +++ b/app/components/workflows/matrix_form_component.rb @@ -0,0 +1,45 @@ +# 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 Workflows + class MatrixFormComponent < ApplicationComponent + include OpTurbo::Streamable + include OpPrimer::ComponentHelpers + + def initialize(tab:, role:, type:, statuses:, has_status_changes:) + super + @tab = tab + @role = role + @type = type + @statuses = statuses + @has_status_changes = has_status_changes + end + end +end diff --git a/app/controllers/workflows_controller.rb b/app/controllers/workflows_controller.rb index a9625b12839..60785e28d85 100644 --- a/app/controllers/workflows_controller.rb +++ b/app/controllers/workflows_controller.rb @@ -56,10 +56,8 @@ class WorkflowsController < ApplicationController end def update # rubocop:disable Metrics/AbcSize - tab = params[:tab] || "always" - call = Workflows::BulkUpdateService - .new(role: @role, type: @type, tab:) + .new(role: @role, type: @type, tab: current_tab) .call(permitted_status_params) if call.success? @@ -67,6 +65,18 @@ class WorkflowsController < ApplicationController message: I18n.t(:notice_successful_update), scheme: :success ) + if statuses_for_form.empty? + # Need to replace with the blankslate. + update_via_turbo_stream( + component: Workflows::MatrixFormComponent.new( + tab: current_tab, + role: @role, + type: @type, + statuses: @statuses, + has_status_changes: false + ) + ) + end else render_flash_message_via_turbo_stream( message: I18n.t(:notice_unsuccessful_update), diff --git a/app/views/workflows/edit.html.erb b/app/views/workflows/edit.html.erb index af0903f145d..58ffcec74b5 100644 --- a/app/views/workflows/edit.html.erb +++ b/app/views/workflows/edit.html.erb @@ -36,62 +36,6 @@ See COPYRIGHT and LICENSE files for more details. <%= turbo_frame_tag "workflow-table", data: { turbo_cache: false } do %> <%= render Workflows::EditSubHeaderComponent.new(tab: @current_tab, current_role: @role, type: @type, available_roles: @roles, status_ids: @statuses.pluck(:id)) %> - <% if @statuses.any? %> - <%= form_tag( - { action: :update }, - id: "workflow_form", - method: :patch, - autocomplete: "off", - data: { - controller: "admin--workflow-checkbox-state", - "admin--workflow-checkbox-state-has-status-changes-value": @has_status_changes, - turbo_temporary: true - } - ) do %> - <%= hidden_field_tag "type_id", @type.id %> - <%= hidden_field_tag "role_id", @role.id %> - <%= hidden_field_tag "tab", @current_tab %> - - <%= render_tabs workflow_tabs(@type) %> - -
- <%= - render Primer::Beta::Button.new(scheme: :primary, type: :submit, mt: 3, ml: 3, mb: 4) do - t(:button_save) - end - %> -
- - <%= - render( - Primer::OpenProject::FeedbackDialog.new( - title: t("admin.workflows.leave_confirmation.title"), - data: { - "admin--workflow-checkbox-state-target": "confirmationDialog", - } - ) - ) do |dialog| - dialog.with_feedback_message(icon_arguments: { icon: :none }) do |message| - message.with_heading(tag: :h2) { t("admin.workflows.leave_confirmation.title") } - message.with_description_content(t("admin.workflows.leave_confirmation.description")) - end - - dialog.with_footer do - component_collection do |footer| - footer.with_component( - Primer::Beta::Button.new(scheme: :danger, data: { "admin--workflow-checkbox-state-target": "ignoreButton" }) - ) { t("admin.workflows.leave_confirmation.ignore") } - - footer.with_component( - Primer::Beta::Button.new(scheme: :primary, data: { "admin--workflow-checkbox-state-target": "saveButton" }) - ) { t("admin.workflows.leave_confirmation.save") } - end - end - end - %> - <% end %> - <% else %> - <%= render Workflows::BlankslateComponent.new(role: @role, type: @type, tab: @current_tab) %> - <% end %> + <%= render Workflows::MatrixFormComponent.new(tab: @current_tab, role: @role, type: @type, statuses: @statuses, has_status_changes: @has_status_changes) %> <% end %> <% end %> diff --git a/spec/features/workflows/edit_spec.rb b/spec/features/workflows/edit_spec.rb index e07473d92c2..8ebd1f049b3 100644 --- a/spec/features/workflows/edit_spec.rb +++ b/spec/features/workflows/edit_spec.rb @@ -396,7 +396,7 @@ RSpec.describe "Workflow edit", :js do it "shows a confirmation dialog when changing roles after adding a status" do add_status_via_dialog(statuses[2]) - expect(page).to have_no_field workflow_checkbox(0, 2) + expect(page).to have_field workflow_checkbox(0, 2) click_button role.name click_link other_role.name @@ -650,11 +650,96 @@ RSpec.describe "Workflow edit", :js do end end - it "allows navigating to any Copy page", :js do - within ".PageHeader-actions" do - click_on "Copy" + context "with copy dialog" do + it "allows navigating to any Copy page", :js do + within ".PageHeader-actions" do + click_on "Copy" + end + + expect(page).to have_dialog "Copy workflow" end - expect(page).to have_heading "Copy workflow" + context "with unsaved checkbox" do + it "loses unsaved checkbox changes when clicking on copy and ignoring" do + within "#workflow_form_always" do + check workflow_checkbox(1, 0) + end + + click_link "Copy" + + within_dialog "Save changes before continuing?" do + click_button "Ignore changes" + end + + within "#workflow_form_always" do + expect(page).to have_field workflow_checkbox(1, 0), checked: false + end + expect(page).to have_dialog "Copy workflow" + end + + it "saves changes and switches to the new role when clicking 'Save changes and continue'" do + within "#workflow_form_always" do + check workflow_checkbox(1, 0) + end + + click_link "Copy" + + within_dialog "Save changes before continuing?" do + click_button "Save changes and continue" + end + + expect_flash(message: "Successful update.") + + expect(Workflow.exists?(role_id: role.id, type_id: type.id, + old_status_id: statuses[1].id, new_status_id: statuses[0].id, + author: false, assignee: false)).to be true + + expect(page).to have_dialog "Copy workflow" + end + + it "keeps unsaved changes and stays on the same role when closing the dialog via 'X'" do + within "#workflow_form_always" do + check workflow_checkbox(1, 0) + end + + click_link "Copy" + + within_dialog "Save changes before continuing?" do + find(".close-button").click + end + + expect(page).to have_no_dialog("Save changes before continuing?") + + within "#workflow_form_always" do + expect(page).to have_field workflow_checkbox(1, 0), checked: true + end + + expect(page).to have_no_dialog "Copy workflow" + end + end + + context "with unsaved new status" do + it "shows a confirmation dialog when copying after adding a status" do + add_status_via_dialog(statuses[2]) + expect(page).to have_field workflow_checkbox(0, 2) + + click_link "Copy" + + expect(page).to have_dialog("Save changes before continuing?") + end + + it "reverts the added status on changes ignored" do + add_status_via_dialog(statuses[2]) + expect(page).to have_field workflow_checkbox(0, 2) + + click_link "Copy" + + within_dialog "Save changes before continuing?" do + click_button "Ignore changes" + end + + expect(page).to have_no_field workflow_checkbox(0, 2) + end + end end end