Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#55163] Build project-list sharing modal #15971

Merged
merged 68 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from 57 commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
96f1a7c
Trigger dialog from PageHeader "share" button
aaron-contreras Jun 25, 2024
0a45c94
Add a dialog method so we can dynamically initialize the modal
klaustopher Jun 26, 2024
65f5232
Fix loading of concern
klaustopher Jun 26, 2024
48a3aec
Render sharing modal for project lists
aaron-contreras Jun 27, 2024
8cd4f8c
Disable cops
klaustopher Jun 27, 2024
f643faf
Fix css top-layer issue for invitee dropdown on sharing modals
aaron-contreras Jun 27, 2024
5492893
Fix invalid YAML formatting
aaron-contreras Jun 27, 2024
fcb45b5
Lint locales/en.yml
aaron-contreras Jun 27, 2024
c3ff1af
Add Project List sharing dialog title translation
aaron-contreras Jun 27, 2024
4fbd3cc
Enfore small size of action menu dropdown. Otherwise it would be way …
HDinger Jul 1, 2024
6ed97fa
Use smaller variant for ActionMenus of sharing dialog to ensure that …
HDinger Jul 1, 2024
231e44a
Merge branch 'dev' into implementation/55163-build-sharing-modal
klaustopher Jul 1, 2024
708e5ae
Introduce `SharingStrategy` concept to not litter the controller with…
klaustopher Jul 1, 2024
30139b9
EntityMemberQuery -> NonInheritedMemberQuery
klaustopher Jul 1, 2024
540e1b4
Allow filtering `entity_type` to `ProjectQuery`
klaustopher Jul 1, 2024
778ca35
Merge branch 'dev' into implementation/55163-build-sharing-modal
klaustopher Jul 1, 2024
7280429
Implement contracts for sharing project queries
klaustopher Jul 1, 2024
ee78a83
BaseStrategy to show what can be implemented
klaustopher Jul 1, 2024
a573110
BaseStrategy to show what can be implemented
klaustopher Jul 1, 2024
28d9de0
Remove redundant naming from Strategy
klaustopher Jul 1, 2024
e3f2cc2
Only pass strategy into the modal and modal body
klaustopher Jul 1, 2024
218ee5b
Invite User Form uses strategy
klaustopher Jul 1, 2024
2c9a975
Use Strategy in all components
klaustopher Jul 1, 2024
74cd593
Merge branch 'dev' into implementation/55163-build-sharing-modal
klaustopher Jul 2, 2024
6445156
WorkPackageStrategy was missing the BaseStrategy
klaustopher Jul 2, 2024
8be4917
Merge branch 'dev' into implementation/55163-build-sharing-modal
klaustopher Jul 2, 2024
b4927cc
Correctly pass all arguments for render calls
klaustopher Jul 2, 2024
444df51
Extract blank slate config into its own component and allow overriding
klaustopher Jul 2, 2024
5f46802
Allow adding custom modal component via strategy
klaustopher Jul 2, 2024
dccbdca
Add a `keepOpenOnSubmit` attribute to the dialog and remove duplicate…
klaustopher Jul 2, 2024
4f93c2e
Clean up JS code
klaustopher Jul 2, 2024
4a60d82
Use correct ID for the modal dialog
klaustopher Jul 2, 2024
5635e36
Add an injectable body for the project modal
klaustopher Jul 2, 2024
78215ce
Do not display my own public queries twice
klaustopher Jul 2, 2024
327a614
Hide sharing behind the feature flag
klaustopher Jul 2, 2024
23990f0
Only add one endpoint to toggle public flag on product query
klaustopher Jul 2, 2024
1b5ecb9
Public lists should be shown in public, not private if owned
klaustopher Jul 2, 2024
58156a4
Implement the publish button
klaustopher Jul 2, 2024
e4da1d0
Add custom permission checks based on SharingStrategy
klaustopher Jul 2, 2024
018ff2c
Fix toggle public specs
klaustopher Jul 2, 2024
55491c4
Use the `#editable?` method in Projects::IndexPageHeaderComponent
klaustopher Jul 2, 2024
f7145b7
Fix permission check for non-project scoped permissions
klaustopher Jul 2, 2024
58fd4fe
Hide share button in header component if user can't view shares
aaron-contreras Jul 2, 2024
2b24c4d
Render a custom empty state component when the project query is public
aaron-contreras Jul 2, 2024
9cf1e08
Add translations to custom empty state component
aaron-contreras Jul 2, 2024
09fea52
Revert "Hide share button in header component if user can't view shares"
aaron-contreras Jul 2, 2024
f167e73
i18n role names and descriptions for project query sharing
aaron-contreras Jul 2, 2024
2cb3e10
Correct copyright
aaron-contreras Jul 2, 2024
4020db7
Fix copyright notices that still mention 2023
klaustopher Jul 3, 2024
15aa897
Merge branch 'dev' into implementation/55163-build-sharing-modal
klaustopher Jul 3, 2024
825a62d
Allow multiple custom components
klaustopher Jul 3, 2024
d88d2ed
Add translations to the public flag component
klaustopher Jul 3, 2024
18c1f1a
Add an access warning
klaustopher Jul 3, 2024
a7403fe
Add tri-state to empty state
klaustopher Jul 3, 2024
f6b9dae
Add documentation for keep-open-on-submit
klaustopher Jul 3, 2024
c8fa05b
Add ShareDialog preview
klaustopher Jul 3, 2024
0dcdd58
Add descriptions for roles
klaustopher Jul 3, 2024
90dcd9d
Fix typo in dialog documentation
aaron-contreras Jul 3, 2024
05d05ad
Extract 'show toggle share dialog' button condition
aaron-contreras Jul 3, 2024
467fe56
Extract blankslate config setup into separate methods
aaron-contreras Jul 3, 2024
ff8a5f8
Set consistent order in state initialization
aaron-contreras Jul 3, 2024
e964e00
Use gap as spacing between rows in the share modal
aaron-contreras Jul 3, 2024
d0d820d
Split complex guard clause into two
aaron-contreras Jul 3, 2024
1f8d492
Rename `#checked` to `#published?`
aaron-contreras Jul 3, 2024
5fd3b5a
Split multi-check conditionals in project/menu.rb
aaron-contreras Jul 3, 2024
1831b76
Fix typo in SharingStrategies::BaseStrategy
aaron-contreras Jul 3, 2024
c600054
Fix .erb tag
aaron-contreras Jul 3, 2024
dee8171
Merge branch 'release/14.3' into implementation/55163-build-sharing-m…
klaustopher Jul 4, 2024
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
40 changes: 14 additions & 26 deletions app/components/projects/index_page_header_component.html.erb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<% if show_state? %>
<%=
render(Primer::OpenProject::PageHeader.new) do |header|
header.with_title(data: { 'test-selector': 'project-query-name'}) { page_title }
header.with_title(data: { 'test-selector': 'project-query-name' }) { page_title }
header.with_breadcrumbs(breadcrumb_items, selected_item_font_weight: current_breadcrumb_element == page_title ? :bold : :normal)

if can_save?
Expand All @@ -21,14 +21,25 @@
)
end

if query.persisted? && OpenProject::FeatureDecisions.project_list_sharing_active?
aaron-contreras marked this conversation as resolved.
Show resolved Hide resolved
header.with_action_icon_button(
tag: :a,
href: dialog_project_query_members_path(query),
icon: "share-android",
mobile_icon: "share-android",
label: t(:label_share),
data: { controller: "async-dialog" }
)
end

header.with_action_menu(menu_arguments: {
anchor_align: :end
},
button_arguments: {
icon: "op-kebab-vertical",
"aria-label": t(:label_more),
data: { "test-selector": "project-more-dropdown-menu" }
}) do |menu|
}) do |menu|
if can_rename?
menu.with_item(
label: t('button_rename'),
Expand Down Expand Up @@ -90,29 +101,6 @@
end

if query.persisted?
# TODO: Remove section when the sharing modal is implemented (https://community.openproject.org/projects/openproject/work_packages/55163)
if can_publish?
if query.public?
menu.with_item(
label: t(:button_unpublish),
scheme: :danger,
href: unpublish_project_query_path(query),
content_arguments: { data: { method: :post } }
) do |item|
item.with_leading_visual_icon(icon: 'eye-closed')
end
else
menu.with_item(
label: t(:button_publish),
scheme: :default,
href: publish_project_query_path(query),
content_arguments: { data: { method: :post } }
) do |item|
item.with_leading_visual_icon(icon: 'eye')
end
end
end

menu.with_item(
label: t(:button_delete),
scheme: :danger,
Expand All @@ -130,7 +118,7 @@
<% else %>
<%=
render(Primer::OpenProject::PageHeader.new) do |header|
header.with_title(data: { 'test-selector': 'project-query-name'}) do
header.with_title(data: { 'test-selector': 'project-query-name' }) do
primer_form_with(model: query,
url: @query.new_record? ? project_queries_path(projects_query_params) : project_query_path(@query, projects_query_params),
scope: 'query',
Expand Down
18 changes: 2 additions & 16 deletions app/components/projects/index_page_header_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,29 +77,15 @@ def can_save?
return false unless query.persisted?
return false unless query.changed?

if query.public?
current_user.allowed_globally?(:manage_public_project_queries)
else
query.user == current_user
end
query.editable?
end

def can_rename?
return false unless current_user.logged?
return false unless query.persisted?
return false if query.changed?

if query.public?
current_user.allowed_globally?(:manage_public_project_queries)
else
query.user == current_user
end
end

def can_publish?
OpenProject::FeatureDecisions.project_list_sharing_active? &&
current_user.allowed_globally?(:manage_public_project_queries) &&
query.persisted?
query.editable?
end

def show_state?
Expand Down
6 changes: 3 additions & 3 deletions app/components/shares/bulk_permission_button_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@

module Shares
class BulkPermissionButtonComponent < ApplicationComponent # rubocop:disable OpenProject/AddPreviewForViewComponent
def initialize(entity:, available_roles:)
def initialize(strategy:)
super

@entity = entity
@available_roles = available_roles
@entity = strategy.entity
@available_roles = strategy.available_roles
end

def update_path
Expand Down
2 changes: 1 addition & 1 deletion app/components/shares/counter_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# There's no point in rendering the BulkSelectionCounterComponent even if
# I'm able to manage shares if the only user that the work package is
# currently shared is myself, since I'm not able to manage my own share.
if sharing_manageable? && shared_with_anyone_else_other_than_myself?
if strategy.manageable? && shared_with_anyone_else_other_than_myself?
render(Shares::BulkSelectionCounterComponent.new(count:))
else
render(Shares::ShareCounterComponent.new(count:))
Expand Down
14 changes: 5 additions & 9 deletions app/components/shares/counter_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,26 +34,22 @@ class CounterComponent < ApplicationComponent # rubocop:disable OpenProject/AddP
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers

def initialize(entity:,
count:,
sharing_manageable:)
def initialize(strategy:, count:)
super

@entity = entity
@strategy = strategy
@entity = strategy.entity
@count = count
@sharing_manageable = sharing_manageable
end

private

attr_reader :entity, :count

def sharing_manageable? = @sharing_manageable
attr_reader :entity, :count, :strategy

def shared_with_anyone_else_other_than_myself?
Member.of_entity(@entity)
.where.not(principal: User.current)
.any?
.exists?
end
end
end
15 changes: 15 additions & 0 deletions app/components/shares/empty_state_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<%= render(Primer::Beta::Blankslate.new) do |component|
component.with_visual_icon(icon: blankslate_config[:icon], size: :medium)
component.with_heading(tag: :h2).with_content(
blankslate_config[:heading_text],
)
component.with_description do
flex_layout do |flex|
flex.with_row(mb: 2) do
render(Primer::Beta::Text.new(color: :subtle)) do
blankslate_config[:description_text]
end
end
end
end
end %>
58 changes: 58 additions & 0 deletions app/components/shares/empty_state_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2024 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 Shares
class EmptyStateComponent < ApplicationComponent # rubocop:disable OpenProject/AddPreviewForViewComponent
dombesz marked this conversation as resolved.
Show resolved Hide resolved
include OpPrimer::ComponentHelpers

def initialize(strategy:)
super

@strategy = strategy
@entity = strategy.entity
end

private

attr_reader :strategy, :entity

def blankslate_config # rubocop:disable Metrics/AbcSize
aaron-contreras marked this conversation as resolved.
Show resolved Hide resolved
@blankslate_config ||= {}.tap do |config|
if params[:filters].blank?
config[:icon] = :people
config[:heading_text] = I18n.t("sharing.text_empty_state_header")
config[:description_text] = I18n.t("sharing.text_empty_state_description", entity: @entity.class.model_name.human)
else
config[:icon] = :search
config[:heading_text] = I18n.t("sharing.text_empty_search_header")
config[:description_text] = I18n.t("sharing.text_empty_search_description")
end
end
end
end
end
18 changes: 8 additions & 10 deletions app/components/shares/invite_user_form_component.html.erb
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
<%=
component_wrapper do
if @sharing_manageable
if strategy.manageable?
primer_form_with(
model: new_share,
url: url_for([@entity, Member]),
data: { controller: 'user-limit ' \
'shares--user-selected',
url: url_for([entity, Member]),
data: { controller: 'user-limit shares--user-selected',
'application-target': 'dynamic',
'user-limit-open-seats-value': OpenProject::Enterprise.open_seats_count,
action: 'submit->shares--user-selected#ensureUsersSelected' }
) do |form|
grid_layout('invite-user-form',
tag: :div) do |invite_form|
grid_layout('invite-user-form', tag: :div) do |invite_form|
invite_form.with_area('invitee') do
render(Shares::Invitee.new(form))
end

invite_form.with_area('permission') do
render(Shares::PermissionButtonComponent.new(
share: new_share,
available_roles: @available_roles,
available_roles: strategy.available_roles,
form_arguments: { builder: form, name: "role_id" },
data: { 'test-selector': 'op-share-dialog-invite-role' })
)
Expand All @@ -46,7 +44,7 @@
I18n.t(
"sharing.warning_user_limit_reached#{'_admin' if User.current.admin?}",
upgrade_url: OpenProject::Enterprise.upgrade_url,
entity: @entity.model_name.human
entity: entity.model_name.human
).html_safe
end
end
Expand All @@ -65,7 +63,7 @@

no_selected_user_row.with_column do
render(Primer::Beta::Text.new(color: :danger)) do
I18n.t("sharing.warning_no_selected_user", entity: @entity.model_name.human)
I18n.t("sharing.warning_no_selected_user", entity: entity.model_name.human)
end
end
end
Expand Down Expand Up @@ -96,7 +94,7 @@
end
end
else
render(Primer::Alpha::Banner.new(icon: :info)) { I18n.t('sharing.denied', entities: @entity.model_name.human(count: 2)) }
render(Primer::Alpha::Banner.new(icon: :info)) { I18n.t('sharing.denied', entities: entity.model_name.human(count: 2)) }
end
end
%>
16 changes: 7 additions & 9 deletions app/components/shares/invite_user_form_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,26 +32,24 @@ class InviteUserFormComponent < ApplicationComponent # rubocop:disable OpenProje
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers

def initialize(entity:,
available_roles:,
sharing_manageable:,
errors: nil)
attr_reader :entity, :strategy, :errors

def initialize(strategy:, errors: nil)
super

@entity = entity
@available_roles = available_roles
@sharing_manageable = sharing_manageable
@strategy = strategy
@entity = strategy.entity
@errors = errors
end

def new_share
@new_share ||= Member.new(entity: @entity, roles: [Role.new(id: default_role[:value])])
@new_share ||= Member.new(entity:, roles: [Role.new(id: default_role[:value])])
end

private

def default_role
@available_roles.find { |role_hash| role_hash[:default] } || @available_roles.first
strategy.available_roles.find { |role_hash| role_hash[:default] } || strategy.available_roles.first
end
end
end
Loading
Loading