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