mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
[#63593] Primerize Project create form
Updates the form visuals according to new wireframes: the Project create form now only includes required (built-in and custom) fields, along with the "Subproject of" field. In detail: * Introduces `Projects::NewComponent` and `TemplateSelectComponent`. * Adds, extends necessary Primer forms with basic specs. * Adds `ProjectsController#create` action, routes and permissions. * Adds `highlight-when-value-selected` Stimulus controller. * Updates project feature specs for new form, including rewriting some expectations that were reliant on `data-qa-name` attribute that Primer form components do not render by default. * Removes now obsolete Project Status administation feature spec.
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
<%#-- 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: "turbo-frame",
|
||||
refresh: :morph,
|
||||
autoscroll: true,
|
||||
data: {
|
||||
turbo_action: :replace,
|
||||
autoscroll_block: :start,
|
||||
autoscroll_behavior: :smooth
|
||||
}
|
||||
) do
|
||||
settings_primer_form_with(model: project) do |f|
|
||||
flex_layout do |container|
|
||||
container.with_row(mb: 3) do
|
||||
render(
|
||||
Primer::Forms::FormList.new(
|
||||
Projects::Settings::NameForm.new(f),
|
||||
Projects::Settings::RelationsForm.new(f),
|
||||
Projects::Settings::CustomFieldsForm.new(f)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
if template
|
||||
container.with_row(mb: 3) do
|
||||
render(Primer::Box.new(border: true, border_radius: 2, bg: :inset, p: 3)) do
|
||||
render(
|
||||
Projects::TemplateForm.new(f, template:, copy_options:)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
container.with_row(mb: 3) do
|
||||
render Projects::SubmitOrCancel.new(f)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
%>
|
||||
@@ -0,0 +1,39 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# -- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 2010-2024 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 Projects
|
||||
class NewComponent < ApplicationComponent
|
||||
include ApplicationHelper
|
||||
include OpPrimer::ComponentHelpers
|
||||
include OpTurbo::Streamable
|
||||
|
||||
options :project, :template, :copy_options
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,59 @@
|
||||
<%#-- 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
|
||||
settings_primer_form_with(
|
||||
url: new_project_path,
|
||||
method: :get,
|
||||
data: {
|
||||
turbo_frame: "projects-new-component",
|
||||
controller: "auto-submit",
|
||||
auto_submit_delay_value: 0
|
||||
}
|
||||
) do |f|
|
||||
render(
|
||||
Primer::Box.new(
|
||||
border: true,
|
||||
border_radius: 2,
|
||||
bg: :inset,
|
||||
my: 3,
|
||||
p: 3,
|
||||
data: {
|
||||
controller: "highlight-when-value-selected",
|
||||
highlight_when_value_selected_highlight_class: resolve_css_classes(bg: :accent),
|
||||
highlight_when_value_selected_target: "content"
|
||||
}
|
||||
)
|
||||
) do
|
||||
render Projects::TemplateAutocompleter.new(f, template_id:, parent_id:)
|
||||
end
|
||||
end
|
||||
end
|
||||
%>
|
||||
@@ -0,0 +1,48 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# -- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 2010-2024 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 Projects
|
||||
class TemplateSelectComponent < ApplicationComponent
|
||||
include ApplicationHelper
|
||||
include OpPrimer::ComponentHelpers
|
||||
include OpTurbo::Streamable
|
||||
|
||||
options :template, :parent
|
||||
|
||||
def resolve_css_classes(**)
|
||||
Primer::Classify.(**)[:class]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def template_id = template&.id
|
||||
def parent_id = parent&.id
|
||||
end
|
||||
end
|
||||
@@ -32,11 +32,13 @@ class ProjectsController < ApplicationController
|
||||
menu_item :overview
|
||||
menu_item :roadmap, only: :roadmap
|
||||
|
||||
before_action :find_project, except: %i[index new export_list_modal]
|
||||
before_action :find_project, except: %i[index new create export_list_modal]
|
||||
before_action :load_query_or_deny_access, only: %i[index export_list_modal]
|
||||
before_action :authorize, only: %i[copy deactivate_work_package_attachments]
|
||||
before_action :authorize_global, only: %i[new]
|
||||
before_action :authorize_global, only: %i[new create]
|
||||
before_action :require_admin, only: %i[destroy destroy_info]
|
||||
before_action :find_optional_template, only: %i[new create]
|
||||
before_action :find_optional_parent, only: :new
|
||||
|
||||
no_authorization_required! :index, :export_list_modal
|
||||
|
||||
@@ -89,7 +91,19 @@ class ProjectsController < ApplicationController
|
||||
end
|
||||
|
||||
def new
|
||||
render layout: "no_menu"
|
||||
if from_template?
|
||||
new_from_template
|
||||
else
|
||||
new_blank
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
if from_template?
|
||||
create_from_template
|
||||
else
|
||||
create_blank
|
||||
end
|
||||
end
|
||||
|
||||
def copy
|
||||
@@ -135,6 +149,68 @@ class ProjectsController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def from_template? = @template.present?
|
||||
|
||||
def new_blank
|
||||
@new_project = @parent&.children&.build || Project.new
|
||||
|
||||
render layout: "no_menu"
|
||||
end
|
||||
|
||||
def new_from_template
|
||||
@copy_options = Projects::CopyOptions.new
|
||||
@new_project = Projects::CopyService
|
||||
.new(user: current_user, source: @template, contract_options: { validate_model: false })
|
||||
.call(target_project_params: params.permit(:parent_id).to_h, attributes_only: true)
|
||||
.result
|
||||
|
||||
render layout: "no_menu"
|
||||
end
|
||||
|
||||
def create_blank
|
||||
service_call = Projects::CreateService
|
||||
.new(user: current_user)
|
||||
.call(permitted_params.project)
|
||||
|
||||
@new_project = service_call.result
|
||||
|
||||
if service_call.success?
|
||||
redirect_to project_path(@new_project), notice: I18n.t(:notice_successful_create)
|
||||
else
|
||||
flash.now[:error] = I18n.t(:notice_unsuccessful_create_with_reason, reason: service_call.message)
|
||||
render action: :new, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def create_from_template # rubocop:disable Metrics/AbcSize
|
||||
@copy_options = Projects::CopyOptions.new(copy_options_params)
|
||||
|
||||
service_call = Projects::EnqueueCopyService
|
||||
.new(user: current_user, model: @template)
|
||||
.call(
|
||||
target_project_params: permitted_params.project.to_h,
|
||||
only: @copy_options.dependencies,
|
||||
send_notifications: @copy_options.send_notifications
|
||||
)
|
||||
|
||||
if service_call.success?
|
||||
job = service_call.result
|
||||
redirect_to job_status_path(job.job_id)
|
||||
else
|
||||
@new_project = service_call.result
|
||||
flash.now[:error] = I18n.t(:notice_unsuccessful_create_with_reason, reason: service_call.message)
|
||||
render action: :new, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def find_optional_template
|
||||
@template = Project.templated.visible(current_user).find(params[:template_id]) if params[:template_id].present?
|
||||
end
|
||||
|
||||
def find_optional_parent
|
||||
@parent = Project.visible(current_user).find(params[:parent_id]) if params[:parent_id].present?
|
||||
end
|
||||
|
||||
def has_managed_project_folders?(project)
|
||||
project.project_storages.any?(&:project_folder_automatic?)
|
||||
end
|
||||
@@ -162,5 +238,9 @@ class ProjectsController < ApplicationController
|
||||
::Exports::Register.list_formats(Project).map(&:to_s)
|
||||
end
|
||||
|
||||
def copy_options_params
|
||||
params.expect(copy_options: [[dependencies: []], :send_notifications])
|
||||
end
|
||||
|
||||
helper_method :supported_export_formats
|
||||
end
|
||||
|
||||
@@ -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 Projects
|
||||
class CopyOptions
|
||||
include ActiveModel::Model
|
||||
include ActiveModel::Attributes
|
||||
|
||||
def self.all_dependencies
|
||||
CopyService.copyable_dependencies.to_h { [it[:identifier], it[:name_source].call] }
|
||||
end
|
||||
|
||||
attribute :dependencies, array: true, default: all_dependencies.keys
|
||||
attribute :send_notifications, :boolean, default: false
|
||||
|
||||
validates :dependencies, inclusion: { in: all_dependencies.keys }
|
||||
end
|
||||
end
|
||||
@@ -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 Projects
|
||||
class CopyOptionsForm < ApplicationForm
|
||||
form do |f|
|
||||
f.check_box_group(name: :dependencies, label: I18n.t("js.project.copy.copy_options")) do |group|
|
||||
CopyOptions.all_dependencies.each do |value, label|
|
||||
group.check_box label:, value:
|
||||
end
|
||||
end
|
||||
|
||||
f.separator
|
||||
|
||||
f.check_box name: :send_notifications, label: I18n.t("label_project_copy_notifications")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,52 @@
|
||||
# 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 Projects
|
||||
module Settings
|
||||
class CustomFieldsForm < ApplicationForm
|
||||
include ::CustomFields::CustomFieldRendering
|
||||
|
||||
form do |f|
|
||||
render_custom_fields(form: f)
|
||||
end
|
||||
|
||||
def additional_custom_field_input_arguments
|
||||
{ wrapper_id: nil }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def custom_fields
|
||||
@custom_fields ||= model
|
||||
.available_custom_fields
|
||||
.required
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -40,7 +40,7 @@ module Projects
|
||||
model: project_autocompleter_model,
|
||||
focusDirectly: false,
|
||||
dropdownPosition: "bottom",
|
||||
url: ::API::V3::Utilities::PathHelper::ApiV3Path.projects_available_parents + "?of=#{model.id}",
|
||||
url: project_autocompleter_url,
|
||||
filters: [],
|
||||
data: { qa_field_name: "parent" }
|
||||
}
|
||||
@@ -55,6 +55,12 @@ module Projects
|
||||
|
||||
{ id: parent.id, name: parent.name }
|
||||
end
|
||||
|
||||
def project_autocompleter_url
|
||||
url_str = ::API::V3::Utilities::PathHelper::ApiV3Path.projects_available_parents
|
||||
url_str << "?of=#{model.id}" unless model.new_record?
|
||||
url_str
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# 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 Projects
|
||||
class SubmitOrCancel < ApplicationForm
|
||||
attr_reader :submit_options, :cancel_options
|
||||
|
||||
form do |buttons|
|
||||
buttons.group(layout: :horizontal) do |button_group|
|
||||
button_group.submit(**submit_options)
|
||||
button_group.button(**cancel_options)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(submit_options: {}, cancel_options: {})
|
||||
super()
|
||||
|
||||
@submit_options = submit_options.with_defaults(default_submit_options)
|
||||
@cancel_options = cancel_options.with_defaults(default_cancel_options)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def default_submit_options
|
||||
{
|
||||
name: :submit,
|
||||
scheme: :primary,
|
||||
label: I18n.t("button_create"),
|
||||
disabled: false
|
||||
}
|
||||
end
|
||||
|
||||
def default_cancel_options
|
||||
{
|
||||
name: :cancel,
|
||||
scheme: :default,
|
||||
tag: :a,
|
||||
href: OpenProject::StaticRouting::StaticRouter.new.url_helpers.projects_path,
|
||||
label: I18n.t("button_cancel")
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,61 @@
|
||||
# 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 Projects
|
||||
class TemplateAutocompleter < ApplicationForm
|
||||
extend Dry::Initializer
|
||||
|
||||
option :template_id, optional: true
|
||||
option :parent_id, optional: true
|
||||
|
||||
form do |f|
|
||||
f.project_autocompleter(
|
||||
name: :template_id,
|
||||
label: I18n.t("js.project.use_template"),
|
||||
autocomplete_options: {
|
||||
focusDirectly: false,
|
||||
dropdownPosition: "bottom",
|
||||
inputValue: template_id,
|
||||
placeholder: I18n.t("js.project.no_template_selected"),
|
||||
filters: [
|
||||
{ name: "user_action", operator: "=", values: ["projects/copy"] },
|
||||
{ name: "templated", operator: "=", values: ["t"] }
|
||||
],
|
||||
data: {
|
||||
action: "change->highlight-when-value-selected#itemSelected change->auto-submit#submit",
|
||||
"qa-field-name": "use_template"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
f.hidden name: :parent_id, value: parent_id
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,46 @@
|
||||
# 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 Projects
|
||||
class TemplateForm < ApplicationForm
|
||||
extend Dry::Initializer
|
||||
|
||||
option :template
|
||||
option :copy_options
|
||||
|
||||
form do |f|
|
||||
f.hidden name: :template_id, value: template.id, scope_name_to_model: false
|
||||
|
||||
f.fields_for(:copy_options, copy_options, nested: false) do |builder|
|
||||
CopyOptionsForm.new(builder)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -33,4 +33,18 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
header.with_breadcrumbs([{ href: home_path, text: organization_name }, t(:label_project_new)])
|
||||
end
|
||||
%>
|
||||
<%= angular_component_tag "opce-new-project" %>
|
||||
|
||||
<%=
|
||||
render Projects::TemplateSelectComponent.new(
|
||||
template: @template,
|
||||
parent: @parent
|
||||
)
|
||||
%>
|
||||
|
||||
<%=
|
||||
render Projects::NewComponent.new(
|
||||
project: @new_project,
|
||||
template: @template,
|
||||
copy_options: @copy_options
|
||||
)
|
||||
%>
|
||||
|
||||
@@ -30,7 +30,7 @@ Rails.application.reloader.to_prepare do
|
||||
OpenProject::AccessControl.map do |map|
|
||||
map.project_module nil, order: 100 do
|
||||
map.permission :add_project,
|
||||
{ projects: %i[new] },
|
||||
{ projects: %i[new create] },
|
||||
permissible_on: :global,
|
||||
require: :loggedin,
|
||||
contract_actions: { projects: %i[create] }
|
||||
@@ -202,7 +202,7 @@ Rails.application.reloader.to_prepare do
|
||||
require: :member
|
||||
|
||||
map.permission :add_subprojects,
|
||||
{ projects: %i[new] },
|
||||
{ projects: %i[new create] },
|
||||
permissible_on: :project,
|
||||
require: :member
|
||||
|
||||
|
||||
@@ -936,6 +936,11 @@ en:
|
||||
|
||||
actionview_instancetag_blank_option: "Please select"
|
||||
|
||||
activemodel:
|
||||
attributes:
|
||||
projects/copy_options:
|
||||
dependencies: "Dependencies"
|
||||
|
||||
activerecord:
|
||||
attributes:
|
||||
announcements:
|
||||
@@ -3464,6 +3469,8 @@ en:
|
||||
notice_successful_delete: "Successful deletion."
|
||||
notice_successful_cancel: "Successful cancellation."
|
||||
notice_successful_update: "Successful update."
|
||||
notice_unsuccessful_create: "Creation failed."
|
||||
notice_unsuccessful_create_with_reason: "Creation failed: %{reason}"
|
||||
notice_unsuccessful_update: "Update failed."
|
||||
notice_unsuccessful_update_with_reason: "Update failed: %{reason}"
|
||||
notice_successful_update_custom_fields_added_to_project: |
|
||||
|
||||
+1
-1
@@ -255,7 +255,7 @@ Rails.application.routes.draw do
|
||||
resource :menu, only: %i[show]
|
||||
end
|
||||
|
||||
resources :projects, except: %i[show edit create update] do
|
||||
resources :projects, except: %i[show edit update] do
|
||||
scope module: "projects" do
|
||||
namespace "settings" do
|
||||
resource :general, only: %i[show update], controller: "general" do
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* -- 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 { IAutocompleteItem } from 'core-app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component';
|
||||
|
||||
export default class HighlightWhenValueSelectedController extends Controller {
|
||||
static targets = ['content'];
|
||||
static classes = ['highlight'];
|
||||
|
||||
declare readonly contentTarget:HTMLElement;
|
||||
declare readonly highlightClass:string;
|
||||
|
||||
itemSelected({ detail: item }:CustomEvent<IAutocompleteItem | null>):void {
|
||||
this.contentTarget.classList.toggle(this.highlightClass, item != null);
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import HoverCardTriggerController from './controllers/hover-card-trigger.control
|
||||
import ScrollIntoViewController from './controllers/scroll-into-view.controller';
|
||||
import CkeditorFocusController from './controllers/ckeditor-focus.controller';
|
||||
import AddMeetingParamsController from './controllers/add-meeting-params.controller';
|
||||
import HighlightWhenValueSelectedController from './controllers/highlight-when-value-selected.controller';
|
||||
|
||||
import AutoSubmit from '@stimulus-components/auto-submit';
|
||||
|
||||
@@ -59,5 +60,6 @@ instance.register('pattern-input', PatternInputController);
|
||||
instance.register('scroll-into-view', ScrollIntoViewController);
|
||||
instance.register('ckeditor-focus', CkeditorFocusController);
|
||||
instance.register('add-meeting-params', AddMeetingParamsController);
|
||||
instance.register('highlight-when-value-selected', HighlightWhenValueSelectedController);
|
||||
|
||||
instance.register('auto-submit', AutoSubmit);
|
||||
|
||||
@@ -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 "rails_helper"
|
||||
|
||||
RSpec.describe Projects::NewComponent, type: :component do
|
||||
let(:project) { build_stubbed(:project) }
|
||||
|
||||
def render_component(**params)
|
||||
render_inline(described_class.new(project:, **params))
|
||||
page
|
||||
end
|
||||
|
||||
it "renders a form" do
|
||||
expect(render_component).to have_css "form"
|
||||
end
|
||||
end
|
||||
@@ -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 "rails_helper"
|
||||
|
||||
RSpec.describe Projects::TemplateSelectComponent, type: :component do
|
||||
let(:template) { build_stubbed(:template_project) }
|
||||
|
||||
def render_component(**params)
|
||||
render_inline(described_class.new(template:, **params))
|
||||
page
|
||||
end
|
||||
|
||||
it "renders a form" do
|
||||
expect(render_component).to have_css "form"
|
||||
end
|
||||
end
|
||||
@@ -32,35 +32,200 @@ require "spec_helper"
|
||||
|
||||
RSpec.describe ProjectsController do
|
||||
shared_let(:admin) { create(:admin) }
|
||||
let(:non_member) { create(:non_member) }
|
||||
|
||||
let(:user) { admin }
|
||||
|
||||
before do
|
||||
allow(controller).to receive(:set_localization)
|
||||
|
||||
login_as admin
|
||||
login_as user
|
||||
end
|
||||
|
||||
describe "#new" do
|
||||
it "renders 'new'" do
|
||||
get "new"
|
||||
expect(response).to be_successful
|
||||
expect(response).to render_template "new"
|
||||
end
|
||||
shared_examples_for "successful requests" do
|
||||
context "without a parent" do
|
||||
let(:parent) { nil }
|
||||
|
||||
context "by non-admin user with add_project permission" do
|
||||
let(:non_member_user) { create(:user) }
|
||||
context "without a template" do
|
||||
let(:template) { nil }
|
||||
|
||||
before do
|
||||
non_member.add_permission! :add_project
|
||||
login_as non_member_user
|
||||
it_behaves_like "successful request"
|
||||
end
|
||||
|
||||
context "with a template" do
|
||||
let(:template) { create(:template_project) }
|
||||
|
||||
it_behaves_like "successful request"
|
||||
end
|
||||
end
|
||||
|
||||
it "accepts get" do
|
||||
get :new
|
||||
context "with a parent" do
|
||||
let(:parent) { create(:project) }
|
||||
|
||||
context "without a template" do
|
||||
let(:template) { nil }
|
||||
|
||||
it_behaves_like "successful request"
|
||||
end
|
||||
|
||||
context "with a template" do
|
||||
let(:template) { create(:template_project) }
|
||||
|
||||
it_behaves_like "successful request"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for "successful request" do
|
||||
it "renders 'new'", :aggregate_faulures do
|
||||
expect(response).to be_successful
|
||||
expect(assigns(:new_project)).to be_a_new(Project)
|
||||
expect(assigns(:parent)).to eq parent
|
||||
expect(assigns(:template)).to eq template
|
||||
expect(response).to render_template "new"
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
get :new, params: { parent_id: parent&.id, template_id: template&.id }
|
||||
end
|
||||
|
||||
context "as an admin" do
|
||||
it_behaves_like "successful requests"
|
||||
end
|
||||
|
||||
context "as a non-admin with global add_project permission" do
|
||||
let(:user) { create(:user, global_permissions: [:add_project]) }
|
||||
let(:template) { nil }
|
||||
|
||||
context "without a parent" do
|
||||
let(:parent) { nil }
|
||||
|
||||
it_behaves_like "successful request"
|
||||
end
|
||||
|
||||
context "with a parent with public permissions" do
|
||||
let(:user) { create(:user, global_permissions: [:add_project], member_with_roles: { parent => parent_role }) }
|
||||
let(:parent_role) { create(:project_role) }
|
||||
let(:parent) { create(:project) }
|
||||
|
||||
it_behaves_like "successful request"
|
||||
end
|
||||
end
|
||||
|
||||
context "as a non-admin without global add_project permission" do
|
||||
let(:user) { create(:user, global_permissions: []) }
|
||||
let(:template) { nil }
|
||||
|
||||
context "without a parent" do
|
||||
let(:parent) { nil }
|
||||
|
||||
it "returns 403 Not Authorized" do
|
||||
expect(response).not_to be_successful
|
||||
expect(response).to have_http_status :forbidden
|
||||
end
|
||||
end
|
||||
|
||||
context "with a parent with add_subprojects permissions" do
|
||||
let(:user) { create(:user, global_permissions: [], member_with_roles: { parent => parent_role }) }
|
||||
let(:parent_role) { create(:project_role, permissions: [:add_subprojects]) }
|
||||
let(:parent) { create(:project) }
|
||||
let(:template) { nil }
|
||||
|
||||
it_behaves_like "successful request"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#create" do
|
||||
context "without a template" do
|
||||
before do
|
||||
creation_service = instance_double(Projects::CreateService, call: service_result)
|
||||
|
||||
allow(Projects::CreateService)
|
||||
.to receive(:new)
|
||||
.with(user: admin)
|
||||
.and_return(creation_service)
|
||||
end
|
||||
|
||||
context "when service call succeeds" do
|
||||
let(:project) { build_stubbed(:project) }
|
||||
let(:service_result) { ServiceResult.success(result: project) }
|
||||
|
||||
it "redirects to project show", :aggregate_failures do
|
||||
post :create, params: { project: { name: "New Project" } }
|
||||
|
||||
expect(response).to redirect_to project_path(project)
|
||||
expect(flash[:notice]).to eq I18n.t(:notice_successful_create)
|
||||
end
|
||||
end
|
||||
|
||||
context "when service call fails" do
|
||||
let(:project) { Project.new }
|
||||
let(:service_result) { ServiceResult.failure(result: project, message: "") }
|
||||
|
||||
it "renders new template with errors", :aggregate_failures do
|
||||
post :create, params: { project: { name: "" } }
|
||||
|
||||
expect(response).not_to be_successful
|
||||
expect(response).to have_http_status :unprocessable_entity
|
||||
expect(assigns(:new_project)).to be_a_new(Project)
|
||||
expect(assigns(:new_project)).not_to be_valid
|
||||
expect(flash[:error]).to start_with I18n.t(:notice_unsuccessful_create_with_reason, reason: "")
|
||||
expect(response).to render_template "new"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with a template" do
|
||||
let(:template) { create(:template_project) }
|
||||
|
||||
before do
|
||||
copy_service = instance_double(Projects::EnqueueCopyService, call: service_result)
|
||||
|
||||
allow(Projects::EnqueueCopyService)
|
||||
.to receive(:new)
|
||||
.with(user: admin, model: template)
|
||||
.and_return(copy_service)
|
||||
end
|
||||
|
||||
context "when service call succeeds" do
|
||||
let(:job) { CopyProjectJob.new }
|
||||
let(:service_result) { ServiceResult.success(result: job) }
|
||||
|
||||
it "redirects to job status", :aggregate_failures do
|
||||
post :create, params: {
|
||||
template_id: template.id,
|
||||
project: { name: "New Project" },
|
||||
copy_options: { dependencies: [], send_notifications: false }
|
||||
}
|
||||
|
||||
expect(response).to redirect_to job_status_path(job.job_id)
|
||||
end
|
||||
end
|
||||
|
||||
context "when service call fails" do
|
||||
let(:project) { Project.new }
|
||||
let(:service_result) { ServiceResult.failure(result: project, message: "") }
|
||||
|
||||
it "renders new template with errors", :aggregate_failures do
|
||||
post :create, params: {
|
||||
template_id: template.id,
|
||||
project: { name: "" },
|
||||
copy_options: { dependencies: [], send_notifications: false }
|
||||
}
|
||||
|
||||
expect(response).not_to be_successful
|
||||
expect(response).to have_http_status :unprocessable_entity
|
||||
expect(assigns(:new_project)).to be_a_new(Project)
|
||||
expect(assigns(:new_project)).not_to be_valid
|
||||
expect(assigns(:template)).not_to be_nil
|
||||
expect(assigns(:copy_options)).not_to be_nil
|
||||
expect(flash[:error]).to start_with I18n.t(:notice_unsuccessful_create_with_reason, reason: "")
|
||||
expect(response).to render_template "new"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "index.html" do
|
||||
|
||||
@@ -81,17 +81,15 @@ RSpec.describe "Global role: Global Create project",
|
||||
roles: [global_role])
|
||||
end
|
||||
|
||||
let(:name_field) { FormFields::InputFormField.new :name }
|
||||
|
||||
current_user { user }
|
||||
|
||||
it 'allows creating projects via the "+ Project" button' do
|
||||
projects_page.visit!
|
||||
projects_page.navigate_to_new_project_page_from_toolbar_items
|
||||
|
||||
name_field.set_value "New project name"
|
||||
fill_in "Name", with: "New project name"
|
||||
|
||||
find("button:not([disabled])", text: "Save").click
|
||||
click_on "Create"
|
||||
|
||||
expect(page).to have_current_path "/projects/new-project-name/"
|
||||
end
|
||||
|
||||
@@ -35,11 +35,14 @@ RSpec.describe "Project attribute help texts", :js do
|
||||
|
||||
let(:instance) do
|
||||
create(:project_help_text,
|
||||
attribute_name: :status,
|
||||
help_text: "Some **help text** for status.")
|
||||
attribute_name: :name,
|
||||
help_text: "Some **help text** for name.")
|
||||
create(:project_help_text,
|
||||
attribute_name: :description,
|
||||
help_text: "Some **help text** for description.")
|
||||
create(:project_help_text,
|
||||
attribute_name: :status,
|
||||
help_text: "Some **help text** for status.")
|
||||
end
|
||||
|
||||
let(:grid) do
|
||||
@@ -87,19 +90,27 @@ RSpec.describe "Project attribute help texts", :js do
|
||||
|
||||
it_behaves_like "allows to view help texts"
|
||||
|
||||
it "shows the help text on the project create form" do
|
||||
it "shows the help text on the project create form", :selenium do
|
||||
visit new_project_path
|
||||
|
||||
page.find(".op-fieldset--legend", text: "ADVANCED SETTINGS").click
|
||||
expect(page).to have_field "Name"
|
||||
|
||||
expect(page).to have_css(".spot-form-field--label attribute-help-text", wait: 10)
|
||||
within(:element, "label", text: "Name") do
|
||||
click_on accessible_name: "Show help text"
|
||||
end
|
||||
|
||||
# Open help text modal
|
||||
modal.open!
|
||||
expect(modal.modal_container).to have_css("strong", text: "help text")
|
||||
modal.expect_edit(editable: user.allowed_globally?(:edit_attribute_help_texts))
|
||||
expect(page).to have_modal "Name"
|
||||
|
||||
modal.close!
|
||||
within_modal "Name" do
|
||||
expect(page).to have_css "strong", text: "help text"
|
||||
|
||||
expect(page).to have_button "Close"
|
||||
expect(page).to have_link "Edit"
|
||||
|
||||
click_on "Close"
|
||||
end
|
||||
|
||||
expect(page).not_to have_modal "Name"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ require "spec_helper"
|
||||
|
||||
RSpec.describe "Projects", "creation",
|
||||
:js do
|
||||
shared_let(:name_field) { FormFields::InputFormField.new :name }
|
||||
shared_let(:project_custom_field_section) { create(:project_custom_field_section, name: "Section A") }
|
||||
|
||||
current_user { create(:admin) }
|
||||
@@ -49,6 +48,8 @@ RSpec.describe "Projects", "creation",
|
||||
it "can navigate to the create project page" do
|
||||
projects_page.navigate_to_new_project_page_from_toolbar_items
|
||||
|
||||
expect(page).to have_heading "New project"
|
||||
|
||||
expect(page).to have_current_path(new_project_path)
|
||||
end
|
||||
end
|
||||
@@ -56,8 +57,10 @@ RSpec.describe "Projects", "creation",
|
||||
it "can create a project" do
|
||||
projects_page.navigate_to_new_project_page_from_toolbar_items
|
||||
|
||||
name_field.set_value "Foo bar"
|
||||
click_button "Save"
|
||||
expect(page).to have_heading "New project"
|
||||
|
||||
fill_in "Name", with: "Foo bar"
|
||||
click_on "Create"
|
||||
|
||||
expect(page).to have_current_path /\/projects\/foo-bar\/?/
|
||||
expect(page).to have_content "Foo bar"
|
||||
@@ -66,8 +69,10 @@ RSpec.describe "Projects", "creation",
|
||||
it "does not create a project with an already existing identifier" do
|
||||
projects_page.navigate_to_new_project_page_from_toolbar_items
|
||||
|
||||
name_field.set_value "Foo project"
|
||||
click_on "Save"
|
||||
expect(page).to have_heading "New project"
|
||||
|
||||
fill_in "Name", with: "Foo project"
|
||||
click_on "Create"
|
||||
|
||||
expect(page).to have_current_path /\/projects\/foo-project-1\/?/
|
||||
|
||||
@@ -77,20 +82,30 @@ RSpec.describe "Projects", "creation",
|
||||
|
||||
context "with a multi-select list custom field" do
|
||||
shared_let(:list_custom_field) do
|
||||
create(:list_project_custom_field, name: "List CF", multi_value: true, project_custom_field_section:)
|
||||
create(:list_project_custom_field,
|
||||
name: "List CF",
|
||||
is_required: true,
|
||||
multi_value: true,
|
||||
project_custom_field_section:)
|
||||
end
|
||||
let(:list_field) do
|
||||
FormFields::SelectFormField.new(
|
||||
list_custom_field,
|
||||
selector: "[data-qa-field-name='#{list_custom_field.attribute_name(:kebab_case)}'"
|
||||
)
|
||||
end
|
||||
let(:list_field) { FormFields::SelectFormField.new list_custom_field }
|
||||
|
||||
it "can create a project" do
|
||||
projects_page.navigate_to_new_project_page_from_toolbar_items
|
||||
|
||||
name_field.set_value "Foo bar"
|
||||
expect(page).to have_heading "New project"
|
||||
|
||||
find(".op-fieldset--toggle", text: "ADVANCED SETTINGS").click
|
||||
fill_in "Name", with: "Foo bar"
|
||||
|
||||
expect(page).to have_combo_box "List CF *"
|
||||
list_field.select_option "A", "B"
|
||||
|
||||
click_button "Save"
|
||||
click_on "Create"
|
||||
|
||||
expect(page).to have_current_path /\/projects\/foo-bar\/?/
|
||||
expect(page).to have_content "Foo bar"
|
||||
@@ -120,18 +135,26 @@ RSpec.describe "Projects", "creation",
|
||||
shared_let(:version_custom_field) do
|
||||
create(:version_project_custom_field,
|
||||
name: "Version CF",
|
||||
is_required: true,
|
||||
multi_value: true,
|
||||
project_custom_field_section:)
|
||||
end
|
||||
|
||||
let(:version_field) { FormFields::SelectFormField.new version_custom_field }
|
||||
let(:version_field) do
|
||||
FormFields::SelectFormField.new(
|
||||
version_custom_field,
|
||||
selector: "[data-qa-field-name='#{version_custom_field.attribute_name(:kebab_case)}'"
|
||||
)
|
||||
end
|
||||
|
||||
it "can create a project" do
|
||||
projects_page.navigate_to_new_project_page_from_toolbar_items
|
||||
|
||||
name_field.set_value "Foo bar"
|
||||
expect(page).to have_heading "New project"
|
||||
|
||||
find(".op-fieldset--toggle", text: "ADVANCED SETTINGS").click
|
||||
fill_in "Name", with: "Foo bar"
|
||||
|
||||
expect(page).to have_combo_box "Version CF *"
|
||||
|
||||
# expect the versions are grouped by the project name
|
||||
version_field.expect_option(versions.first.name, grouping: project.name)
|
||||
@@ -139,7 +162,7 @@ RSpec.describe "Projects", "creation",
|
||||
|
||||
version_field.select_option(versions.first.name, versions.last.name)
|
||||
|
||||
click_button "Save"
|
||||
click_on "Create"
|
||||
|
||||
expect(page).to have_current_path /\/projects\/foo-bar\/?/
|
||||
expect(page).to have_content "Foo bar"
|
||||
@@ -155,7 +178,7 @@ RSpec.describe "Projects", "creation",
|
||||
it "hides the active field and the identifier" do
|
||||
visit new_project_path
|
||||
|
||||
find(".op-fieldset--toggle", text: "ADVANCED SETTINGS").click
|
||||
expect(page).to have_heading "New project"
|
||||
|
||||
expect(page).to have_no_content "Active"
|
||||
expect(page).to have_no_content "Identifier"
|
||||
@@ -184,19 +207,14 @@ RSpec.describe "Projects", "creation",
|
||||
project_custom_field_section:)
|
||||
end
|
||||
|
||||
it "separates optional and required custom fields for new" do
|
||||
it "renders required custom fields for new" do
|
||||
visit new_project_path
|
||||
|
||||
expect(page).to have_content "Required Foo"
|
||||
expect(page).to have_content "Required User"
|
||||
expect(page).to have_heading "New project"
|
||||
|
||||
click_on "Advanced settings"
|
||||
|
||||
within(".op-fieldset") do
|
||||
expect(page).to have_text "Optional Foo"
|
||||
expect(page).to have_no_text "Required Foo"
|
||||
expect(page).to have_no_text "Required User"
|
||||
end
|
||||
expect(page).to have_field "Required Foo", required: true
|
||||
expect(page).to have_field "Required User *" # FIXME required: true
|
||||
expect(page).to have_no_field "Optional Foo"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -206,10 +224,11 @@ RSpec.describe "Projects", "creation",
|
||||
end
|
||||
|
||||
it "requires the required custom field" do
|
||||
click_on "Save"
|
||||
expect(page).to have_heading "New project"
|
||||
|
||||
expect(page).to have_content "Required Foo can't be blank"
|
||||
expect(page).to have_no_content "Optional Foo can't be blank"
|
||||
click_on "Create"
|
||||
|
||||
expect(page).to have_field "Required Foo", validation_error: "can't be blank."
|
||||
end
|
||||
end
|
||||
|
||||
@@ -222,17 +241,15 @@ RSpec.describe "Projects", "creation",
|
||||
|
||||
before do
|
||||
visit new_project_path
|
||||
|
||||
expect(page).to have_heading "New project" # rubocop:disable RSpec/ExpectInHook
|
||||
|
||||
fill_in "Name", with: "Foo bar"
|
||||
fill_in "Required Foo", with: "Required value"
|
||||
|
||||
click_on "Advanced settings"
|
||||
end
|
||||
|
||||
it "enables custom fields with provided values for this project" do
|
||||
fill_in "Optional Foo", with: "Optional value"
|
||||
fill_in "Unused Foo", with: ""
|
||||
|
||||
click_on "Save"
|
||||
click_on "Create"
|
||||
|
||||
expect(page).to have_current_path /\/projects\/foo-bar\/?/
|
||||
|
||||
@@ -240,7 +257,7 @@ RSpec.describe "Projects", "creation",
|
||||
|
||||
# unused custom field should not be activated
|
||||
expect(project.project_custom_field_ids).to contain_exactly(
|
||||
optional_custom_field.id, required_custom_field.id
|
||||
required_custom_field.id
|
||||
)
|
||||
end
|
||||
|
||||
@@ -248,13 +265,14 @@ RSpec.describe "Projects", "creation",
|
||||
shared_let(:custom_field_with_default_value) do
|
||||
create(:project_custom_field, name: "Foo with default value",
|
||||
field_format: "string",
|
||||
is_required: true,
|
||||
default_value: "Default value",
|
||||
project_custom_field_section:)
|
||||
end
|
||||
|
||||
it "enables custom fields with default values if not set to blank explicitly" do
|
||||
# don't touch the default value
|
||||
click_on "Save"
|
||||
click_on "Create"
|
||||
|
||||
expect(page).to have_current_path /\/projects\/foo-bar\/?/
|
||||
|
||||
@@ -268,27 +286,10 @@ RSpec.describe "Projects", "creation",
|
||||
expect(project.custom_value_for(custom_field_with_default_value).value).to eq("Default value")
|
||||
end
|
||||
|
||||
it "does not enable custom fields with default values if set to blank explicitly" do
|
||||
# native blank input does not work with this input, using support class here
|
||||
field = FormFields::InputFormField.new(custom_field_with_default_value)
|
||||
field.set_value("")
|
||||
|
||||
click_on "Save"
|
||||
|
||||
expect(page).to have_current_path /\/projects\/foo-bar\/?/
|
||||
|
||||
project = Project.last
|
||||
|
||||
# custom_field_with_default_value should not be activated
|
||||
expect(project.project_custom_field_ids).to contain_exactly(
|
||||
required_custom_field.id
|
||||
)
|
||||
end
|
||||
|
||||
it "does enable custom fields with default values if overwritten with a new value" do
|
||||
fill_in "Foo with default value", with: "foo"
|
||||
|
||||
click_on "Save"
|
||||
click_on "Create"
|
||||
|
||||
expect(page).to have_current_path /\/projects\/foo-bar\/?/
|
||||
|
||||
@@ -303,86 +304,10 @@ RSpec.describe "Projects", "creation",
|
||||
end
|
||||
end
|
||||
|
||||
context "with correct handling of optional boolean values" do
|
||||
shared_let(:custom_boolean_field_default_true) do
|
||||
create(:project_custom_field, name: "Boolean with default true",
|
||||
field_format: "bool",
|
||||
default_value: true,
|
||||
project_custom_field_section:)
|
||||
end
|
||||
|
||||
shared_let(:custom_boolean_field_default_false) do
|
||||
create(:project_custom_field, name: "Boolean with default false",
|
||||
field_format: "bool",
|
||||
default_value: false,
|
||||
project_custom_field_section:)
|
||||
end
|
||||
|
||||
shared_let(:custom_boolean_field_with_no_default) do
|
||||
create(:project_custom_field, name: "Boolean with no default",
|
||||
field_format: "bool",
|
||||
project_custom_field_section:)
|
||||
end
|
||||
|
||||
it "only enables boolean custom fields with default values if untouched" do
|
||||
# do not touch any of the boolean fields
|
||||
click_on "Save"
|
||||
|
||||
expect(page).to have_current_path /\/projects\/foo-bar\/?/
|
||||
|
||||
project = Project.last
|
||||
|
||||
expect(project.project_custom_field_ids).to contain_exactly(
|
||||
required_custom_field.id,
|
||||
custom_boolean_field_default_true.id,
|
||||
custom_boolean_field_default_false.id
|
||||
)
|
||||
|
||||
expect(project.custom_value_for(custom_boolean_field_default_true).typed_value).to be_truthy
|
||||
expect(project.custom_value_for(custom_boolean_field_default_false).typed_value).to be_falsy
|
||||
end
|
||||
|
||||
it "enables boolean custom fields without default values if set to true explicitly" do
|
||||
check "Boolean with no default"
|
||||
|
||||
click_on "Save"
|
||||
|
||||
expect(page).to have_current_path /\/projects\/foo-bar\/?/
|
||||
|
||||
project = Project.last
|
||||
|
||||
expect(project.project_custom_field_ids).to contain_exactly(
|
||||
required_custom_field.id,
|
||||
custom_boolean_field_default_true.id,
|
||||
custom_boolean_field_default_false.id,
|
||||
custom_boolean_field_with_no_default.id
|
||||
)
|
||||
|
||||
expect(project.custom_value_for(custom_boolean_field_with_no_default).typed_value).to be_truthy
|
||||
end
|
||||
|
||||
it "enables boolean custom fields with default values if set to false explicitly" do
|
||||
uncheck "Boolean with default true"
|
||||
|
||||
click_on "Save"
|
||||
|
||||
expect(page).to have_current_path /\/projects\/foo-bar\/?/
|
||||
|
||||
project = Project.last
|
||||
|
||||
expect(project.project_custom_field_ids).to contain_exactly(
|
||||
required_custom_field.id,
|
||||
custom_boolean_field_default_true.id,
|
||||
custom_boolean_field_default_false.id
|
||||
)
|
||||
|
||||
expect(project.custom_value_for(custom_boolean_field_default_true).typed_value).to be_falsy
|
||||
end
|
||||
end
|
||||
|
||||
context "with correct handling of invisible values" do
|
||||
shared_let(:invisible_field) do
|
||||
create(:string_project_custom_field, name: "Text for Admins only",
|
||||
is_required: true,
|
||||
admin_only: true,
|
||||
project_custom_field_section:)
|
||||
end
|
||||
@@ -393,7 +318,7 @@ RSpec.describe "Projects", "creation",
|
||||
|
||||
fill_in "Text for Admins only", with: "foo"
|
||||
|
||||
click_on "Save"
|
||||
click_on "Create"
|
||||
|
||||
expect(page).to have_current_path /\/projects\/foo-bar\/?/
|
||||
|
||||
@@ -411,9 +336,11 @@ RSpec.describe "Projects", "creation",
|
||||
current_user { create(:user, global_permissions: %i[add_project]) }
|
||||
|
||||
it "does not show invisible fields in the form and thus not activates the invisible field" do
|
||||
pending "Admin-only project attributes currently prevent users from creating projects (OP#64479)"
|
||||
|
||||
expect(page).to have_no_content "Text for Admins only"
|
||||
|
||||
click_on "Save"
|
||||
click_on "Create"
|
||||
|
||||
expect(page).to have_current_path /\/projects\/foo-bar\/?/
|
||||
|
||||
|
||||
@@ -1,88 +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 "Projects status administration", :js do
|
||||
include_context "ng-select-autocomplete helpers"
|
||||
|
||||
let(:current_user) do
|
||||
create(:user) do |u|
|
||||
create(:global_member,
|
||||
principal: u,
|
||||
roles: [create(:global_role, permissions: global_permissions)])
|
||||
end
|
||||
end
|
||||
let(:global_permissions) { [:add_project] }
|
||||
let(:project_permissions) { [:edit_project] }
|
||||
let!(:project_role) do
|
||||
create(:project_role, permissions: project_permissions) do |r|
|
||||
allow(Setting)
|
||||
.to receive(:new_project_user_role_id)
|
||||
.and_return(r.id.to_s)
|
||||
end
|
||||
end
|
||||
let(:status_description) { Components::WysiwygEditor.new('[data-qa-field-name="statusExplanation"]') }
|
||||
|
||||
let(:name_field) { FormFields::InputFormField.new :name }
|
||||
let(:status_field) { FormFields::SelectFormField.new :status }
|
||||
|
||||
before do
|
||||
login_as current_user
|
||||
end
|
||||
|
||||
it "allows setting the status on project creation" do
|
||||
visit new_project_path
|
||||
|
||||
# Create the project with status
|
||||
click_button "Advanced settings"
|
||||
|
||||
name_field.set_value "New project"
|
||||
status_field.select_option "On track"
|
||||
|
||||
status_description.set_markdown "Everything is fine at the start"
|
||||
status_description.expect_supports_macros
|
||||
|
||||
click_button "Save"
|
||||
|
||||
expect(page).to have_current_path /projects\/new-project\/?/
|
||||
|
||||
# Check that the status has been set correctly
|
||||
visit project_settings_general_path(project_id: "new-project")
|
||||
|
||||
status_description.expect_value "Everything is fine at the start"
|
||||
|
||||
status_description.set_markdown "Oh no"
|
||||
|
||||
click_button "Update status description"
|
||||
|
||||
status_description.expect_value "Oh no"
|
||||
end
|
||||
end
|
||||
@@ -31,7 +31,6 @@
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe "Subproject creation", :js do
|
||||
let(:name_field) { FormFields::InputFormField.new :name }
|
||||
let(:parent_field) { FormFields::SelectFormField.new :parent }
|
||||
let(:add_subproject_role) { create(:project_role, permissions: %i[edit_project add_subprojects]) }
|
||||
let(:view_project_role) { create(:project_role, permissions: %i[edit_project]) }
|
||||
@@ -55,16 +54,20 @@ RSpec.describe "Subproject creation", :js do
|
||||
end
|
||||
|
||||
it "can create a subproject" do
|
||||
click_link "Subproject"
|
||||
click_on "New subproject"
|
||||
|
||||
expect(page).to have_heading "New project"
|
||||
|
||||
fill_in "Name", with: "Foo child"
|
||||
|
||||
expect(page).to have_combo_box "Subproject of"
|
||||
|
||||
name_field.set_value "Foo child"
|
||||
parent_field.expect_required
|
||||
# The other project is not a valid parent since the user is lacking
|
||||
# the add_subproject permission therein.
|
||||
parent_field.expect_no_option(other_project.name)
|
||||
parent_field.expect_selected parent_project.name
|
||||
|
||||
click_button "Save"
|
||||
click_on "Create"
|
||||
|
||||
expect(page).to have_current_path /\/projects\/foo-child\/?/
|
||||
|
||||
|
||||
@@ -85,10 +85,7 @@ RSpec.describe "Project templates", :js, with_good_job_batches: [CopyProjectJob,
|
||||
create(:user, member_with_roles: { template => role })
|
||||
end
|
||||
|
||||
let(:name_field) { FormFields::InputFormField.new :name }
|
||||
let(:template_field) { FormFields::SelectFormField.new :use_template }
|
||||
let(:status_field) { FormFields::SelectFormField.new :status }
|
||||
let(:parent_field) { FormFields::SelectFormField.new :parent }
|
||||
|
||||
current_user do
|
||||
create(:user,
|
||||
@@ -99,42 +96,37 @@ RSpec.describe "Project templates", :js, with_good_job_batches: [CopyProjectJob,
|
||||
it "can instantiate the project with the copy permission" do
|
||||
visit new_project_path
|
||||
|
||||
name_field.set_value "Foo bar"
|
||||
fill_in "Name", with: "Foo bar"
|
||||
|
||||
expect(page)
|
||||
.to have_no_content("COPY OPTIONS")
|
||||
expect(page).to have_no_selector :fieldset, "Copy options"
|
||||
|
||||
template_field.select_option "My template"
|
||||
|
||||
# Only when a template is selected, the options are displayed.
|
||||
# Using this to know when the copy form has been fetched from the backend.
|
||||
expect(page)
|
||||
.to have_content("COPY OPTIONS")
|
||||
expect(page).to have_selector :fieldset, "Copy options"
|
||||
|
||||
# FIXME: It should keep the name. See BUG OP#64594 https://community.openproject.org/wp/64594
|
||||
# expect(page).to have_field "Name", with: "Foo bar"
|
||||
fill_in "Name", with: "Foo bar"
|
||||
|
||||
# It keeps the name
|
||||
name_field.expect_value "Foo bar"
|
||||
template_field.expect_selected "My template"
|
||||
|
||||
# Updates the identifier in advanced settings
|
||||
page.find(".op-fieldset--toggle", text: "ADVANCED SETTINGS").click
|
||||
status_field.expect_selected "ON TRACK"
|
||||
expect(page).to have_unchecked_field "Send email notifications during the project copy"
|
||||
|
||||
# Update status to off track
|
||||
status_field.select_option "Off track"
|
||||
parent_field.select_option other_project.name
|
||||
within_fieldset "Copy options" do
|
||||
# And allows to deselect copying the members.
|
||||
uncheck "Project members"
|
||||
end
|
||||
|
||||
page.find(".op-fieldset--toggle", text: "COPY OPTIONS").click
|
||||
click_on "Create"
|
||||
|
||||
# Now shows the send notifications field.
|
||||
expect(page).to have_css('[data-qa-field-name="sendNotifications"]')
|
||||
expect(page).to have_dialog "Background job status"
|
||||
|
||||
# And allows to deselect copying the members.
|
||||
uncheck I18n.t(:"projects.copy.members")
|
||||
|
||||
page.find("button:not([disabled])", text: "Save").click
|
||||
|
||||
expect(page).to have_content I18n.t(:label_copy_project)
|
||||
expect(page).to have_content I18n.t("job_status_dialog.generic_messages.in_queue")
|
||||
within_dialog "Background job status" do
|
||||
expect(page).to have_heading "Copy project"
|
||||
expect(page).to have_text "The job has been queued and will be processed shortly."
|
||||
end
|
||||
|
||||
# Run background jobs twice: the background job which itself enqueues the mailer job
|
||||
GoodJob.perform_inline
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# 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 Projects::CopyOptionsForm, type: :forms do
|
||||
include_context "with rendered form"
|
||||
|
||||
let(:model) { Projects::CopyOptions.new }
|
||||
|
||||
shared_examples "rendering dependency checkbox" do |locator|
|
||||
it "renders checked checkbox for '#{locator}'" do
|
||||
expect(page).to have_checked_field locator, fieldset: "Copy options"
|
||||
end
|
||||
end
|
||||
|
||||
describe "dependencies" do
|
||||
include_examples "rendering dependency checkbox", "Project members"
|
||||
include_examples "rendering dependency checkbox", "Versions"
|
||||
include_examples "rendering dependency checkbox", "Work packages: categories"
|
||||
include_examples "rendering dependency checkbox", "Work packages"
|
||||
include_examples "rendering dependency checkbox", "Work packages: attachments"
|
||||
include_examples "rendering dependency checkbox", "Work packages: shares"
|
||||
include_examples "rendering dependency checkbox", "Wiki pages"
|
||||
include_examples "rendering dependency checkbox", "Wiki pages: attachments"
|
||||
include_examples "rendering dependency checkbox", "Forums"
|
||||
include_examples "rendering dependency checkbox", "Work packages: saved views"
|
||||
include_examples "rendering dependency checkbox", "Boards"
|
||||
include_examples "rendering dependency checkbox", "Project overview"
|
||||
include_examples "rendering dependency checkbox", "File storages"
|
||||
include_examples "rendering dependency checkbox", "File storages: Project folders"
|
||||
include_examples "rendering dependency checkbox", "Work packages: file links"
|
||||
end
|
||||
|
||||
describe "notifications" do
|
||||
it "renders unchecked checkbox for email notifications" do
|
||||
expect(page).to have_unchecked_field "Send email notifications during the project copy"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,51 @@
|
||||
# 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 Projects::CopyOptions, type: :model do
|
||||
describe "#dependencies" do
|
||||
let(:all_dependencies) { Projects::CopyService.copyable_dependencies.pluck(:identifier) }
|
||||
|
||||
it "has a default value" do
|
||||
expect(subject.dependencies).to match_array(all_dependencies)
|
||||
end
|
||||
|
||||
it "validates dependencies" do
|
||||
expect(subject).to validate_inclusion_of(:dependencies).in_array(all_dependencies)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#send_notifications" do
|
||||
it "has a default value" do
|
||||
expect(subject.send_notifications).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,131 @@
|
||||
# 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 Projects::Settings::CustomFieldsForm, type: :forms do
|
||||
let(:string_project_custom_field) { create(:string_project_custom_field, name: "String field", is_required: true) }
|
||||
let(:boolean_project_custom_field) { create(:boolean_project_custom_field, name: "Boolean field", is_required: true) }
|
||||
let(:text_project_custom_field) { create(:text_project_custom_field, name: "Text field", is_required: true) }
|
||||
let(:integer_project_custom_field) { create(:integer_project_custom_field, name: "Integer field", is_required: true) }
|
||||
let(:float_project_custom_field) { create(:float_project_custom_field, name: "Float field", is_required: true) }
|
||||
let(:date_project_custom_field) { create(:date_project_custom_field, name: "Date field", is_required: true) }
|
||||
let(:list_project_custom_field) do
|
||||
create(:list_project_custom_field, name: "List field", is_required: true, possible_values: ["eins", "zwei", "drei"])
|
||||
end
|
||||
let(:multi_list_project_custom_field) do
|
||||
create(:list_project_custom_field,
|
||||
name: "Multi-list field",
|
||||
is_required: true,
|
||||
multi_value: true,
|
||||
possible_values: ["uno", "due", "tre", "quattro"])
|
||||
end
|
||||
let(:version_project_custom_field) { create(:version_project_custom_field, name: "Version field", is_required: true) }
|
||||
let(:user_project_custom_field) { create(:user_project_custom_field, name: "User field", is_required: true) }
|
||||
let(:link_project_custom_field) { create(:link_project_custom_field, name: "Link field", is_required: true) }
|
||||
|
||||
let(:user) { create(:user) }
|
||||
let(:version) { create(:version) }
|
||||
|
||||
let(:custom_field_values) do
|
||||
{
|
||||
"#{string_project_custom_field.id}": "str_val",
|
||||
"#{boolean_project_custom_field.id}": true,
|
||||
"#{integer_project_custom_field.id}": 43,
|
||||
"#{float_project_custom_field.id}": 78.23,
|
||||
"#{date_project_custom_field.id}}": Date.civil(2024, 0o3, 20),
|
||||
"#{link_project_custom_field.id}}": "https://rubygems.org/",
|
||||
"#{list_project_custom_field.id}}": list_project_custom_field.possible_values.first.id,
|
||||
"#{multi_list_project_custom_field.id}}": multi_list_project_custom_field.possible_values.last(2).map(&:id),
|
||||
"#{version_project_custom_field.id}}": version,
|
||||
"#{user_project_custom_field.id}}": user
|
||||
}
|
||||
end
|
||||
|
||||
let(:model) { create(:project, custom_field_values:) }
|
||||
let(:current_user) { build_stubbed(:admin) }
|
||||
|
||||
current_user { build_stubbed(:admin) }
|
||||
|
||||
include_context "with rendered form"
|
||||
|
||||
it "renders HTML input fields", :aggregate_failures do
|
||||
expect(page).to have_field "String field", with: "str_val", required: true
|
||||
expect(page).to have_checked_field "Boolean field", required: true
|
||||
expect(page).to have_field "Integer field", type: :number, with: "43", required: true
|
||||
expect(page).to have_field "Float field", type: :number, with: "78.23", required: true
|
||||
expect(page).to have_field "Date field", type: :date, with: "2024-03-20", required: true
|
||||
expect(page).to have_field "Link field", with: "https://rubygems.org/", required: true
|
||||
end
|
||||
|
||||
it "renders list field" do
|
||||
expect(page).to have_element :label, text: "List field"
|
||||
|
||||
label_id = page.find(:element, :label, text: "List field")["for"]
|
||||
expect(page).to have_element "opce-autocompleter", "data-label-for-id": "\"#{label_id}\"" do |autocompleter|
|
||||
expect(autocompleter["data-multiple"]).to be_json_eql(%{false})
|
||||
expect(autocompleter["data-items"]).to have_json_size(3)
|
||||
expect(autocompleter["data-model"]).to be_json_eql(%{{"name": "eins"}})
|
||||
end
|
||||
end
|
||||
|
||||
it "renders multi-list field" do
|
||||
expect(page).to have_element :label, text: "Multi-list field"
|
||||
|
||||
label_id = page.find(:element, :label, text: "Multi-list field")["for"]
|
||||
expect(page).to have_element "opce-autocompleter", "data-label-for-id": "\"#{label_id}\"" do |autocompleter|
|
||||
expect(autocompleter["data-multiple"]).to be_json_eql(%{true})
|
||||
expect(autocompleter["data-items"]).to have_json_size(4)
|
||||
expect(autocompleter["data-model"]).to have_json_size(2)
|
||||
expect(autocompleter["data-model"]).to be_json_eql(%{[{"name": "tre"}, {"name": "quattro"}]})
|
||||
end
|
||||
end
|
||||
|
||||
it "renders version field" do
|
||||
expect(page).to have_element :label, text: "Version field"
|
||||
|
||||
label_id = page.find(:element, :label, text: "Version field")["for"]
|
||||
expect(page).to have_element "opce-autocompleter", "data-label-for-id": "\"#{label_id}\"" do |autocompleter|
|
||||
expect(autocompleter["data-items"]).to have_json_size(0)
|
||||
expect(autocompleter["data-model"]).to be_json_eql(%{null})
|
||||
end
|
||||
end
|
||||
|
||||
it "renders user field" do
|
||||
expect(page).to have_element :label, text: "User field"
|
||||
|
||||
label_id = page.find(:element, :label, text: "User field")["for"]
|
||||
expect(page).to have_element "opce-user-autocompleter", "data-label-for-id": "\"#{label_id}\"" do |autocompleter|
|
||||
expect(autocompleter["data-resource"]).to be_json_eql(%{"principals"})
|
||||
expect(autocompleter["data-url"]).to be_json_eql(%{"/api/v3/principals"})
|
||||
expect(autocompleter["data-input-value"]).to be_json_eql(%{"#{user.id}"})
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,49 @@
|
||||
# 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 Projects::SubmitOrCancel, type: :forms do
|
||||
include_context "with rendered form"
|
||||
|
||||
let(:model) { build_stubbed(:project) }
|
||||
|
||||
it "renders a horizontal group" do
|
||||
expect(page).to have_css ".FormControl-horizontalGroup"
|
||||
end
|
||||
|
||||
it "renders submit button" do
|
||||
expect(page).to have_button "Create", class: "Button--primary"
|
||||
end
|
||||
|
||||
it "renders cancel link" do
|
||||
expect(page).to have_link "Cancel", class: "Button--secondary"
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,62 @@
|
||||
# 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 Projects::TemplateAutocompleter, type: :forms do
|
||||
include ViewComponent::TestHelpers
|
||||
|
||||
def render_form
|
||||
render_in_view_context(template_id, described_class) do |template_id, described_class|
|
||||
primer_form_with(url: "/foo") do |f|
|
||||
render(described_class.new(f, template_id:))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
render_form
|
||||
end
|
||||
|
||||
let(:template_id) { 1001 }
|
||||
|
||||
it "renders field" do
|
||||
expect(page).to have_element "opce-project-autocompleter", "data-input-name": "\"template_id\"" do |element|
|
||||
expect(element["data-input-value"]).to eq "1001"
|
||||
end
|
||||
end
|
||||
|
||||
it "connects Stimulus controller actions" do
|
||||
expect(page).to have_element "opce-project-autocompleter", "data-input-name": "\"template_id\"" do |element|
|
||||
expect(element["data-action"]).to include "change->highlight-when-value-selected#itemSelected"
|
||||
expect(element["data-action"]).to include "change->auto-submit#submit"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -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.
|
||||
#++
|
||||
#
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe Projects::TemplateForm, type: :forms do
|
||||
include ViewComponent::TestHelpers
|
||||
|
||||
def render_form
|
||||
render_in_view_context(
|
||||
model,
|
||||
template,
|
||||
copy_options,
|
||||
described_class
|
||||
) do |model, template, copy_options, described_class|
|
||||
primer_form_with(url: "/foo", model:) do |f|
|
||||
render(described_class.new(f, template:, copy_options:))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
render_form
|
||||
end
|
||||
|
||||
let(:model) { build_stubbed(:project) }
|
||||
let(:template) { build_stubbed(:template_project) }
|
||||
let(:copy_options) { Projects::CopyOptions.new }
|
||||
|
||||
it "renders hidden field" do
|
||||
expect(page).to have_field "template_id", type: :hidden, with: template.id
|
||||
end
|
||||
|
||||
it "renders Copy options" do
|
||||
expect(page).to have_selector :fieldset, "Copy options"
|
||||
end
|
||||
end
|
||||
@@ -59,6 +59,14 @@ RSpec.describe ProjectsController do
|
||||
end
|
||||
end
|
||||
|
||||
describe "create" do
|
||||
it do
|
||||
expect(post("/projects")).to route_to(
|
||||
controller: "projects", action: "create"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe "destroy_info" do
|
||||
it do
|
||||
expect(get("/projects/123/destroy_info")).to route_to(
|
||||
|
||||
Reference in New Issue
Block a user