Merge workflow copy modes into one unified form. wp/72383

This commit is contained in:
David F
2026-03-31 19:33:59 +02:00
parent f24e41fd83
commit 004876b177
26 changed files with 509 additions and 554 deletions
@@ -1,39 +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 Workflows::PageHeaders
class CopyComponent < BaseComponent
options :title
def page_breadcrumb
{ href: workflows_path, text: t(:label_workflow_plural) }
end
end
end
@@ -42,33 +42,19 @@ module Workflows::PageHeaders
type.name
end
def add_action_buttons(header) # rubocop:disable Metrics/AbcSize
def add_action_buttons(header)
header.with_action_button(
data: { controller: "async-dialog" },
tag: :a,
mobile_icon: :copy,
mobile_label: t(:label_copy_workflow_from_type),
mobile_label: t(:button_copy),
size: :medium,
href: new_workflow_copy_from_type_path(type),
aria: { label: helpers.t(:label_copy_workflow_from_type) },
title: helpers.t(:label_copy_workflow_from_type)
href: new_workflow_copy_path(type, source_role_id: role&.id),
aria: { label: helpers.t(:button_copy) },
title: helpers.t(:button_copy)
) do |button|
button.with_leading_visual_icon(icon: :copy)
t(:label_copy_workflow_from_type)
end
header.with_action_button(
data: { controller: "async-dialog" },
tag: :a,
mobile_icon: :copy,
mobile_label: t(:label_copy_workflow_from_role),
size: :medium,
href: new_workflow_copy_from_role_path(type, source_role_id: role&.id),
aria: { label: helpers.t(:label_copy_workflow_from_role) },
title: helpers.t(:label_copy_workflow_from_role)
) do |button|
button.with_leading_visual_icon(icon: :copy)
t(:label_copy_workflow_from_role)
t(:button_copy)
end
end
@@ -54,16 +54,10 @@ See COPYRIGHT and LICENSE files for more details.
item.with_leading_visual_icon(icon: :pencil)
end
menu.with_divider
menu.with_item(label:t("label_copy_workflow_from_type"),
menu.with_item(label: t("button_copy"),
content_arguments: { data: { controller: "async-dialog" }},
tag: :a,
href: new_workflow_copy_from_type_path(type)) do |item|
item.with_leading_visual_icon(icon: :copy)
end
menu.with_item(label:t("label_copy_workflow_from_role"),
content_arguments: { data: { controller: "async-dialog" }},
tag: :a,
href: new_workflow_copy_from_role_path(type)) do |item|
href: new_workflow_copy_path(type)) do |item|
item.with_leading_visual_icon(icon: :copy)
end
end
@@ -37,10 +37,7 @@ class Workflows::Copies::FromRolesController < ApplicationController
before_action :set_source_type
before_action :set_source_role
before_action :set_all_roles
before_action :set_target_roles, only: %i[create]
def new; end
before_action :set_target_roles
def create
if @source_type.nil? || @source_role.nil?
@@ -74,10 +71,6 @@ class Workflows::Copies::FromRolesController < ApplicationController
@source_role = eligible_roles.find_by(id: params[:source_role_id])
end
def set_all_roles
@all_roles = eligible_roles
end
def set_target_roles
@target_roles = eligible_roles.find_by(id: params[:target_role_ids])
end
@@ -36,10 +36,7 @@ class Workflows::Copies::FromTypesController < ApplicationController
before_action :require_admin
before_action :set_source_type
before_action :set_other_types
before_action :set_target_type, only: %i[create]
def new; end
before_action :set_target_type
def create
if @source_type.nil?
@@ -71,10 +68,6 @@ class Workflows::Copies::FromTypesController < ApplicationController
@source_type = ::Type.find(params[:workflow_type_id])
end
def set_other_types
@other_types = ::Type.where.not(id: @source_type.id).order(:position)
end
def set_target_type
@target_type = ::Type.find(params[:target_type_id])
end
@@ -27,25 +27,40 @@
#
# See COPYRIGHT and LICENSE files for more details.
#++
#
require "spec_helper"
RSpec.describe Workflows::Copies::FromTypeForm, type: :forms do
include_context "with rendered form"
class Workflows::CopiesController < ApplicationController
include OpTurbo::ComponentStream
let(:model) { false }
let(:params) { { source_type:, other_types: } }
let(:source_type) { create(:type) }
let(:other_types) { create_list(:type, 4) }
layout "admin"
it "renders the Target type select list" do
expect(page).to have_select "Target type", required: true do |select|
options_text = select.all("option").map(&:text)
expect(options_text).to match_array(other_types.map(&:name))
end
before_action :require_admin
before_action :set_source_type
before_action :set_source_role
before_action :set_other_types
before_action :set_all_roles
def new; end
private
def set_source_type
@source_type = ::Type.find(params[:workflow_type_id])
end
it "renders submit button" do
expect(page).to have_button "Copy", class: "Button--primary"
def set_source_role
@source_role = eligible_roles.find_by(id: params[:source_role_id])
end
def set_other_types
@other_types = ::Type.where.not(id: @source_type.id).order(:position)
end
def set_all_roles
@all_roles = eligible_roles
end
def eligible_roles
@eligible_roles ||= Workflow.eligible_roles
end
end
+119
View File
@@ -0,0 +1,119 @@
# 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 Workflows::Copies::Form < ApplicationForm
def initialize(source_type:, source_role:, other_types:, all_roles:, append_to: nil)
super()
@source_type = source_type
@source_role = source_role
@other_types = other_types
@all_roles = all_roles
@append_to = append_to
end
form do |copy|
another_type_at_first = @source_role.nil?
copy.advanced_radio_button_group(name: :mode) do |radio_group|
radio_group.radio_button(
value: "from_type",
checked: another_type_at_first,
label: helpers.t("workflows.copies.form.mode.from_type.label"),
caption: helpers.t("workflows.copies.form.mode.from_type.caption"),
data: {
target_name: "mode",
"show-when-value-selected-target": "cause"
}
)
radio_group.radio_button(
value: "from_role",
checked: !another_type_at_first,
label: helpers.t("workflows.copies.form.mode.from_role.label"),
caption: helpers.t("workflows.copies.form.mode.from_role.caption"),
data: {
target_name: "mode",
"show-when-value-selected-target": "cause"
}
)
end
copy.group(
hidden: !another_type_at_first,
data: {
target_name: "mode",
value: "from_type",
"show-when-value-selected-target": "effect"
}
) do |from_type|
target_label = helpers.t("workflows.copies.form.target_type_id.label")
from_type.select_list(name: :target_type_id, label: target_label, required: true) do |target_list|
@other_types.each do |other_type|
target_list.option(label: other_type.name, value: other_type.id)
end
end
end
copy.group(
hidden: another_type_at_first,
data: {
target_name: "mode",
value: "from_role",
"show-when-value-selected-target": "effect"
}
) do |from_role|
source_label = helpers.t("workflows.copies.form.source_role_id.label")
required = another_type_at_first
disabled = !another_type_at_first
from_role.select_list(name: :source_role_id, label: source_label, required:, disabled:) do |source_role_list|
@all_roles.each do |role|
source_role_list.option(label: role.name, value: role.id, selected: role == @source_role)
end
end
from_role.autocompleter(
name: "target_role_ids",
required: true,
include_blank: false,
label: helpers.t("workflows.copies.form.target_role_ids.label"),
autocomplete_options: {
multiple: true,
decorated: true,
closeOnSelect: false,
appendTo: @append_to,
data: {
"test-selector": "target_roles_autocomplete"
}
}
) do |target_list|
@all_roles.each do |role|
target_list.option(label: role.name, value: role.id)
end
end
end
end
end
@@ -1,67 +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 Workflows::Copies::FromRoleForm < ApplicationForm
def initialize(source_type:, source_role:, all_roles:, append_to: nil)
super()
@source_type = source_type
@source_role = source_role
@all_roles = all_roles
@append_to = append_to
end
form do |copy|
source_label = helpers.t("workflows.copies.from_role_form.source_role")
copy.select_list(name: :source_role_id, label: source_label, required: true) do |source_role_list|
@all_roles.each do |role|
source_role_list.option(label: role.name, value: role.id, selected: role == @source_role)
end
end
copy.autocompleter(
name: "target_role_ids",
required: true,
include_blank: false,
label: helpers.t("workflows.copies.from_role_form.target_roles"),
autocomplete_options: {
multiple: true,
decorated: true,
closeOnSelect: false,
appendTo: @append_to,
data: {
"test-selector": "target_roles_autocomplete"
}
}
) do |target_list|
@all_roles.each do |role|
target_list.option(label: role.name, value: role.id)
end
end
end
end
@@ -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.
#++
class Workflows::Copies::FromTypeForm < ApplicationForm
def initialize(source_type:, other_types:)
super()
@source_type = source_type
@other_types = other_types
end
form do |copy|
target_label = helpers.t("workflows.copies.from_type_form.target_type")
copy.select_list(name: :target_type_id, label: target_label, required: true) do |target_list|
@other_types.each do |other_type|
target_list.option(label: other_type.name, value: other_type.id)
end
end
end
end
@@ -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.
++#%>
<% title = t(".title", source_type: @source_type.name) %>
<%= turbo_stream.dialog do
render(Primer::Alpha::Dialog.new(title:)) do |d|
d.with_header(variant: :large)
d.with_body do
settings_primer_form_with(url: workflow_copy_from_type_path(@source_type), id: "copy_from_type") do |f|
render(Primer::Alpha::Banner.new(scheme: :warning, mb: 4)) do |banner|
t(".warning")
end +
render(Workflows::Copies::FromTypeForm.new(f, source_type: @source_type, other_types: @other_types))
end
end
d.with_footer do
render(Primer::Beta::Button.new(
scheme: :primary,
tag: :button,
type: :submit,
form: "copy_from_type",
data: { turbo: true }
)) do
t(:button_copy)
end
end
end
end
%>
@@ -26,27 +26,51 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>
<% title = t(".title", source_type: @source_type.name) %>
<% dialog_id = "copy_from_type_dialog" %>
<%= turbo_stream.dialog do
render(Primer::Alpha::Dialog.new(title:, size: :medium_portrait, id: dialog_id)) do |d|
title = t(".title", source_type: @source_type.name)
dialog_id = "copy_from_type_dialog"
another_type_at_first = @source_role.nil?
render(Primer::Alpha::Dialog.new(title:, size: :large, id: dialog_id, data: { controller: "show-when-value-selected" })) do |d|
d.with_header(variant: :large)
d.with_body(classes: "workflow-status-dialog-body") do
settings_primer_form_with(url: workflow_copy_from_role_path(@source_type), id: "copy_from_role") do |f|
render(Primer::Alpha::Banner.new(scheme: :warning, mb: 4)) do |banner|
t(".warning")
end +
render(Workflows::Copies::FromRoleForm.new(f, source_type: @source_type, source_role: @source_role, all_roles: @all_roles, append_to: "##{dialog_id}"))
settings_primer_form_with(id: "copy_form") do |f|
render(Workflows::Copies::Form.new(f, source_type: @source_type, source_role: @source_role, other_types: @other_types, all_roles: @all_roles, append_to: "##{dialog_id}"))
end
end
d.with_footer do
render(Primer::Beta::Button.new(data: { "close-dialog-id": dialog_id })) { "Cancel" } +
render(Primer::Beta::Button.new(
hidden: !another_type_at_first,
scheme: :primary,
tag: :button,
type: :submit,
form: "copy_from_role",
data: { turbo: true }
form: "copy_form",
formaction: workflow_copy_from_type_path(@source_type),
data: {
target_name: "mode",
value: "from_type",
"show-when-value-selected-target": "effect",
turbo: true
}
)) do
t(:button_copy)
end +
render(Primer::Beta::Button.new(
hidden: another_type_at_first,
scheme: :primary,
tag: :button,
type: :submit,
form: "copy_form",
formaction: workflow_copy_from_role_path(@source_type, source_role_id: @source_role&.id),
data: {
target_name: "mode",
value: "from_role",
"show-when-value-selected-target": "effect",
turbo: true
}
)) do
t(:button_copy)
end
+16 -13
View File
@@ -1393,19 +1393,22 @@ en:
workflows:
copies:
from_role_form:
source_role: "Source role"
target_roles: "Target roles"
from_roles:
new:
title: "Copy workflow of \"%{source_type}\" between roles"
warning: "This action will replace existing workflows of the selected target roles."
from_type_form:
target_type: "Target type"
from_types:
new:
title: "Copy workflow from \"%{source_type}\" to another type"
warning: "This action will replace existing workflows of all the roles of the selected target type."
form:
source_role_id:
label: "Source role"
target_role_ids:
label: "Target roles"
target_type_id:
label: "Target type"
mode:
from_role:
label: "Copy to other roles"
caption: "Copy the current workflow to one or more roles inside the same work package type. If the selected role has already a workflow the current one will be overwritten."
from_type:
label: "Copy to another type"
caption: "Copy the current workflow to another work packages type. If the selected type has already a workflow the current one will be overwritten."
new:
title: "Copy workflow of \"%{source_type}\""
form:
matrix_caption: "Workflow matrix"
matrix_caption_assignee: "Workflow matrix for assignee"
+5 -3
View File
@@ -804,9 +804,11 @@ Rails.application.routes.draw do
end
resources :workflows, only: %i[index edit update], param: :type_id do
resource :copy, only: %i[], module: "workflows/copies" do
resource :from_type, only: %i[new create]
resource :from_role, only: %i[new create]
resource :copy, only: %i[new], module: :workflows do
scope module: :copies do
resource :from_type, only: %i[create]
resource :from_role, only: %i[create]
end
end
collection do
get :status_dialog
+1 -1
View File
@@ -137,7 +137,7 @@ primer_form_with(
data: {
target_name: "frequency",
value: "foo"
"show-when-value-selected-target": "cause"
"show-when-value-selected-target": "effect"
}
end
@@ -65,14 +65,12 @@ RSpec.describe Workflows::TableComponent, type: :component do
expect(rendered_component).to have_css("li", text: types.first.name) do |row|
expect(row).to have_link(types.first.name, href: edit_workflow_path(types.first))
expect(row).to have_link("Edit", href: edit_workflow_path(types.first))
expect(row).to have_link("Copy to another type", href: new_workflow_copy_from_type_path(types.first))
expect(row).to have_link("Copy to other roles", href: new_workflow_copy_from_role_path(types.first))
expect(row).to have_link("Copy", href: new_workflow_copy_path(types.first))
end
expect(rendered_component).to have_css("li", text: types.second.name) do |row|
expect(row).to have_link(types.second.name, href: edit_workflow_path(types.second))
expect(row).to have_link("Edit", href: edit_workflow_path(types.second))
expect(row).to have_link("Copy to another type", href: new_workflow_copy_from_type_path(types.second))
expect(row).to have_link("Copy to other roles", href: new_workflow_copy_from_role_path(types.second))
expect(row).to have_link("Copy", href: new_workflow_copy_path(types.second))
end
end
end
@@ -53,58 +53,12 @@ RSpec.describe Workflows::Copies::FromRolesController do
build_stubbed_list(:project_role, 2)
end
let!(:source_role) { nil }
before do
allow(eligible_roles).to receive(:find_by).and_return(source_role)
end
current_user { build_stubbed(:admin) }
describe "#new" do
let(:params) do
{ workflow_type_id: source_type.id.to_s, source_role_id: source_role&.id }
end
before do
get :new, params:
end
it "is a success" do
expect(response)
.to have_http_status(:ok)
end
it "renders the correct template" do
expect(response)
.to render_template :new
end
it "assigns the source type" do
expect(assigns[:source_type])
.to eq source_type
end
it "does not assign any source role" do
expect(assigns[:source_role])
.to be_nil
end
it "assigns the eligible roles" do
expect(assigns[:all_roles])
.to match_array(all_roles)
end
describe "when the source role is specified" do
let!(:source_role) { all_roles.sample }
it "assigns the source role" do
expect(assigns[:source_role])
.to eq source_role
end
end
end
describe "#create" do
let!(:source_role) { all_roles.sample }
let!(:target_roles) do
@@ -53,36 +53,6 @@ RSpec.describe Workflows::Copies::FromTypesController do
current_user { build_stubbed(:admin) }
describe "#new" do
let(:params) do
{ workflow_type_id: source_type.id.to_s }
end
before do
get :new, params:
end
it "is a success" do
expect(response)
.to have_http_status(:ok)
end
it "renders the correct template" do
expect(response)
.to render_template :new
end
it "assigns the source_type" do
expect(assigns[:source_type])
.to eq source_type
end
it "assigns the source_role" do
expect(assigns[:other_types])
.to match_array(other_types)
end
end
describe "#create" do
let!(:target_type) do
other_types.last.tap do |stub|
@@ -0,0 +1,123 @@
# 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 Workflows::CopiesController do
let!(:source_type) do
build_stubbed(:type) do |stub|
allow(Type)
.to receive(:find)
.with(stub.id.to_s)
.and_return(stub)
end
end
let!(:other_types) do
build_stubbed_list(:type, 2).tap do |stubs|
where_double = instance_double(ActiveRecord::QueryMethods::WhereChain)
not_double = instance_double(ActiveRecord::Relation)
allow(Type).to receive(:where).and_return(where_double)
allow(where_double).to receive(:not).and_return(not_double)
allow(not_double).to receive(:order).and_return(stubs)
end
end
let!(:eligible_roles) do
instance_double(ActiveRecord::Relation, to_a: all_roles).tap do |relation|
allow(Role)
.to receive(:where)
.with(type: ProjectRole.name)
.and_return(relation)
end
end
let!(:all_roles) do
build_stubbed_list(:project_role, 2)
end
let!(:source_role) { nil }
before do
allow(eligible_roles).to receive(:find_by).and_return(source_role)
end
current_user { build_stubbed(:admin) }
describe "#new" do
let(:params) do
{ workflow_type_id: source_type.id.to_s, source_role_id: source_role&.id }
end
before do
get :new, params:, format: :turbo_stream
end
it "is a success" do
expect(response)
.to have_http_status(:ok)
end
it "renders the correct template" do
expect(response)
.to render_template :new
end
it "assigns the source type" do
expect(assigns[:source_type])
.to eq source_type
end
it "assigns the other types" do
expect(assigns[:other_types])
.to match_array(other_types)
end
it "does not assign any source role" do
expect(assigns[:source_role])
.to be_nil
end
it "assigns the eligible roles" do
expect(assigns[:all_roles])
.to match_array(all_roles)
end
describe "when the source role is specified" do
let!(:source_role) { all_roles.sample }
it "assigns the source role" do
expect(assigns[:source_role])
.to eq source_role
end
end
end
end
@@ -30,7 +30,7 @@
require "spec_helper"
RSpec.describe "Workflow copy from role" do
RSpec.describe "Workflow copy from role", :js do
let!(:type) { create(:type) }
let!(:roles) { create_list(:project_role, 3) }
let(:admin) { create(:admin) }
@@ -39,45 +39,42 @@ RSpec.describe "Workflow copy from role" do
current_user { admin }
before do
visit new_workflow_copy_from_role_path(type)
shared_examples "a copy-to-other-roles dialog" do |with_source_role:|
it "permits to select a source role and target source roles" do
unless with_source_role
choose "Copy to other roles"
expect(page).to have_select("Source role", text: roles.first.name)
select(roles.last.name, from: "Source role")
end
target_roles_autocompleter.select_option roles.first.name, roles.second.name
target_roles_autocompleter.close_autocompleter
click_button "Copy"
expect(page).to have_css(".flash-success", text: "Successful update.")
end
end
it "permits to select a source role and target source roles", :js do
expect(page).to have_select("Source role", text: roles.first.name)
select(roles.last.name, from: "Source role")
describe "from the workflows index page" do
before do
visit workflows_path
within "li", text: type.name do
find("button[aria-haspopup=true]").click
click_link "Copy"
end
end
target_roles_autocompleter.select_option roles.first.name, roles.second.name
target_roles_autocompleter.close_autocompleter
click_button "Copy"
expect(page).to have_css(".flash-success", text: "Successful update.")
it_behaves_like "a copy-to-other-roles dialog", with_source_role: false
end
it "allows to go back to Workflow index page" do
visit workflows_path
within "li", text: type.name do
click_link "Copy to other roles"
describe "from the workflows edit page" do
before do
visit edit_workflow_path(type)
click_link "Copy"
end
within ".Banner--warning" do
click_link "Cancel"
end
expect(page).to have_heading "Workflow"
expect(page).to have_current_path(workflows_path)
end
it "allows to go back to Workflow edit page" do
visit edit_workflow_path(type)
click_link "Copy to other roles"
within ".Banner--warning" do
click_link "Cancel"
end
expect(page).to have_heading type.name
expect(page).to have_current_path(edit_workflow_path(type))
it_behaves_like "a copy-to-other-roles dialog", with_source_role: true
end
end
@@ -30,48 +30,46 @@
require "spec_helper"
RSpec.describe "Workflow copy from type" do
let(:types) { create_list(:type, 3) }
let(:type) { types.first }
RSpec.describe "Workflow copy from type", :js do
let!(:types) { create_list(:type, 3) }
let!(:type) { types.first }
let(:admin) { create(:admin) }
current_user { admin }
before do
visit new_workflow_copy_from_type_path(type)
shared_examples "a copy-to-another-type dialog" do |with_source_role:|
it "permits to select a target type" do
if with_source_role
choose "Copy to another type"
end
expect(page).to have_select("Target type", text: types.second.name)
select(types.last.name, from: "Target type")
click_button "Copy"
expect(page).to have_css(".flash-success", text: "Successful update.")
end
end
it "permits to select another type", :js do
expect(page).to have_select("Target type", text: types.second.name)
select(types.last.name, from: "Target type")
click_button "Copy"
describe "from the workflows index page" do
before do
visit workflows_path
within "li", text: type.name do
find("button[aria-haspopup=true]").click
click_link "Copy"
end
end
expect(page).to have_css(".flash-success", text: "Successful update.")
it_behaves_like "a copy-to-another-type dialog", with_source_role: false
end
it "allows to go back to Workflow index page" do
visit workflows_path
within "li", text: type.name do
click_link "Copy to another type"
describe "from the workflows edit page" do
before do
visit edit_workflow_path(type)
click_link "Copy"
end
within ".Banner--warning" do
click_link "Cancel"
end
expect(page).to have_heading "Workflow"
expect(page).to have_current_path(workflows_path)
end
it "allows to go back to Workflow edit page" do
visit edit_workflow_path(type)
click_link "Copy to another type"
within ".Banner--warning" do
click_link "Cancel"
end
expect(page).to have_heading type.name
expect(page).to have_current_path(edit_workflow_path(type))
it_behaves_like "a copy-to-another-type dialog", with_source_role: true
end
end
+8 -18
View File
@@ -372,24 +372,6 @@ RSpec.describe "Workflow edit" do
end
end
it "allows navigating to Workflow copy-from-type page" do
within ".PageHeader-actions" do
click_on "Copy to another type"
end
expect(page).to have_heading "Copy workflow"
expect(page).to have_current_path(new_workflow_copy_from_type_path(type))
end
it "allows navigating to Workflow copy-from-role page" do
within ".PageHeader-actions" do
click_on "Copy to other roles"
end
expect(page).to have_heading "Copy workflow"
expect(page).to have_current_path(new_workflow_copy_from_role_path(type, source_role_id: role.id))
end
context "with status dialog", :js do
before do
visit_workflow_edit(role:)
@@ -579,4 +561,12 @@ RSpec.describe "Workflow edit" do
expect(page).to have_text("Add statuses to start configuring workflows for this role")
end
end
it "allows navigating to any Copy page", :js do
within ".PageHeader-actions" do
click_on "Copy"
end
expect(page).to have_heading "Copy workflow"
end
end
+2 -18
View File
@@ -74,34 +74,18 @@ RSpec.describe "Workflows index" do
expect(page).to have_current_path(edit_workflow_path(some_type))
end
it "allows navigating to any copy-from-type page" do
it "allows navigating to any Copy page", :js do
expect(page).to have_heading("Workflows")
some_type = types.sample
within "ul.Box-list" do
within "li", text: some_type.name do
find("button[aria-haspopup=true]").click
click_link "Copy to another type"
click_link "Copy"
end
end
expect(page).to have_heading "Copy workflow"
expect(page).to have_current_path(new_workflow_copy_from_type_path(some_type))
end
it "allows navigating to any copy-from-role page" do
expect(page).to have_heading("Workflows")
some_type = types.sample
within "ul.Box-list" do
within "li", text: some_type.name do
find("button[aria-haspopup=true]").click
click_link "Copy to other roles"
end
end
expect(page).to have_heading "Copy workflow"
expect(page).to have_current_path(new_workflow_copy_from_role_path(some_type))
end
it "allows navigating to Workflow summary page" do
@@ -68,8 +68,9 @@ RSpec.describe "Configuring the workflow for work package sharing", :js,
# On the copy workflow form, select the already existing workflow for copying
within "li", text: type.name do
find("button[aria-haspopup=true]").click
click_link "Copy to other roles"
click_link "Copy"
end
choose "Copy to other roles"
select role.name, from: "source_role_id"
target_roles_autocompleter.select_option work_package_role.name
target_roles_autocompleter.close_autocompleter
+91
View File
@@ -0,0 +1,91 @@
# 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 Workflows::Copies::Form, type: :forms do
include_context "with rendered form"
let(:model) { false }
let(:params) { { source_type:, source_role:, other_types:, all_roles: } }
let(:source_type) { create(:type) }
let(:other_types) { create_list(:type, 4) }
let(:all_roles) { create_list(:project_role, 4) }
shared_examples "a copy form with conditional fields" do |another_type_at_first:|
it "renders radio buttons to choose the mode" do
expect(page).to have_field("Copy to another type", checked: another_type_at_first)
expect(page).to have_field("Copy to other roles", checked: !another_type_at_first)
end
it "renders the Target type select list" do
expect(page).to have_select "Target type", required: true, visible: another_type_at_first do |select|
options_text = select.all("option", visible: another_type_at_first).map(&:text)
expect(options_text).to match_array(other_types.map(&:name))
end
end
it "renders the Source role select list" do
required = another_type_at_first
disabled = visible = !another_type_at_first
expect(page).to have_select "Source role", required:, disabled:, visible: do |select|
options_text = select.all("option", visible: !another_type_at_first).map(&:text)
expect(options_text).to match_array(all_roles.map(&:name))
end
end
it "renders the Target roles autocompleter" do
data_attributes = "[data-test-selector=\"target_roles_autocomplete\"][data-multiple=\"true\"]"
expect(page).to have_css "opce-autocompleter#{data_attributes}", visible: !another_type_at_first do |autocompleter|
options_text = JSON.parse(autocompleter["data-items"]).map { |item| item["name"] }
expect(options_text).to match_array(all_roles.map(&:name))
end
end
end
describe "when the source role is not specified" do
let(:source_role) { nil }
it_behaves_like "a copy form with conditional fields", another_type_at_first: true
end
describe "when the source role is specified" do
let(:source_role) { all_roles.sample }
it_behaves_like "a copy form with conditional fields", another_type_at_first: false
it "renders the Source role select list with read-only source" do
expect(page).to have_select "Source role", disabled: true do |select|
selected_option_text = select.all("option[selected=selected]").map(&:text)
expect(selected_option_text).to contain_exactly(source_role.name)
end
end
end
end
@@ -1,71 +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 Workflows::Copies::FromRoleForm, type: :forms do
include_context "with rendered form"
let(:model) { false }
let(:params) { { source_type:, source_role:, all_roles: } }
let(:source_type) { create(:type) }
let(:all_roles) { create_list(:project_role, 4) }
let(:source_role) { nil }
it "renders the Source role select list" do
expect(page).to have_select "Source role", required: true do |select|
options_text = select.all("option").map(&:text)
expect(options_text).to match_array(all_roles.map(&:name))
end
end
it "renders the Target roles autocompleter" do
data_attributes = "[data-test-selector=\"target_roles_autocomplete\"][data-multiple=\"true\"]"
expect(page).to have_css "opce-autocompleter#{data_attributes}" do |autocompleter|
options_text = JSON.parse(autocompleter["data-items"]).map { |item| item["name"] }
expect(options_text).to match_array(all_roles.map(&:name))
end
end
it "renders submit button" do
expect(page).to have_button "Copy", class: "Button--primary"
end
describe "when the source type is specified" do
let(:source_role) { all_roles.sample }
it "renders the Source role select list with selected source" do
expect(page).to have_select "Source role", required: true do |select|
selected_option_text = select.all("option[selected=selected]").map(&:text)
expect(selected_option_text).to contain_exactly(source_role.name)
end
end
end
end
+4 -6
View File
@@ -36,16 +36,14 @@ RSpec.describe "workflows routes" do
it { expect(get("/workflows/42/edit")).to route_to("workflows#edit", type_id: "42") }
it { expect(patch("/workflows/42")).to route_to("workflows#update", type_id: "42") }
it { expect(get("/workflows/42/copy/from_type/new")).to route_to("workflows/copies/from_types#new", workflow_type_id: "42") }
it { expect(post("/workflows/42/copy/from_type")).to route_to("workflows/copies/from_types#create", workflow_type_id: "42") }
it { expect(get("/workflows/42/copy/from_role/new")).to route_to("workflows/copies/from_roles#new", workflow_type_id: "42") }
it { expect(get("/workflows/42/copy/new")).to route_to("workflows/copies#new", workflow_type_id: "42") }
it do
expect(get("/workflows/42/copy/from_role/new?source_role_id=23"))
.to route_to("workflows/copies/from_roles#new", workflow_type_id: "42", source_role_id: "23")
expect(get("/workflows/42/copy/new?source_role_id=23"))
.to route_to("workflows/copies#new", workflow_type_id: "42", source_role_id: "23")
end
it { expect(post("/workflows/42/copy/from_type")).to route_to("workflows/copies/from_types#create", workflow_type_id: "42") }
it { expect(post("/workflows/42/copy/from_role")).to route_to("workflows/copies/from_roles#create", workflow_type_id: "42") }
it { expect(get("/workflows/summary")).to route_to("workflows/summaries#show") }