diff --git a/Gemfile b/Gemfile
index c0bb815f2d36..314c64783da5 100644
--- a/Gemfile
+++ b/Gemfile
@@ -188,7 +188,7 @@ gem 'aws-sdk-core', '~> 3.107'
# File upload via fog + screenshots on travis
gem 'aws-sdk-s3', '~> 1.91'
-gem 'openproject-token', '~> 3.0.1'
+gem 'openproject-token', '~> 4.0'
gem 'plaintext', '~> 0.3.2'
diff --git a/Gemfile.lock b/Gemfile.lock
index 0d29092bf932..eda38d29683a 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -686,7 +686,7 @@ GEM
activesupport (>= 5.0.0)
openproject-octicons (>= 19.7.0)
view_component (>= 3.1, < 4.0)
- openproject-token (3.0.1)
+ openproject-token (4.0.0)
activemodel
os (1.1.4)
ox (2.14.17)
@@ -1127,7 +1127,7 @@ DEPENDENCIES
openproject-reporting!
openproject-storages!
openproject-team_planner!
- openproject-token (~> 3.0.1)
+ openproject-token (~> 4.0)
openproject-two_factor_authentication!
openproject-webhooks!
openproject-xls_export!
diff --git a/app/components/work_packages/share/modal_upsale_component.html.erb b/app/components/work_packages/share/modal_upsale_component.html.erb
new file mode 100644
index 000000000000..2d5f3269a0d7
--- /dev/null
+++ b/app/components/work_packages/share/modal_upsale_component.html.erb
@@ -0,0 +1,17 @@
+<%=
+ component_wrapper(tag: 'turbo-frame') do
+ render Primer::Beta::Blankslate.new(border: true) do |component|
+ component.with_visual_icon(icon: :'op-enterprise-addons', classes: 'upsale-colored')
+ component.with_heading(tag: :h2, classes: 'upsale-colored').with_content(I18n.t(:label_enterprise_addon))
+ component.with_description { I18n.t('mail.sharing.work_packages.enterprise_text') }
+
+ href = "#{OpenProject::Static::Links.links[:upsale][:href]}/?utm_source=unknown&utm_medium=community-edition&utm_campaign=work-package-sharing-modal"
+ component.with_secondary_action(href:) do
+ flex_layout(justify_content: :center) do |flex|
+ flex.with_column(mr: 1) { I18n.t("admin.enterprise.enterprise_link") }
+ flex.with_column { render(Primer::Beta::Octicon.new(icon: :'link-external')) }
+ end
+ end
+ end
+ end
+%>
diff --git a/app/components/work_packages/share/modal_upsale_component.rb b/app/components/work_packages/share/modal_upsale_component.rb
new file mode 100644
index 000000000000..498494814fd4
--- /dev/null
+++ b/app/components/work_packages/share/modal_upsale_component.rb
@@ -0,0 +1,41 @@
+#-- copyright
+# OpenProject is an open source project management software.
+# Copyright (C) 2012-2023 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 WorkPackages
+ module Share
+ class ModalUpsaleComponent < ApplicationComponent
+ include ApplicationHelper
+ include OpTurbo::Streamable
+ include OpPrimer::ComponentHelpers
+
+ def self.wrapper_key
+ "work_package_share_list"
+ end
+ end
+ end
+end
diff --git a/app/controllers/work_packages/shares_controller.rb b/app/controllers/work_packages/shares_controller.rb
index 9b54db9928d9..3b399d3e4088 100644
--- a/app/controllers/work_packages/shares_controller.rb
+++ b/app/controllers/work_packages/shares_controller.rb
@@ -34,6 +34,7 @@ class WorkPackages::SharesController < ApplicationController
before_action :find_share, only: %i[destroy update]
before_action :find_project
before_action :authorize
+ before_action :enterprise_check, only: %i[index]
def index
query = load_query
@@ -52,10 +53,10 @@ def create
find_or_create_users(send_notification: false) do |member_params|
service_call = WorkPackageMembers::CreateOrUpdateService
- .new(user: current_user)
- .call(entity: @work_package,
- user_id: member_params[:user_id],
- role_ids: find_role_ids(params[:member][:role_id]))
+ .new(user: current_user)
+ .call(entity: @work_package,
+ user_id: member_params[:user_id],
+ role_ids: find_role_ids(params[:member][:role_id]))
overall_result.push(service_call)
end
@@ -95,6 +96,12 @@ def destroy
private
+ def enterprise_check
+ return if EnterpriseToken.allows_to?(:work_package_sharing)
+
+ render WorkPackages::Share::ModalUpsaleComponent.new
+ end
+
def respond_with_replace_modal
replace_via_turbo_stream(
component: WorkPackages::Share::ModalBodyComponent.new(work_package: @work_package, shares: @new_shares || find_shares)
@@ -165,10 +172,10 @@ def find_project
def current_visible_member_count
@current_visible_member_count ||= Member
- .joins(:member_roles)
- .of_work_package(@work_package)
- .merge(MemberRole.only_non_inherited)
- .size
+ .joins(:member_roles)
+ .of_work_package(@work_package)
+ .merge(MemberRole.only_non_inherited)
+ .size
end
def load_query
diff --git a/app/services/authorization/enterprise_service.rb b/app/services/authorization/enterprise_service.rb
index d6409849329d..f2333fb65ec6 100644
--- a/app/services/authorization/enterprise_service.rb
+++ b/app/services/authorization/enterprise_service.rb
@@ -47,6 +47,7 @@ class Authorization::EnterpriseService
team_planner_view
two_factor_authentication
work_package_query_relation_columns
+ work_package_sharing
).freeze
def initialize(token)
diff --git a/app/views/projects/settings/modules/_form.html.erb b/app/views/projects/settings/modules/_form.html.erb
index 40c73c1b7ab6..e4fceb99ccac 100644
--- a/app/views/projects/settings/modules/_form.html.erb
+++ b/app/views/projects/settings/modules/_form.html.erb
@@ -37,7 +37,7 @@ See COPYRIGHT and LICENSE files for more details.
<%= spot_icon('enterprise-addons',
inline: true,
size: '1_25',
- classnames: 'upsale-icon_highlighted') %>
+ classnames: 'upsale-colored') %>
<% end %>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index b47f8268a575..3e69e8440657 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -1858,6 +1858,7 @@ en:
label_enterprise_active_users: "%{current}/%{limit} booked active users"
label_enterprise_edition: "Enterprise edition"
label_enterprise_support: "Enterprise support"
+ label_enterprise_addon: "Enterprise add-on"
label_environment: "Environment"
label_estimates_and_time: "Estimates and time"
label_equals: "is"
@@ -2330,6 +2331,7 @@ en:
create_account: "To access this work package you will need to create an %{instance} account. "
open_work_package: "Open work package"
subject: "You have been shared work package #%{id}"
+ enterprise_text: "Share work packages with users who are not members of the project."
summary:
user: "%{user} shared a work package with you with %{role_rights} rights"
group: "%{user} shared a work package with the group %{group} you are a member of"
diff --git a/frontend/src/global_styles/content/_enterprise.sass b/frontend/src/global_styles/content/_enterprise.sass
index 78631572cfa6..76bc9b1956db 100644
--- a/frontend/src/global_styles/content/_enterprise.sass
+++ b/frontend/src/global_styles/content/_enterprise.sass
@@ -52,9 +52,8 @@
justify-content: center
align-items: center
-.upsale-icon
- &_highlighted
- color: $spot-color-feedback-warning-dark
+.upsale-colored
+ color: $spot-color-feedback-warning-dark
.widget-box--blocks--upsale-title
font-weight: 400
diff --git a/lib/redmine/menu_manager/menu_helper.rb b/lib/redmine/menu_manager/menu_helper.rb
index ca403b4ae957..8796224a0cd8 100644
--- a/lib/redmine/menu_manager/menu_helper.rb
+++ b/lib/redmine/menu_manager/menu_helper.rb
@@ -229,7 +229,7 @@ def render_single_menu_node(item, project = nil, menu_class = 'op-menu')
lang: menu_item_locale(item)) do
title_text = ''.html_safe + content_tag(:span, caption, class: 'ellipsis') + badge_for(item)
if item.enterprise_feature.present? && !EnterpriseToken.allows_to?(item.enterprise_feature)
- title_text << (''.html_safe + spot_icon('enterprise-addons', size: '1_25', classnames: 'upsale-icon_highlighted'))
+ title_text << (''.html_safe + spot_icon('enterprise-addons', size: '1_25', classnames: 'upsale-colored'))
end
title_text
end
diff --git a/spec/features/work_packages/share/bulk_sharing_spec.rb b/spec/features/work_packages/share/bulk_sharing_spec.rb
index 494977c43166..9ebf4ff8cfa0 100644
--- a/spec/features/work_packages/share/bulk_sharing_spec.rb
+++ b/spec/features/work_packages/share/bulk_sharing_spec.rb
@@ -32,6 +32,7 @@
RSpec.describe 'Work Packages', 'Bulk Sharing',
:js, :with_cuprite,
+ with_ee: %i[work_package_sharing],
with_flag: { work_package_sharing: true } do
shared_let(:view_work_package_role) { create(:view_work_package_role) }
shared_let(:comment_work_package_role) { create(:comment_work_package_role) }
diff --git a/spec/features/work_packages/share/enterprise_restriction_spec.rb b/spec/features/work_packages/share/enterprise_restriction_spec.rb
new file mode 100644
index 000000000000..76b1cfccd1d4
--- /dev/null
+++ b/spec/features/work_packages/share/enterprise_restriction_spec.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+# -- copyright
+# OpenProject is an open source project management software.
+# Copyright (C) 2023 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 'Work Package Sharing Enterprise Restriction',
+ :js, :with_cuprite,
+ with_flag: { work_package_sharing: true } do
+ shared_let(:view_work_package_role) { create(:view_work_package_role) }
+ shared_let(:comment_work_package_role) { create(:comment_work_package_role) }
+ shared_let(:edit_work_package_role) { create(:edit_work_package_role) }
+
+ shared_let(:sharer_role) do
+ create(:project_role, permissions: %i[view_work_packages
+ view_shared_work_packages
+ share_work_packages])
+ end
+
+ shared_let(:sharer) { create(:user, firstname: 'Sharer', lastname: 'User') }
+
+ shared_let(:project) { create(:project, members: { sharer => [sharer_role] }) }
+ shared_let(:work_package) { create(:work_package, project:) }
+
+ let(:work_package_page) { Pages::FullWorkPackage.new(work_package) }
+ let(:share_modal) { Components::WorkPackages::ShareModal.new(work_package) }
+
+ current_user { sharer }
+
+ before do
+ work_package_page.visit!
+ click_button 'Share'
+ share_modal.expect_open
+ end
+
+ context 'without an enterprise token' do
+ it 'renders an upsale banner' do
+ share_modal.expect_upsale_banner
+ end
+ end
+
+ context 'with an enterprise token', with_ee: %i[work_package_sharing] do
+ it 'renders the share modal' do
+ share_modal.expect_blankslate
+ end
+ end
+end
diff --git a/spec/features/work_packages/share/filter_spec.rb b/spec/features/work_packages/share/filter_spec.rb
index 253d3f5470eb..7f4094f166c0 100644
--- a/spec/features/work_packages/share/filter_spec.rb
+++ b/spec/features/work_packages/share/filter_spec.rb
@@ -31,8 +31,8 @@
require 'spec_helper'
RSpec.describe 'Work package sharing',
- :js,
- :with_cuprite,
+ :js, :with_cuprite,
+ with_ee: %i[work_package_sharing],
with_flag: { work_package_sharing: true } do
shared_let(:view_work_package_role) { create(:view_work_package_role) }
shared_let(:comment_work_package_role) { create(:comment_work_package_role) }
diff --git a/spec/features/work_packages/share/multi_invite_spec.rb b/spec/features/work_packages/share/multi_invite_spec.rb
index 095c6ac2d39e..7aaca0d74ec3 100644
--- a/spec/features/work_packages/share/multi_invite_spec.rb
+++ b/spec/features/work_packages/share/multi_invite_spec.rb
@@ -31,8 +31,8 @@
require 'spec_helper'
RSpec.describe 'Work package sharing',
- :js,
- :with_cuprite,
+ :js, :with_cuprite,
+ with_ee: %i[work_package_sharing],
with_flag: { work_package_sharing: true } do
shared_let(:view_work_package_role) { create(:view_work_package_role) }
shared_let(:comment_work_package_role) { create(:comment_work_package_role) }
diff --git a/spec/features/work_packages/share_spec.rb b/spec/features/work_packages/share_spec.rb
index 5e23d61e0f54..010ff5ae340a 100644
--- a/spec/features/work_packages/share_spec.rb
+++ b/spec/features/work_packages/share_spec.rb
@@ -31,8 +31,8 @@
require 'spec_helper'
RSpec.describe 'Work package sharing',
- :js,
- :with_cuprite,
+ :js, :with_cuprite,
+ with_ee: %i[work_package_sharing],
with_flag: { work_package_sharing: true } do
shared_let(:view_work_package_role) { create(:view_work_package_role) }
shared_let(:comment_work_package_role) { create(:comment_work_package_role) }
diff --git a/spec/support/components/work_packages/share_modal.rb b/spec/support/components/work_packages/share_modal.rb
index d8ef2741e02a..3bda399e64b1 100644
--- a/spec/support/components/work_packages/share_modal.rb
+++ b/spec/support/components/work_packages/share_modal.rb
@@ -234,7 +234,9 @@ def change_role(user, role_name)
within user_row(user) do
find('[data-test-selector="op-share-wp-update-role"]').click
- find('.ActionListContent', text: role_name).click
+ within '.ActionListWrap' do
+ click_button role_name
+ end
end
end
@@ -338,6 +340,13 @@ def select_not_existing_user_option(email)
select_text: "Send invite to\"#{email}\"",
results_selector: 'body'
end
+
+ def expect_upsale_banner
+ within modal_element do
+ expect(page)
+ .to have_text(I18n.t(:label_enterprise_addon))
+ end
+ end
end
end
end