diff --git a/app/components/enterprise_edition/banner_component.html.erb b/app/components/enterprise_edition/banner_component.html.erb
index fbaeaaa2c48..b634dee2a86 100644
--- a/app/components/enterprise_edition/banner_component.html.erb
+++ b/app/components/enterprise_edition/banner_component.html.erb
@@ -93,7 +93,7 @@
classes: class_names("op-enterprise-banner--close-icon", classes),
scheme: :invisible,
tag: :a,
- href: dismiss_enterprise_banner_path(feature_key: @dismiss_key),
+ href: dismiss_enterprise_banner_path(feature_key: @feature_key, dismiss_key: @dismiss_key),
data: { turbo_stream: true, turbo_method: :post },
icon: :x,
aria: { label: t("ee.upsell.hide_banner") }
diff --git a/app/controllers/my/enterprise_banners_controller.rb b/app/controllers/my/enterprise_banners_controller.rb
index c9cca555a87..d025c907492 100644
--- a/app/controllers/my/enterprise_banners_controller.rb
+++ b/app/controllers/my/enterprise_banners_controller.rb
@@ -63,10 +63,8 @@ class My::EnterpriseBannersController < ApplicationController
private
def get_feature_key
- raw_key = params[:feature_key]
-
- @dismiss_key = raw_key
- @feature_key = raw_key.gsub(/_trial$/, "").to_sym
+ @feature_key = params[:feature_key].to_sym
+ @dismiss_key = params[:dismiss_key].presence&.to_sym || @feature_key
render_400 unless OpenProject::Token.lowest_plan_for(@feature_key)
end
diff --git a/app/forms/users/invitation/project_step/form.rb b/app/forms/users/invitation/project_step/form.rb
index ea8496849dc..12437cea14d 100644
--- a/app/forms/users/invitation/project_step/form.rb
+++ b/app/forms/users/invitation/project_step/form.rb
@@ -57,18 +57,34 @@ module Users::Invitation::ProjectStep
name: :principal_type,
visually_hide_label: true
) do |radio_group|
- radio_group.radio_button(value: "User",
- checked: model.principal_type.nil? || model.principal_type == "User",
- label: User.model_name.human,
- caption: I18n.t("users.invite_user_modal.type.user.description"))
- radio_group.radio_button(value: "Group",
- checked: model.principal_type == "Group",
- label: Group.model_name.human,
- caption: I18n.t("users.invite_user_modal.type.group.description"))
- radio_group.radio_button(value: "PlaceholderUser",
- checked: model.principal_type == "PlaceholderUser",
- label: PlaceholderUser.model_name.human,
- caption: I18n.t("users.invite_user_modal.type.placeholder_user.description"))
+ radio_group.radio_button(
+ value: "User",
+ checked: model.principal_type.nil? || model.principal_type == "User",
+ label: User.model_name.human,
+ caption: I18n.t("users.invite_user_modal.type.user.description")
+ )
+ radio_group.radio_button(
+ value: "Group",
+ checked: model.principal_type == "Group",
+ label: Group.model_name.human,
+ caption: I18n.t("users.invite_user_modal.type.group.description")
+ )
+
+ radio_group.radio_button(
+ value: "PlaceholderUser",
+ disabled: !EnterpriseToken.allows_to?(:placeholder_users),
+ checked: model.principal_type == "PlaceholderUser",
+ label: PlaceholderUser.model_name.human,
+ caption: I18n.t("users.invite_user_modal.type.placeholder_user.description")
+ )
+ end
+
+ unless EnterpriseToken.allows_to?(:placeholder_users)
+ f.html_content do
+ render(EnterpriseEdition::BannerComponent.new(:placeholder_users,
+ dismissable: true,
+ dismiss_key: "invitation_placeholder_users"))
+ end
end
end
end
diff --git a/app/models/users/Invitation/form_model.rb b/app/models/users/Invitation/form_model.rb
index a4b548f9475..d6d4cf3e9df 100644
--- a/app/models/users/Invitation/form_model.rb
+++ b/app/models/users/Invitation/form_model.rb
@@ -40,11 +40,21 @@ module Users::Invitation
attribute :message, :text, default: nil
validates :project_id, presence: true, on: :project_step
- validates :principal_type, inclusion: { in: %w[User PlaceholderUser Group] }, on: :project_step
+ validates :principal_type,
+ inclusion: { in: ->(*) { available_principal_types } },
+ on: :project_step
validates :id_or_email, presence: true, on: :principal_step
validates :role_id, presence: true, on: :principal_step
+ def self.available_principal_types
+ if EnterpriseToken.allows_to?(:placeholder_users)
+ %w[User PlaceholderUser Group]
+ else
+ %w[User Group]
+ end
+ end
+
def project_name
project&.name || project_id
end
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 050f8bf007e..74540d55d65 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -379,7 +379,7 @@ en:
danger_dialog:
confirmation_live_message_checked: "The button to proceed is now active."
- confirmation_live_message_unchecked: "The button to proceed is now inactive. You need to tick the checkbox to continue."
+ confirmation_live_message_unchecked: "The button to proceed is now inactive. You need to tick the checkbox to continue."
op_dry_validation:
or: "or"
@@ -733,8 +733,6 @@ en:
title: "Add placeholder user to %{project_name}"
title_no_ee: "Placeholder user (Enterprise edition only add-on)"
description: "Has no access to the project and no emails are sent out."
- description_no_ee: 'Has no access to the project and no emails are sent out.
-
Check out the Enterprise edition'
already_member_message: "Already a member of %{project}"
principal:
diff --git a/spec/models/users/invitation/form_model_spec.rb b/spec/models/users/invitation/form_model_spec.rb
new file mode 100644
index 00000000000..ffeac270a1a
--- /dev/null
+++ b/spec/models/users/invitation/form_model_spec.rb
@@ -0,0 +1,250 @@
+# 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 Users::Invitation::FormModel do
+ subject(:form_model) { described_class.new }
+
+ let(:project) { build_stubbed(:project) }
+
+ describe "validations" do
+ describe "principal_type" do
+ context "with enterprise token allowing placeholder users" do
+ before do
+ allow(EnterpriseToken)
+ .to receive(:allows_to?)
+ .with(:placeholder_users)
+ .and_return(true)
+ end
+
+ context "when validating on project_step" do
+ it "accepts User as principal_type" do
+ form_model.project = project
+ form_model.principal_type = "User"
+
+ expect(form_model).to be_valid(:project_step)
+ end
+
+ it "accepts PlaceholderUser as principal_type" do
+ form_model.project = project
+ form_model.principal_type = "PlaceholderUser"
+
+ expect(form_model).to be_valid(:project_step)
+ end
+
+ it "accepts Group as principal_type" do
+ form_model.project = project
+ form_model.principal_type = "Group"
+
+ expect(form_model).to be_valid(:project_step)
+ end
+
+ it "rejects invalid principal_type" do
+ form_model.project = project
+ form_model.principal_type = "InvalidType"
+
+ expect(form_model).not_to be_valid(:project_step)
+ expect(form_model.errors[:principal_type]).to be_present
+ end
+
+ it "rejects nil principal_type" do
+ form_model.project = project
+ form_model.principal_type = nil
+
+ expect(form_model).not_to be_valid(:project_step)
+ expect(form_model.errors[:principal_type]).to be_present
+ end
+ end
+ end
+
+ context "without enterprise token allowing placeholder users" do
+ before do
+ allow(EnterpriseToken)
+ .to receive(:allows_to?)
+ .with(:placeholder_users)
+ .and_return(false)
+ end
+
+ context "when validating on project_step" do
+ it "accepts User as principal_type" do
+ form_model.project = project
+ form_model.principal_type = "User"
+
+ expect(form_model).to be_valid(:project_step)
+ end
+
+ it "accepts Group as principal_type" do
+ form_model.project = project
+ form_model.principal_type = "Group"
+
+ expect(form_model).to be_valid(:project_step)
+ end
+
+ it "rejects PlaceholderUser as principal_type" do
+ form_model.project = project
+ form_model.principal_type = "PlaceholderUser"
+
+ expect(form_model).not_to be_valid(:project_step)
+ expect(form_model.errors[:principal_type]).to be_present
+ end
+
+ it "rejects invalid principal_type" do
+ form_model.project = project
+ form_model.principal_type = "InvalidType"
+
+ expect(form_model).not_to be_valid(:project_step)
+ expect(form_model.errors[:principal_type]).to be_present
+ end
+
+ it "rejects nil principal_type" do
+ form_model.project = project
+ form_model.principal_type = nil
+
+ expect(form_model).not_to be_valid(:project_step)
+ expect(form_model.errors[:principal_type]).to be_present
+ end
+ end
+ end
+ end
+
+ describe "project_id" do
+ context "when validating on project_step" do
+ it "requires project_id to be present" do
+ form_model.principal_type = "User"
+
+ expect(form_model).not_to be_valid(:project_step)
+ expect(form_model.errors[:project_id]).to be_present
+ end
+
+ it "is valid when project_id is present" do
+ form_model.project = project
+ form_model.principal_type = "User"
+
+ expect(form_model).to be_valid(:project_step)
+ end
+ end
+ end
+
+ describe "id_or_email" do
+ context "when validating on principal_step" do
+ it "requires id_or_email to be present" do
+ form_model.role_id = 1
+
+ expect(form_model).not_to be_valid(:principal_step)
+ expect(form_model.errors[:id_or_email]).to be_present
+ end
+
+ it "is valid when id_or_email is present" do
+ form_model.id_or_email = "user@example.com"
+ form_model.role_id = 1
+
+ expect(form_model).to be_valid(:principal_step)
+ end
+ end
+ end
+
+ describe "role_id" do
+ context "when validating on principal_step" do
+ it "requires role_id to be present" do
+ form_model.id_or_email = "user@example.com"
+
+ expect(form_model).not_to be_valid(:principal_step)
+ expect(form_model.errors[:role_id]).to be_present
+ end
+
+ it "is valid when role_id is present" do
+ form_model.id_or_email = "user@example.com"
+ form_model.role_id = 1
+
+ expect(form_model).to be_valid(:principal_step)
+ end
+ end
+ end
+ end
+
+ describe ".available_principal_types" do
+ context "with enterprise token allowing placeholder users" do
+ before do
+ allow(EnterpriseToken)
+ .to receive(:allows_to?)
+ .with(:placeholder_users)
+ .and_return(true)
+ end
+
+ it "returns User, PlaceholderUser, and Group" do
+ expect(described_class.available_principal_types).to eq(%w[User PlaceholderUser Group])
+ end
+ end
+
+ context "without enterprise token allowing placeholder users" do
+ before do
+ allow(EnterpriseToken)
+ .to receive(:allows_to?)
+ .with(:placeholder_users)
+ .and_return(false)
+ end
+
+ it "returns User and Group" do
+ expect(described_class.available_principal_types).to eq(%w[User Group])
+ end
+ end
+ end
+
+ describe "#project_name" do
+ it "returns the project name when project is present" do
+ form_model.project = project
+ expect(form_model.project_name).to eq(project.name)
+ end
+
+ it "returns the project_id when project is not present" do
+ form_model.project_id = 123
+ expect(form_model.project_name).to eq(123)
+ end
+ end
+
+ describe "#to_h" do
+ it "returns a hash with all attributes" do
+ form_model.project_id = 1
+ form_model.role_id = 2
+ form_model.principal_type = "User"
+ form_model.id_or_email = "user@example.com"
+ form_model.message = "Welcome!"
+
+ expect(form_model.to_h).to eq({
+ project_id: 1,
+ role_id: 2,
+ principal_type: "User",
+ id_or_email: "user@example.com",
+ message: "Welcome!"
+ })
+ end
+ end
+end