From eb09ce0c1bf182d1115b4fa9b32fa1e97dda8bf0 Mon Sep 17 00:00:00 2001 From: ulferts Date: Tue, 5 May 2026 12:08:14 +0200 Subject: [PATCH] enterprise banner with fallback on sprint sharing settings --- Gemfile | 2 +- Gemfile.lock | 6 +-- .../backlogs/sharing_form_component.html.erb | 8 +++- .../backlogs/sharing_form_component.rb | 6 ++- .../settings/backlogs/sharing_form.rb | 31 ++++++++---- .../settings/backlog_sharings/show.html.erb | 15 +++++- modules/backlogs/config/locales/en.yml | 14 +++++- .../settings/backlog_sharing_settings_spec.rb | 47 ++++++++++++++++++- 8 files changed, 110 insertions(+), 19 deletions(-) diff --git a/Gemfile b/Gemfile index 40ec2028be4..ac3507e6b53 100644 --- a/Gemfile +++ b/Gemfile @@ -208,7 +208,7 @@ gem "aws-sdk-core", "~> 3.244" # File upload via fog + screenshots on travis gem "aws-sdk-s3", "~> 1.217" -gem "openproject-token", "~> 8.8.2" +gem "openproject-token", "~> 8.9.0" gem "plaintext", "~> 0.3.7" diff --git a/Gemfile.lock b/Gemfile.lock index e77db09fed0..1853c63e80c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -914,7 +914,7 @@ GEM activesupport (>= 7.2.0) openproject-octicons (>= 19.34.0) view_component (>= 3.1, < 5.0) - openproject-token (8.8.2) + openproject-token (8.9.0) activemodel openssl (4.0.1) openssl-signature_algorithm (1.3.0) @@ -1693,7 +1693,7 @@ DEPENDENCIES openproject-resource_management! openproject-storages! openproject-team_planner! - openproject-token (~> 8.8.2) + openproject-token (~> 8.9.0) openproject-two_factor_authentication! openproject-webhooks! openproject-wikis! @@ -2075,7 +2075,7 @@ CHECKSUMS openproject-resource_management (1.0.0) openproject-storages (1.0.0) openproject-team_planner (1.0.0) - openproject-token (8.8.2) sha256=081cbff7269d92a82fa1d63e9e09c87b70d47d7aefadcbb80d1e7368bc2cf096 + openproject-token (8.9.0) sha256=aa08c144889010750de4edaf61f8614ccb82ac6c63beef1d3a21c6a222358605 openproject-two_factor_authentication (1.0.0) openproject-webhooks (1.0.0) openproject-wikis (1.0.0) diff --git a/modules/backlogs/app/components/projects/settings/backlogs/sharing_form_component.html.erb b/modules/backlogs/app/components/projects/settings/backlogs/sharing_form_component.html.erb index 0e5b2c1050a..03bae13cf22 100644 --- a/modules/backlogs/app/components/projects/settings/backlogs/sharing_form_component.html.erb +++ b/modules/backlogs/app/components/projects/settings/backlogs/sharing_form_component.html.erb @@ -29,7 +29,11 @@ See COPYRIGHT and LICENSE files for more details. <%= render(Primer::Beta::Text.new(tag: :div, color: :muted, mb: 3)) do - I18n.t("backlogs.sharing_description") + if only_fallback_allowed + t(".sharing_fallback_description") + else + t(".sharing_description") + end end %> @@ -40,6 +44,6 @@ See COPYRIGHT and LICENSE files for more details. method: :patch, data: { turbo_frame: "_top", controller: "show-when-value-selected" } ) do |f| - render(Projects::Settings::Backlogs::SharingForm.new(f)) + render(Projects::Settings::Backlogs::SharingForm.new(f, only_fallback_allowed:)) end %> diff --git a/modules/backlogs/app/components/projects/settings/backlogs/sharing_form_component.rb b/modules/backlogs/app/components/projects/settings/backlogs/sharing_form_component.rb index 94847d32bca..db1dc921281 100644 --- a/modules/backlogs/app/components/projects/settings/backlogs/sharing_form_component.rb +++ b/modules/backlogs/app/components/projects/settings/backlogs/sharing_form_component.rb @@ -35,15 +35,17 @@ module Projects include ApplicationHelper include OpPrimer::ComponentHelpers - def initialize(project:) + def initialize(project:, + only_fallback_allowed: false) super @project = project + @only_fallback_allowed = only_fallback_allowed end private - attr_reader :project + attr_reader :project, :only_fallback_allowed end end end diff --git a/modules/backlogs/app/forms/projects/settings/backlogs/sharing_form.rb b/modules/backlogs/app/forms/projects/settings/backlogs/sharing_form.rb index 38eac5f43c5..a3f07efd996 100644 --- a/modules/backlogs/app/forms/projects/settings/backlogs/sharing_form.rb +++ b/modules/backlogs/app/forms/projects/settings/backlogs/sharing_form.rb @@ -41,7 +41,7 @@ module Projects # is saved. # Ideally the hidden field should automatically be rendered by the `radio_button_group` # helper, similar to how the `collection_radio_buttons` rails helper does. - sharing_form.hidden(name: :sprint_sharing, value: "") + sharing_form.hidden(name: :sprint_sharing, value: model.sprint_sharing) sharing_form.radio_button_group( name: :sprint_sharing, @@ -69,14 +69,22 @@ module Projects ) end + def initialize(only_fallback_allowed: false) + super() + @only_fallback_allowed = only_fallback_allowed + end + private + attr_reader :only_fallback_allowed + def checked?(option) option == model.sprint_sharing end def disabled?(option) - option == Project::SHARE_ALL_PROJECTS && share_all_projects_disabled? + (only_fallback_allowed && option != Projects::SprintSharing::NO_SHARING) || + (option == Project::SHARE_ALL_PROJECTS && share_all_projects_disabled?) end def sharing_option_text(option, key, **) @@ -84,12 +92,9 @@ module Projects end def caption_for(option) - if disabled?(option) - if User.current.allowed_in_project?(:view_project, global_sprint_sharer) - sharing_option_text(option, :disabled_caption, name: global_sprint_sharer.name) - else - sharing_option_text(option, :disabled_caption_anonymous) - end + case option + when Project::SHARE_ALL_PROJECTS + shared_all_projects_caption else sharing_option_text(option, :caption) end @@ -117,6 +122,16 @@ module Projects end end end + + def shared_all_projects_caption + if !disabled?(Project::SHARE_ALL_PROJECTS) + sharing_option_text(Project::SHARE_ALL_PROJECTS, :caption) + elsif User.current.allowed_in_project?(:view_project, global_sprint_sharer) + sharing_option_text(Project::SHARE_ALL_PROJECTS, :disabled_caption, name: global_sprint_sharer.name) + else + sharing_option_text(Project::SHARE_ALL_PROJECTS, :disabled_caption_anonymous) + end + end end end end diff --git a/modules/backlogs/app/views/projects/settings/backlog_sharings/show.html.erb b/modules/backlogs/app/views/projects/settings/backlog_sharings/show.html.erb index 9bd54844005..dae891d04a4 100644 --- a/modules/backlogs/app/views/projects/settings/backlog_sharings/show.html.erb +++ b/modules/backlogs/app/views/projects/settings/backlog_sharings/show.html.erb @@ -35,5 +35,18 @@ See COPYRIGHT and LICENSE files for more details. ) ) %> - <%= render(Projects::Settings::Backlogs::SharingFormComponent.new(project: @project)) %> + <% with_enterprise_banner_guard( + :sprint_sharing, + inactive_guard: !@project.not_sharing_sprints?, + variant: :large, + image: "enterprise/project-creation-wizard.png" + ) do %> + <%= render(EnterpriseEdition::BannerComponent.new(:sprint_sharing, variant: :inline)) %> + <%= render( + Projects::Settings::Backlogs::SharingFormComponent.new( + project: @project, + only_fallback_allowed: !EnterpriseToken.allows_to?(:sprint_sharing) + ) + ) %> + <% end %> <% end %> diff --git a/modules/backlogs/config/locales/en.yml b/modules/backlogs/config/locales/en.yml index 15889180aa9..0fc4c5cd1de 100644 --- a/modules/backlogs/config/locales/en.yml +++ b/modules/backlogs/config/locales/en.yml @@ -98,7 +98,6 @@ en: definition_of_done: "Definition of Done" definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." done_status: "Done status" - sharing_description: "This project can either share its own sprints, receive shared sprints or handle sprints independently (no sharing)." sharing: "Sharing" label_burndown_chart: "Burndown chart" label_sprint_board: "Sprint board" @@ -206,6 +205,13 @@ en: story_points: "Story points" story_points_ideal: "Story points (ideal)" + ee: + features: + sprint_sharing: "Sprint sharing" + upsell: + sprint_sharing: + description: "Share sprints across projects to align teams and coordinate work in scaled agile setups (SAFe)." + label_backlog: "Backlog" label_backlog_bucket_edit: "Edit backlog bucket" label_backlog_bucket_new: "New backlog bucket" @@ -257,4 +263,10 @@ en: info: "Sharing a sprint will share the name, status and the start and finish dates in all projects. These cannot be modified in projects that receive and use these sprints." sprint_sharing: Share sprints + backlogs: + sharing_form_component: + sharing_description: "This project can either share its own sprints, receive shared sprints or handle sprints independently (no sharing)." + sharing_fallback_description: "Lacking a corporate enterprise plan, the sharing options are limited to the project's own sprints. The currently active setting remains active." + + remaining_hours: "remaining work" diff --git a/modules/backlogs/spec/features/projects/settings/backlog_sharing_settings_spec.rb b/modules/backlogs/spec/features/projects/settings/backlog_sharing_settings_spec.rb index f498dd1422c..60f208f4341 100644 --- a/modules/backlogs/spec/features/projects/settings/backlog_sharing_settings_spec.rb +++ b/modules/backlogs/spec/features/projects/settings/backlog_sharing_settings_spec.rb @@ -42,7 +42,8 @@ RSpec.describe "Backlogs project settings sprint sharing", :js do login_as current_user end - context "with share_sprint permission" do + context "with share_sprint permission and enterprise token", + with_ee: [:sprint_sharing] do it "displays and stores sprint sharing settings" do visit project_settings_backlog_sharing_path(project) @@ -127,6 +128,50 @@ RSpec.describe "Backlogs project settings sprint sharing", :js do end end + context "with share_sprint permission but no enterprise token" do + context "without existing sharing setting in the project" do + it "shows an enterprise token teaser" do + visit project_settings_backlog_sharing_path(project) + + expect(page).to have_text("Share sprints across projects to align teams") + + expect(page).to have_no_field("Don't share") + expect(page).to have_no_field("All projects") + expect(page).to have_no_field("Subprojects") + expect(page).to have_no_field("Receive shared sprints") + end + end + + context "with existing sharing setting in the project" do + before do + project.update!(sprint_sharing: "receive_shared") + end + + it "shows the existing sharing setting but disables them except for `Don't share`" do + visit project_settings_backlog_sharing_path(project) + + # All radio buttons are present with the selected option displayed. + # But all except the "Don't share" option are disabled. + expect(page).to have_unchecked_field("Don't share") + expect(page).to have_unchecked_field("All projects", disabled: true) + expect(page).to have_unchecked_field("Subprojects", disabled: true) + expect(page).to have_checked_field("Receive shared sprints", disabled: true) + + choose("Don't share") + + click_button "Save" + + # Now that the `Don't share` option is selected, the large enterprise banner is displayed. + expect(page).to have_text("Share sprints across projects to align teams") + + expect(page).to have_no_field("Don't share") + expect(page).to have_no_field("All projects") + expect(page).to have_no_field("Subprojects") + expect(page).to have_no_field("Receive shared sprints") + end + end + end + context "without share_sprint permission" do let(:permissions) { %i[create_sprints select_done_statuses] }