Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
6 changes: 3 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -913,7 +913,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)
Expand Down Expand Up @@ -1692,7 +1692,7 @@ DEPENDENCIES
openproject-reporting!
openproject-storages!
openproject-team_planner!
openproject-token (~> 8.8.2)
openproject-token (~> 8.9.0)
openproject-two_factor_authentication!
openproject-webhooks!
openproject-wikis!
Expand Down Expand Up @@ -2074,7 +2074,7 @@ CHECKSUMS
openproject-reporting (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)
Expand Down
Binary file added app/assets/videos/enterprise/sprint-sharing.mp4
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -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
%>

Expand All @@ -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
%>
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,17 @@ class SharingFormComponent < ApplicationComponent
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class BacklogSettingsContract < ::ModelContract
validate :validate_global_sprint_sharer_uniqueness
validates :sprint_sharing, presence: true
validates :sprint_sharing, inclusion: { in: Project::SPRINT_SHARING_MODES }, allow_blank: true
validate :validate_sprint_sharing_in_ee_token

def validate_model? = false

Expand All @@ -64,5 +65,19 @@ def validate_global_sprint_sharer_uniqueness
end
end
end

def validate_sprint_sharing_in_ee_token
if !model.not_sharing_sprints? &&
!EnterpriseToken.allows_to?(:sprint_sharing) &&
sprint_sharing_changed?
errors.add :sprint_sharing,
:enterprise_plan_required,
plan_name: I18n.t("ee.upsell.plan_name", plan: OpenProject::Token.lowest_plan_for(:sprint_sharing))
end
end

def sprint_sharing_changed?
model.settings_change&.any? { it.key?("sprint_sharing") }
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -36,27 +36,47 @@ class SharingForm < ApplicationForm
# TODO: Remove this hidden field, once the `radio_button_group` supports rendering
# the hidden empty field.
# The purpose of the hidden field is to ensure we submit the `sprint_sharing` field
# even if no radio button is chosen. Otherwise, the submitted form will not include
# even if:
# * no radio button is chosen.
# * the selected option is disabled because of a missing EE token.
# Otherwise, the submitted form will not include
# the field at all and the save request will return success when in fact no setting
# 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,
label: I18n.t("projects.settings.backlog_sharing.sprint_sharing")
) do |group|
Project::SPRINT_SHARING_MODES.each do |option|
group.radio_button(
label: sharing_option_text(option, :label),
value: option,
checked: checked?(option),
disabled: disabled?(option),
caption: caption_for(option),
data: { "show-when-value-selected-target": "cause" }
)
end
group.radio_button(
label: sharing_option_label(Project::NO_SHARING),
value: Project::NO_SHARING,
caption: sharing_option_caption(Project::NO_SHARING),
data: { "show-when-value-selected-target": "cause" }
)
group.radio_button(
label: sharing_option_label(Project::SHARE_ALL_PROJECTS),
value: Project::SHARE_ALL_PROJECTS,
disabled: only_fallback_allowed || share_all_projects_disabled?,
caption: shared_all_projects_caption,
data: { "show-when-value-selected-target": "cause" }
)
group.radio_button(
label: sharing_option_label(Project::SHARE_SUBPROJECTS),
value: Project::SHARE_SUBPROJECTS,
disabled: only_fallback_allowed,
caption: sharing_option_caption(Project::SHARE_SUBPROJECTS),
data: { "show-when-value-selected-target": "cause" }
)
group.radio_button(
label: sharing_option_label(Project::RECEIVE_SHARED),
value: Project::RECEIVE_SHARED,
disabled: only_fallback_allowed,
caption: sharing_option_caption(Project::RECEIVE_SHARED),
data: { "show-when-value-selected-target": "cause" }
)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While less elegant than using the loop, I found it easier to read compared to having the abstraction of the loop also manifest and complicate the called methods for determining whether the button is disabled and which caption to show.

end

sharing_form.html_content { banner_for(Project::SHARE_SUBPROJECTS, type: :info) }
Expand All @@ -69,32 +89,27 @@ class SharingForm < ApplicationForm
)
end

def initialize(only_fallback_allowed: false)
super()
@only_fallback_allowed = only_fallback_allowed
end

private

def checked?(option)
option == model.sprint_sharing
attr_reader :only_fallback_allowed

def sharing_option_caption(option)
sharing_option_text(option, :caption)
end

def disabled?(option)
option == Project::SHARE_ALL_PROJECTS && share_all_projects_disabled?
def sharing_option_label(option)
sharing_option_text(option, :label)
end

def sharing_option_text(option, key, **)
I18n.t("projects.settings.backlog_sharing.options.#{option}.#{key}", **)
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
else
sharing_option_text(option, :caption)
end
end

def share_all_projects_disabled?
global_sprint_sharer && global_sprint_sharer != model
end
Expand All @@ -117,6 +132,16 @@ def banner_for(option, type: :info)
end
end
end

def shared_all_projects_caption
if !only_fallback_allowed && !share_all_projects_disabled?
sharing_option_caption(Project::SHARE_ALL_PROJECTS)
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
video: "enterprise/sprint-sharing.mp4"
) 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 %>
14 changes: 13 additions & 1 deletion modules/backlogs/config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -227,6 +226,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"
Expand Down Expand Up @@ -278,4 +284,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"
1 change: 1 addition & 0 deletions modules/backlogs/lib/open_project/backlogs/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ def self.settings
patch_with_namespace :WorkPackages, :SetAttributesService
patch_with_namespace :WorkPackages, :BaseContract
patch_with_namespace :WorkPackages, :UpdateContract
patch_with_namespace :Projects, :CopyService
patch_with_namespace :API, :V3, :WorkPackages, :EagerLoading, :Checksum
patch_with_namespace :API, :V3, :WorkPackages, :Schema, :SpecificWorkPackageSchema

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
# ++

module OpenProject::Backlogs::Patches::CopyServicePatch
extend ActiveSupport::Concern

included do
prepend InstanceMethods
end

module InstanceMethods
def clean_settings_attributes!(settings)
# There can be only one project sharing with all projects.
if settings["sprint_sharing"] == Projects::SprintSharing::SHARE_ALL_PROJECTS ||
!EnterpriseToken.allows_to?(:sprint_sharing)
settings.delete("sprint_sharing")
end

super
end
end
end
Loading
Loading