From 96f1a7c917ca8385c14a227e7e7a8b6d2ef5b6b9 Mon Sep 17 00:00:00 2001 From: Aaron Contreras Date: Tue, 25 Jun 2024 10:10:37 -0500 Subject: [PATCH 01/62] Trigger dialog from PageHeader "share" button Boilerplate to trigger a dialog component in the Projects index page header section. --- .../projects/index_page_header_component.html.erb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/components/projects/index_page_header_component.html.erb b/app/components/projects/index_page_header_component.html.erb index 6e02e530b605..e7eda9f7fc07 100644 --- a/app/components/projects/index_page_header_component.html.erb +++ b/app/components/projects/index_page_header_component.html.erb @@ -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? @@ -21,6 +21,13 @@ ) end + header.with_action_icon_button( + icon: "share-android", + mobile_icon: "share-android", + label: t(:label_share), + data: { show_dialog_id: Projects::ExportListModalComponent::MODAL_ID } + ) + header.with_action_menu(menu_arguments: { anchor_align: :end }, @@ -28,7 +35,7 @@ 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'), @@ -130,7 +137,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', From 0a45c94c7e21eddd3cbc18820dff222d1ef848b6 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Wed, 26 Jun 2024 14:36:50 +0200 Subject: [PATCH 02/62] Add a dialog method so we can dynamically initialize the modal --- app/controllers/shares_controller.rb | 20 ++++++++++++++------ app/views/projects/index.html.erb | 11 +++++++++++ app/views/sharing/dialog.turbo_stream.erb | 10 ++++++++++ 3 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 app/views/sharing/dialog.turbo_stream.erb diff --git a/app/controllers/shares_controller.rb b/app/controllers/shares_controller.rb index 0e8a174e3d31..e7c22b313a31 100644 --- a/app/controllers/shares_controller.rb +++ b/app/controllers/shares_controller.rb @@ -28,26 +28,34 @@ class SharesController < ApplicationController include OpTurbo::ComponentStream + include OpTurbo::DialogStreamHelper include Shares::WorkPackages::Authorization include MemberHelper before_action :load_entity - before_action :load_shares, only: %i[index] + before_action :load_shares, only: %i[index dialog] before_action :load_selected_shares, only: %i[bulk_update bulk_destroy] before_action :load_share, only: %i[destroy update resend_invite] before_action :authorize before_action :enterprise_check, only: %i[index] + def dialog + @sharing_manageable = sharing_manageable? + @available_roles = available_roles + end + def index unless @query.valid? flash.now[:error] = query.errors.full_messages end - render Shares::ModalBodyComponent.new(entity: @entity, - shares: @shares, - errors: @errors, - sharing_manageable: sharing_manageable?, - available_roles:), layout: nil + render Shares::ModalBodyComponent.new( + entity: @entity, + shares: @shares, + errors: @errors, + sharing_manageable: sharing_manageable?, + available_roles: + ), layout: nil end def create # rubocop:disable Metrics/AbcSize,Metrics/PerceivedComplexity diff --git a/app/views/projects/index.html.erb b/app/views/projects/index.html.erb index a469acb90f2e..e4020a900f89 100644 --- a/app/views/projects/index.html.erb +++ b/app/views/projects/index.html.erb @@ -38,6 +38,17 @@ See COPYRIGHT and LICENSE files for more details. ) %> <%= render(Projects::IndexSubHeaderComponent.new(query:, current_user:, disable_buttons: state === :rename)) %> + <%# TODO: Temporary %> + <%- if @query %> + <%= render(Primer::Beta::Button.new( + tag: :a, + href: dialog_projects_query_members_path(@query), + data: { controller: "async-dialog" } +)) do |button| + "TEMP: SHARE" +end +%> + <%- end%> <%= render Projects::TableComponent.new( query:, current_user: current_user, diff --git a/app/views/sharing/dialog.turbo_stream.erb b/app/views/sharing/dialog.turbo_stream.erb new file mode 100644 index 000000000000..5f8b164eb980 --- /dev/null +++ b/app/views/sharing/dialog.turbo_stream.erb @@ -0,0 +1,10 @@ +<%= turbo_stream.dialog do + render(Primer::Alpha::Dialog.new(title: t(:label_sharing), + id: 'share_list')) do |d| + d.with_header(variant: :large) + d.with_body do + render(Shares::ModalBodyComponent.new( entity: @entity, shares: @shares, errors: @errors, sharing_manageable: @sharing_manageable, available_roles: @available_roles)) + end + end +end +%> From 65f5232a4e53d6ac96f0df630b677bcdec477a5f Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Wed, 26 Jun 2024 15:12:30 +0200 Subject: [PATCH 03/62] Fix loading of concern --- .../shares/work_packages/authorization.rb | 52 ------------------- app/controllers/shares_controller.rb | 27 ++++++++-- app/views/projects/index.html.erb | 2 +- config/initializers/permissions.rb | 2 +- config/routes.rb | 7 ++- 5 files changed, 31 insertions(+), 59 deletions(-) delete mode 100644 app/controllers/concerns/shares/work_packages/authorization.rb diff --git a/app/controllers/concerns/shares/work_packages/authorization.rb b/app/controllers/concerns/shares/work_packages/authorization.rb deleted file mode 100644 index 32b4bf5f34e0..000000000000 --- a/app/controllers/concerns/shares/work_packages/authorization.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -# -- copyright -# OpenProject is an open source project management software. -# Copyright (C) 2010-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 - module WorkPackages - module Authorization - extend ActiveSupport::Concern - - included do - def sharing_manageable? - # TODO: Fix this to check based on the entity - case @entity - when WorkPackage - User.current.allowed_in_project?(:share_work_packages, @entity.project) - else - raise ArgumentError, <<~ERROR - Checking sharing capabilities for an unsupported entity: - - #{@entity.class} - ERROR - end - end - end - end - end -end diff --git a/app/controllers/shares_controller.rb b/app/controllers/shares_controller.rb index e7c22b313a31..a7305b639ea4 100644 --- a/app/controllers/shares_controller.rb +++ b/app/controllers/shares_controller.rb @@ -29,16 +29,17 @@ class SharesController < ApplicationController include OpTurbo::ComponentStream include OpTurbo::DialogStreamHelper - include Shares::WorkPackages::Authorization include MemberHelper before_action :load_entity before_action :load_shares, only: %i[index dialog] before_action :load_selected_shares, only: %i[bulk_update bulk_destroy] before_action :load_share, only: %i[destroy update resend_invite] - before_action :authorize before_action :enterprise_check, only: %i[index] + # TODO: Permission checks need to be implemented correctly depending on entity + before_action :authorize + def dialog @sharing_manageable = sharing_manageable? @available_roles = available_roles @@ -305,11 +306,16 @@ def respond_with_bulk_removed_shares def load_entity @entity = if params["work_package_id"] WorkPackage.visible.find(params["work_package_id"]) - # TODO: Add support for other entities + # TODO: Add support for other entities + elsif params["query_id"] && request.path.starts_with?("/projects/queries") + Queries::Projects::ProjectQuery.visible.find(params["query_id"]) else raise ArgumentError, <<~ERROR Nested the SharesController under an entity controller that is not yet configured to support sharing. Edit the SharesController#load_entity method to load the entity from the correct parent. + + Params: #{params.to_unsafe_h} + Request Path: #{request.path} ERROR end @@ -381,4 +387,19 @@ def sharing_contract_scope Shares::WorkPackages end end + + def sharing_manageable? + # TODO: Fix this to check based on the entity + case @entity + when WorkPackage + User.current.allowed_in_project?(:share_work_packages, @entity.project) + when Queries::Projects::ProjectQuery + @entity.editable? + else + raise ArgumentError, <<~ERROR + Checking sharing capabilities for an unsupported entity: + - #{@entity.class} + ERROR + end + end end diff --git a/app/views/projects/index.html.erb b/app/views/projects/index.html.erb index e4020a900f89..7c67b6aa1a6f 100644 --- a/app/views/projects/index.html.erb +++ b/app/views/projects/index.html.erb @@ -39,7 +39,7 @@ See COPYRIGHT and LICENSE files for more details. %> <%= render(Projects::IndexSubHeaderComponent.new(query:, current_user:, disable_buttons: state === :rename)) %> <%# TODO: Temporary %> - <%- if @query %> + <%- if @query.persisted? %> <%= render(Primer::Beta::Button.new( tag: :a, href: dialog_projects_query_members_path(@query), diff --git a/config/initializers/permissions.rb b/config/initializers/permissions.rb index 8b4ab0de9719..51abafd3c0be 100644 --- a/config/initializers/permissions.rb +++ b/config/initializers/permissions.rb @@ -331,7 +331,7 @@ map.permission :share_work_packages, { members: %i[destroy_by_principal], - shares: %i[index create destroy update resend_invite bulk_update bulk_destroy] + shares: %i[dialog index create destroy update resend_invite bulk_update bulk_destroy] }, permissible_on: :project, dependencies: %i[edit_work_packages view_shared_work_packages], diff --git a/config/routes.rb b/config/routes.rb index 10d63e1decb6..515a3e592f1e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -79,12 +79,13 @@ # Shared route concerns # TODO: Add description how to configure controller to support shares concern :shareable do - resources :members, path: :shares, controller: "shares", only: %i[index create update destroy] do + resources :members, path: "shares", controller: "shares", only: %i[index create update destroy] do member do post "resend_invite" => "shares#resend_invite" end collection do + get :dialog, to: "shares#dialog" patch :bulk, to: "shares#bulk_update" put :bulk, to: "shares#bulk_update" delete :bulk, to: "shares#bulk_destroy" @@ -537,7 +538,9 @@ get "/bulk" => "bulk#destroy" end - resources :work_packages, only: [:index], concerns: [:shareable] do + resources :work_packages, only: [:index] do + concerns :shareable + # move bulk of wps get "move/new" => "work_packages/moves#new", on: :collection, as: "new_move" post "move" => "work_packages/moves#create", on: :collection, as: "move" From 48a3aec647883b526d23892899f8ea90faf775df Mon Sep 17 00:00:00 2001 From: Aaron Contreras Date: Wed, 26 Jun 2024 21:53:21 -0500 Subject: [PATCH 04/62] Render sharing modal for project lists --- .../index_page_header_component.html.erb | 16 +++++++----- app/controllers/shares_controller.rb | 26 ++++++++++++++----- app/views/projects/index.html.erb | 11 -------- app/views/shares/dialog.turbo_stream.erb | 15 +++++++++++ app/views/sharing/dialog.turbo_stream.erb | 10 ------- config/routes.rb | 2 ++ 6 files changed, 46 insertions(+), 34 deletions(-) create mode 100644 app/views/shares/dialog.turbo_stream.erb delete mode 100644 app/views/sharing/dialog.turbo_stream.erb diff --git a/app/components/projects/index_page_header_component.html.erb b/app/components/projects/index_page_header_component.html.erb index e7eda9f7fc07..63c90ec6b29a 100644 --- a/app/components/projects/index_page_header_component.html.erb +++ b/app/components/projects/index_page_header_component.html.erb @@ -21,12 +21,16 @@ ) end - header.with_action_icon_button( - icon: "share-android", - mobile_icon: "share-android", - label: t(:label_share), - data: { show_dialog_id: Projects::ExportListModalComponent::MODAL_ID } - ) + if query.persisted? + 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 diff --git a/app/controllers/shares_controller.rb b/app/controllers/shares_controller.rb index a7305b639ea4..7e0e2d140b71 100644 --- a/app/controllers/shares_controller.rb +++ b/app/controllers/shares_controller.rb @@ -156,7 +156,7 @@ def create_or_update_share(user_id, role_ids) create_contract_class: sharing_contract_scope::CreateContract, update_contract_class: sharing_contract_scope::UpdateContract ) - .call(entity: @entity, user_id:, role_ids:) + .call(entity: @entity, user_id:, role_ids:) end def respond_with_replace_modal @@ -306,9 +306,8 @@ def respond_with_bulk_removed_shares def load_entity @entity = if params["work_package_id"] WorkPackage.visible.find(params["work_package_id"]) - # TODO: Add support for other entities - elsif params["query_id"] && request.path.starts_with?("/projects/queries") - Queries::Projects::ProjectQuery.visible.find(params["query_id"]) + elsif params["project_query_id"] + ProjectQuery.visible.find(params["project_query_id"]) else raise ArgumentError, <<~ERROR Nested the SharesController under an entity controller that is not yet configured to support sharing. @@ -336,8 +335,8 @@ def load_query return @query if defined?(@query) @query = ParamsToQueryService - .new(Member, current_user, query_class: Queries::Members::EntityMemberQuery) - .call(params) + .new(Member, current_user, query_class: Queries::Members::EntityMemberQuery) + .call(params) # Set default filter on the entity @query.where("entity_id", "=", @entity.id) @@ -377,7 +376,20 @@ def available_roles description: I18n.t("work_package.permissions.view_description"), default: true } ] + elsif @entity.is_a?(ProjectQuery) + role_mapping = ProjectQueryRole.unscoped.pluck(:builtin, :id).to_h + + [ + { label: I18n.t("work_package.permissions.edit"), + value: role_mapping[Role::BUILTIN_PROJECT_QUERY_EDIT], + description: I18n.t("work_package.permissions.edit_description") }, + { label: I18n.t("work_package.permissions.view"), + value: role_mapping[Role::BUILTIN_PROJECT_QUERY_VIEW], + description: I18n.t("work_package.permissions.view_description"), + default: true } + ] else + [] end end @@ -393,7 +405,7 @@ def sharing_manageable? case @entity when WorkPackage User.current.allowed_in_project?(:share_work_packages, @entity.project) - when Queries::Projects::ProjectQuery + when ProjectQuery @entity.editable? else raise ArgumentError, <<~ERROR diff --git a/app/views/projects/index.html.erb b/app/views/projects/index.html.erb index 7c67b6aa1a6f..a469acb90f2e 100644 --- a/app/views/projects/index.html.erb +++ b/app/views/projects/index.html.erb @@ -38,17 +38,6 @@ See COPYRIGHT and LICENSE files for more details. ) %> <%= render(Projects::IndexSubHeaderComponent.new(query:, current_user:, disable_buttons: state === :rename)) %> - <%# TODO: Temporary %> - <%- if @query.persisted? %> - <%= render(Primer::Beta::Button.new( - tag: :a, - href: dialog_projects_query_members_path(@query), - data: { controller: "async-dialog" } -)) do |button| - "TEMP: SHARE" -end -%> - <%- end%> <%= render Projects::TableComponent.new( query:, current_user: current_user, diff --git a/app/views/shares/dialog.turbo_stream.erb b/app/views/shares/dialog.turbo_stream.erb new file mode 100644 index 000000000000..7ca4449da4b4 --- /dev/null +++ b/app/views/shares/dialog.turbo_stream.erb @@ -0,0 +1,15 @@ +<%= turbo_stream.dialog do + render(Primer::Alpha::Dialog.new(title: t(:label_sharing), + id: 'share_list', + size: :xlarge)) do |d| + d.with_header(variant: :large) + d.with_body do + render(Shares::ModalBodyComponent.new(entity: @entity, + shares: @shares, + errors: @errors, + sharing_manageable: @sharing_manageable, + available_roles: @available_roles)) + end + end +end +%> diff --git a/app/views/sharing/dialog.turbo_stream.erb b/app/views/sharing/dialog.turbo_stream.erb deleted file mode 100644 index 5f8b164eb980..000000000000 --- a/app/views/sharing/dialog.turbo_stream.erb +++ /dev/null @@ -1,10 +0,0 @@ -<%= turbo_stream.dialog do - render(Primer::Alpha::Dialog.new(title: t(:label_sharing), - id: 'share_list')) do |d| - d.with_header(variant: :large) - d.with_body do - render(Shares::ModalBodyComponent.new( entity: @entity, shares: @shares, errors: @errors, sharing_manageable: @sharing_manageable, available_roles: @available_roles)) - end - end -end -%> diff --git a/config/routes.rb b/config/routes.rb index 515a3e592f1e..4ee44cf9a21f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -197,6 +197,8 @@ end resources :project_queries, only: %i[show new create update destroy], controller: "projects/queries" do + concerns :shareable + member do get :rename From 8cd4f8cae58c77038285576cb200cfc5de19e1d1 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Thu, 27 Jun 2024 10:45:05 +0200 Subject: [PATCH 05/62] Disable cops --- app/controllers/shares_controller.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/controllers/shares_controller.rb b/app/controllers/shares_controller.rb index 7e0e2d140b71..60511ca50ad7 100644 --- a/app/controllers/shares_controller.rb +++ b/app/controllers/shares_controller.rb @@ -303,7 +303,7 @@ def respond_with_bulk_removed_shares respond_with_turbo_streams end - def load_entity + def load_entity # rubocop:disable Metrics/AbcSize @entity = if params["work_package_id"] WorkPackage.visible.find(params["work_package_id"]) elsif params["project_query_id"] @@ -360,7 +360,7 @@ def load_selected_shares .where(id: params[:share_ids]) end - def available_roles + def available_roles # rubocop:disable Metrics/AbcSize @available_roles ||= if @entity.is_a?(WorkPackage) role_mapping = WorkPackageRole.unscoped.pluck(:builtin, :id).to_h @@ -377,6 +377,7 @@ def available_roles default: true } ] elsif @entity.is_a?(ProjectQuery) + # TODO: Load all roles here and see where we can load the description from role_mapping = ProjectQueryRole.unscoped.pluck(:builtin, :id).to_h [ From f643faf1bc4d54e9e56c22f98d0c70e3119809ad Mon Sep 17 00:00:00 2001 From: Aaron Contreras Date: Thu, 27 Jun 2024 18:14:29 -0500 Subject: [PATCH 06/62] Fix css top-layer issue for invitee dropdown on sharing modals --- app/forms/shares/invitee.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/forms/shares/invitee.rb b/app/forms/shares/invitee.rb index e6857c141cd0..61d5a3d41aba 100644 --- a/app/forms/shares/invitee.rb +++ b/app/forms/shares/invitee.rb @@ -50,7 +50,7 @@ class Invitee < ApplicationForm addTagText: I18n.t("members.send_invite_to"), multiple: true, focusDirectly: true, - appendTo: "body", + appendToComponent: true, disabled: @disabled } ) From 5492893ec7b0346a599e1bf900396b42d4baab2c Mon Sep 17 00:00:00 2001 From: Aaron Contreras Date: Thu, 27 Jun 2024 18:27:07 -0500 Subject: [PATCH 07/62] Fix invalid YAML formatting This was marked as invalid YAML in my editor - Zed --- config/locales/en.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 913f69703039..aa55b5eaad99 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -320,8 +320,9 @@ en: project_custom_fields: header: title: "Project attributes" - description: "These project attributes will be displayed in your project overview page under their respective sections. You can enable or disable individual attributes. -Project attributes and sections are defined in the administration settings by the administrator of the instance. " + description: + 'These project attributes will be displayed in your project overview page under their respective sections. You can enable or disable individual attributes. + Project attributes and sections are defined in the administration settings by the administrator of the instance. ' filter: label: "Search project attribute" actions: From fcb45b57dfc4a5fe0f020cc163ef17d12deeeeff Mon Sep 17 00:00:00 2001 From: Aaron Contreras Date: Thu, 27 Jun 2024 18:27:56 -0500 Subject: [PATCH 08/62] Lint locales/en.yml Formatted by Zed editor's autocorrect feature --- config/locales/en.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index aa55b5eaad99..363b1e2568b1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1472,7 +1472,7 @@ en: create_new_page: "Wiki page" date: - abbr_day_names: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ] + abbr_day_names: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] abbr_month_names: [ ~, @@ -2584,12 +2584,12 @@ en: plaintext: storage: "Storage" healthy: - summary: "Good news! The status of your storage, %{storage_name}, is currently displaying as \"Healthy\"." + summary: 'Good news! The status of your storage, %{storage_name}, is currently displaying as "Healthy".' error-solved-on: "Solved On" recommendation: "We will continue monitoring the system to ensure it remains in good health. In case of any discrepancies, we will notify you." details: "For more details or to make any necessary amendments, you can visit your storage configuration" unhealthy: - summary: "The status of your storage, %{storage_name}, is currently displaying as \"Error\". We've detected an issue that might require your attention." + summary: 'The status of your storage, %{storage_name}, is currently displaying as "Error". We''ve detected an issue that might require your attention.' error-details: "Error Details" error-message: "Error Message" error-occurred-on: "Occurred On" @@ -2598,11 +2598,11 @@ en: email_notification_settings: "Storage email notification settings" see_storage_settings: "See storage settings" healthy: - subject: "Storage \"%{name}\" is now healthy!" + subject: 'Storage "%{name}" is now healthy!' solved_at: "solved at" summary: "The problem with your %{storage_name} storage integration is now solved" unhealthy: - subject: "Storage \"%{name}\" is unhealthy!" + subject: 'Storage "%{name}" is unhealthy!' since: "since" summary: "There is a problem with your %{storage_name} storage integration" troubleshooting: From c3ff1affedc5386faed3285c81978cbeaaed529f Mon Sep 17 00:00:00 2001 From: Aaron Contreras Date: Thu, 27 Jun 2024 18:28:45 -0500 Subject: [PATCH 09/62] Add Project List sharing dialog title translation --- app/views/shares/dialog.turbo_stream.erb | 2 +- config/locales/en.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/shares/dialog.turbo_stream.erb b/app/views/shares/dialog.turbo_stream.erb index 7ca4449da4b4..ac31607f8f81 100644 --- a/app/views/shares/dialog.turbo_stream.erb +++ b/app/views/shares/dialog.turbo_stream.erb @@ -1,5 +1,5 @@ <%= turbo_stream.dialog do - render(Primer::Alpha::Dialog.new(title: t(:label_sharing), + render(Primer::Alpha::Dialog.new(title: t(:label_share_project_list), id: 'share_list', size: :xlarge)) do |d| d.with_header(variant: :large) diff --git a/config/locales/en.yml b/config/locales/en.yml index 363b1e2568b1..fcd67e35d165 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2140,6 +2140,7 @@ en: label_introduction_video: "Introduction video" label_invite_user: "Invite user" label_share: "Share" + label_share_project_list: "Share project list" label_show_hide: "Show/hide" label_show_all_registered_users: "Show all registered users" label_journal: "Journal" From 4fbd3ccf104859716cf24ed249dc1409c9f1c6d5 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Mon, 1 Jul 2024 08:19:43 +0200 Subject: [PATCH 10/62] Enfore small size of action menu dropdown. Otherwise it would be way to large when opened inside a dialog --- app/components/shares/permission_button_component.html.erb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/components/shares/permission_button_component.html.erb b/app/components/shares/permission_button_component.html.erb index 6402891c82ad..24a63a92b6d9 100644 --- a/app/components/shares/permission_button_component.html.erb +++ b/app/components/shares/permission_button_component.html.erb @@ -1,6 +1,7 @@ <%= component_wrapper do render(Primer::Alpha::ActionMenu.new(**{ select_variant: :single, + size: :small, dynamic_label: true, anchor_align: :end, color: :subtle }.deep_merge(@system_arguments))) do |menu| From 6ed97fa6f683f56a57821f84619392402f3a1f36 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Mon, 1 Jul 2024 08:24:36 +0200 Subject: [PATCH 11/62] Use smaller variant for ActionMenus of sharing dialog to ensure that they do not span the whole width of the dialog they are rendered in --- app/components/shares/modal_body_component.html.erb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/components/shares/modal_body_component.html.erb b/app/components/shares/modal_body_component.html.erb index 97de19aeae13..8f7252944dd2 100644 --- a/app/components/shares/modal_body_component.html.erb +++ b/app/components/shares/modal_body_component.html.erb @@ -28,6 +28,7 @@ header_actions.with_column(mr: 2) do render(Primer::Alpha::ActionMenu.new(anchor_align: :end, select_variant: :single, + size: :small, dynamic_label: true, dynamic_label_prefix: I18n.t('sharing.filter.type'), color: :muted, @@ -50,6 +51,7 @@ header_actions.with_column do render(Primer::Alpha::ActionMenu.new(anchor_align: :end, select_variant: :single, + size: :small, dynamic_label: true, dynamic_label_prefix: I18n.t('sharing.filter.role'), color: :muted, From 708e5aed23bf9decfe8a7fb5b0374f98075089f4 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Mon, 1 Jul 2024 10:04:19 +0200 Subject: [PATCH 12/62] Introduce `SharingStrategy` concept to not litter the controller with case statements --- app/controllers/shares_controller.rb | 147 ++++++------------ .../project_query_strategy.rb | 39 +++++ .../work_package_strategy.rb | 42 +++++ 3 files changed, 130 insertions(+), 98 deletions(-) create mode 100644 app/models/sharing_strategies/project_query_strategy.rb create mode 100644 app/models/sharing_strategies/work_package_strategy.rb diff --git a/app/controllers/shares_controller.rb b/app/controllers/shares_controller.rb index 60511ca50ad7..b632ba2c462c 100644 --- a/app/controllers/shares_controller.rb +++ b/app/controllers/shares_controller.rb @@ -41,8 +41,12 @@ class SharesController < ApplicationController before_action :authorize def dialog - @sharing_manageable = sharing_manageable? - @available_roles = available_roles + puts "*" * 100 + pp(@shares) + puts "*" * 100 + + @sharing_manageable = sharing_strategy.sharing_manageable? + @available_roles = sharing_strategy.available_roles end def index @@ -54,8 +58,8 @@ def index entity: @entity, shares: @shares, errors: @errors, - sharing_manageable: sharing_manageable?, - available_roles: + sharing_manageable: sharing_strategy.sharing_manageable?, + available_roles: sharing_strategy.available_roles ), layout: nil end @@ -138,6 +142,8 @@ def bulk_destroy private + attr_reader :sharing_strategy + def enterprise_check return if EnterpriseToken.allows_to?(:work_package_sharing) @@ -146,26 +152,25 @@ def enterprise_check def destroy_share(share) Shares::DeleteService - .new(user: current_user, model: share, contract_class: sharing_contract_scope::DeleteContract) + .new(user: current_user, model: share, contract_class: sharing_strategy.delete_contract_class) .call end def create_or_update_share(user_id, role_ids) Shares::CreateOrUpdateService.new( user: current_user, - create_contract_class: sharing_contract_scope::CreateContract, - update_contract_class: sharing_contract_scope::UpdateContract - ) - .call(entity: @entity, user_id:, role_ids:) + create_contract_class: sharing_strategy.create_contract_class, + update_contract_class: sharing_strategy.update_contract_class + ).call(entity: @entity, user_id:, role_ids:) end def respond_with_replace_modal replace_via_turbo_stream( component: Shares::ModalBodyComponent.new( entity: @entity, - available_roles:, + available_roles: sharing_strategy.available_roles, shares: @new_shares || load_shares, - sharing_manageable: sharing_manageable?, + sharing_manageable: sharing_strategy.sharing_manageable?, errors: @errors ) ) @@ -177,8 +182,8 @@ def respond_with_prepend_shares # rubocop:disable Metrics/AbcSize replace_via_turbo_stream( component: Shares::InviteUserFormComponent.new( entity: @entity, - available_roles:, - sharing_manageable: sharing_manageable?, + available_roles: sharing_strategy.available_roles, + sharing_manageable: sharing_strategy.sharing_manageable?, errors: @errors ) ) @@ -187,7 +192,7 @@ def respond_with_prepend_shares # rubocop:disable Metrics/AbcSize component: Shares::CounterComponent.new( entity: @entity, count: current_visible_member_count, - sharing_manageable: sharing_manageable? + sharing_manageable: sharing_strategy.sharing_manageable? ) ) @@ -195,13 +200,13 @@ def respond_with_prepend_shares # rubocop:disable Metrics/AbcSize prepend_via_turbo_stream( component: Shares::ShareRowComponent.new( share:, - available_roles:, - sharing_manageable: sharing_manageable? + available_roles: sharing_strategy.available_roles, + sharing_manageable: sharing_strategy.sharing_manageable? ), target_component: Shares::ModalBodyComponent.new( entity: @entity, - available_roles:, - sharing_manageable: sharing_manageable?, + available_roles: sharing_strategy.available_roles, + sharing_manageable: sharing_strategy.sharing_manageable?, shares: load_shares, errors: @errors ) @@ -215,8 +220,8 @@ def respond_with_new_invite_form replace_via_turbo_stream( component: Shares::InviteUserFormComponent.new( entity: @entity, - available_roles:, - sharing_manageable: sharing_manageable?, + available_roles: sharing_strategy.available_roles, + sharing_manageable: sharing_strategy.sharing_manageable?, errors: @errors ) ) @@ -228,7 +233,7 @@ def respond_with_update_permission_button replace_via_turbo_stream( component: Shares::PermissionButtonComponent.new( share: @share, - available_roles:, + available_roles: sharing_strategy.available_roles, data: { "test-selector": "op-share-dialog-update-role" } ) ) @@ -240,15 +245,15 @@ def respond_with_remove_share remove_via_turbo_stream( component: Shares::ShareRowComponent.new( share: @share, - available_roles:, - sharing_manageable: sharing_manageable? + available_roles: sharing_strategy.available_roles, + sharing_manageable: sharing_strategy.sharing_manageable? ) ) update_via_turbo_stream( component: Shares::CounterComponent.new( entity: @entity, count: current_visible_member_count, - sharing_manageable: sharing_manageable? + sharing_manageable: sharing_strategy.sharing_manageable? ) ) @@ -259,7 +264,7 @@ def respond_with_update_user_details update_via_turbo_stream( component: Shares::UserDetailsComponent.new( share: @share, - manager_mode: sharing_manageable?, + manager_mode: sharing_strategy.sharing_manageable?, invite_resent: true ) ) @@ -272,7 +277,7 @@ def respond_with_bulk_updated_permission_buttons replace_via_turbo_stream( component: Shares::PermissionButtonComponent.new( share:, - available_roles:, + available_roles: sharing_strategy.available_roles, data: { "test-selector": "op-share-dialog-update-role" } ) ) @@ -286,8 +291,8 @@ def respond_with_bulk_removed_shares remove_via_turbo_stream( component: Shares::ShareRowComponent.new( share:, - available_roles:, - sharing_manageable: sharing_manageable? + available_roles: sharing_strategy.available_roles, + sharing_manageable: sharing_strategy.sharing_manageable? ) ) end @@ -296,7 +301,7 @@ def respond_with_bulk_removed_shares component: Shares::CounterComponent.new( entity: @entity, count: current_visible_member_count, - sharing_manageable: sharing_manageable? + sharing_manageable: sharing_strategy.sharing_manageable? ) ) @@ -304,19 +309,21 @@ def respond_with_bulk_removed_shares end def load_entity # rubocop:disable Metrics/AbcSize - @entity = if params["work_package_id"] - WorkPackage.visible.find(params["work_package_id"]) - elsif params["project_query_id"] - ProjectQuery.visible.find(params["project_query_id"]) - else - raise ArgumentError, <<~ERROR - Nested the SharesController under an entity controller that is not yet configured to support sharing. - Edit the SharesController#load_entity method to load the entity from the correct parent. - - Params: #{params.to_unsafe_h} - Request Path: #{request.path} - ERROR - end + if params["work_package_id"] + @entity = WorkPackage.visible.find(params["work_package_id"]) + @sharing_strategy = SharingStrategies::WorkPackageStrategy.new(work_package: @entity) + elsif params["project_query_id"] + @entity = ProjectQuery.visible.find(params["project_query_id"]) + @sharing_strategy = SharingStrategies::ProjectQueryStrategy.new(project_query: @entity) + else + raise ArgumentError, <<~ERROR + Nested the SharesController under an entity controller that is not yet configured to support sharing. + Edit the SharesController#load_entity method to load the entity from the correct parent. + + Params: #{params.to_unsafe_h} + Request Path: #{request.path} + ERROR + end if @entity.respond_to?(:project) @project = @entity.project @@ -359,60 +366,4 @@ def load_selected_shares .of_entity(@entity) .where(id: params[:share_ids]) end - - def available_roles # rubocop:disable Metrics/AbcSize - @available_roles ||= if @entity.is_a?(WorkPackage) - role_mapping = WorkPackageRole.unscoped.pluck(:builtin, :id).to_h - - [ - { label: I18n.t("work_package.permissions.edit"), - value: role_mapping[Role::BUILTIN_WORK_PACKAGE_EDITOR], - description: I18n.t("work_package.permissions.edit_description") }, - { label: I18n.t("work_package.permissions.comment"), - value: role_mapping[Role::BUILTIN_WORK_PACKAGE_COMMENTER], - description: I18n.t("work_package.permissions.comment_description") }, - { label: I18n.t("work_package.permissions.view"), - value: role_mapping[Role::BUILTIN_WORK_PACKAGE_VIEWER], - description: I18n.t("work_package.permissions.view_description"), - default: true } - ] - elsif @entity.is_a?(ProjectQuery) - # TODO: Load all roles here and see where we can load the description from - role_mapping = ProjectQueryRole.unscoped.pluck(:builtin, :id).to_h - - [ - { label: I18n.t("work_package.permissions.edit"), - value: role_mapping[Role::BUILTIN_PROJECT_QUERY_EDIT], - description: I18n.t("work_package.permissions.edit_description") }, - { label: I18n.t("work_package.permissions.view"), - value: role_mapping[Role::BUILTIN_PROJECT_QUERY_VIEW], - description: I18n.t("work_package.permissions.view_description"), - default: true } - ] - else - - [] - end - end - - def sharing_contract_scope - if @entity.is_a?(WorkPackage) - Shares::WorkPackages - end - end - - def sharing_manageable? - # TODO: Fix this to check based on the entity - case @entity - when WorkPackage - User.current.allowed_in_project?(:share_work_packages, @entity.project) - when ProjectQuery - @entity.editable? - else - raise ArgumentError, <<~ERROR - Checking sharing capabilities for an unsupported entity: - - #{@entity.class} - ERROR - end - end end diff --git a/app/models/sharing_strategies/project_query_strategy.rb b/app/models/sharing_strategies/project_query_strategy.rb new file mode 100644 index 000000000000..cc009ecd605e --- /dev/null +++ b/app/models/sharing_strategies/project_query_strategy.rb @@ -0,0 +1,39 @@ +module SharingStrategies + class ProjectQueryStrategy + attr_reader :project_query + + def initialize(project_query:) + @project_query = project_query + end + + def available_roles + ProjectQueryRole.all.map.with_index do |role, index| + { + label: role.name, + value: role.id, + description: "#{role.name} description", # TODO: Figure out from where we can get the description + default: index.zero? + } + end + end + + def sharing_manageable? + @project_query.editable? + end + + def create_contract_class + # Shares::WorkPackages::CreateContract + EmptyContract + end + + def update_contract_class + # Shares::WorkPackages::UpdateContract + EmptyContract + end + + def delete_contract_class + # Shares::WorkPackages::DeleteContract + EmptyContract + end + end +end diff --git a/app/models/sharing_strategies/work_package_strategy.rb b/app/models/sharing_strategies/work_package_strategy.rb new file mode 100644 index 000000000000..6b40ee390066 --- /dev/null +++ b/app/models/sharing_strategies/work_package_strategy.rb @@ -0,0 +1,42 @@ +module SharingStrategies + class WorkPackageStrategy + attr_reader :work_package + + def initialize(work_package:) + @work_package = work_package + end + + def available_roles + role_mapping = WorkPackageRole.unscoped.pluck(:builtin, :id).to_h + + [ + { label: I18n.t("work_package.permissions.edit"), + value: role_mapping[Role::BUILTIN_WORK_PACKAGE_EDITOR], + description: I18n.t("work_package.permissions.edit_description") }, + { label: I18n.t("work_package.permissions.comment"), + value: role_mapping[Role::BUILTIN_WORK_PACKAGE_COMMENTER], + description: I18n.t("work_package.permissions.comment_description") }, + { label: I18n.t("work_package.permissions.view"), + value: role_mapping[Role::BUILTIN_WORK_PACKAGE_VIEWER], + description: I18n.t("work_package.permissions.view_description"), + default: true } + ] + end + + def sharing_manageable? + User.current.allowed_in_project?(:share_work_packages, @work_package.project) + end + + def create_contract_class + Shares::WorkPackages::CreateContract + end + + def update_contract_class + Shares::WorkPackages::UpdateContract + end + + def delete_contract_class + Shares::WorkPackages::DeleteContract + end + end +end From 30139b99c6438b160591458479e268ca3444cf29 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Mon, 1 Jul 2024 10:09:42 +0200 Subject: [PATCH 13/62] EntityMemberQuery -> NonInheritedMemberQuery --- app/controllers/shares_controller.rb | 2 +- app/models/queries/members.rb | 2 +- .../{entity_member_query.rb => non_inherited_member_query.rb} | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename app/models/queries/members/{entity_member_query.rb => non_inherited_member_query.rb} (94%) diff --git a/app/controllers/shares_controller.rb b/app/controllers/shares_controller.rb index b632ba2c462c..1e51f160eaa5 100644 --- a/app/controllers/shares_controller.rb +++ b/app/controllers/shares_controller.rb @@ -342,7 +342,7 @@ def load_query return @query if defined?(@query) @query = ParamsToQueryService - .new(Member, current_user, query_class: Queries::Members::EntityMemberQuery) + .new(Member, current_user, query_class: Queries::Members::NonInheritedMemberQuery) .call(params) # Set default filter on the entity diff --git a/app/models/queries/members.rb b/app/models/queries/members.rb index 98228d61265e..e1b56bfe2c6e 100644 --- a/app/models/queries/members.rb +++ b/app/models/queries/members.rb @@ -50,7 +50,7 @@ module Queries::Members order Orders::StatusOrder end - ::Queries::Register.register(EntityMemberQuery) do + ::Queries::Register.register(NonInheritedMemberQuery) do filter Filters::NameFilter filter Filters::AnyNameAttributeFilter filter Filters::ProjectFilter diff --git a/app/models/queries/members/entity_member_query.rb b/app/models/queries/members/non_inherited_member_query.rb similarity index 94% rename from app/models/queries/members/entity_member_query.rb rename to app/models/queries/members/non_inherited_member_query.rb index 27714076f0e1..87349f0d1f85 100644 --- a/app/models/queries/members/entity_member_query.rb +++ b/app/models/queries/members/non_inherited_member_query.rb @@ -28,7 +28,7 @@ # See COPYRIGHT and LICENSE files for more details. # ++ -class Queries::Members::EntityMemberQuery < Queries::Members::MemberQuery +class Queries::Members::NonInheritedMemberQuery < Queries::Members::MemberQuery def default_scope Member.joins(:member_roles).merge(MemberRole.only_non_inherited) end From 540e1b411bb05cc093d8a26e7e107f5be33fdb17 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Mon, 1 Jul 2024 10:23:26 +0200 Subject: [PATCH 14/62] Allow filtering `entity_type` to `ProjectQuery` --- app/controllers/shares_controller.rb | 4 ---- app/models/queries/members/filters/entity_type_filter.rb | 5 ++++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/controllers/shares_controller.rb b/app/controllers/shares_controller.rb index 1e51f160eaa5..71abd7fc8183 100644 --- a/app/controllers/shares_controller.rb +++ b/app/controllers/shares_controller.rb @@ -41,10 +41,6 @@ class SharesController < ApplicationController before_action :authorize def dialog - puts "*" * 100 - pp(@shares) - puts "*" * 100 - @sharing_manageable = sharing_strategy.sharing_manageable? @available_roles = sharing_strategy.available_roles end diff --git a/app/models/queries/members/filters/entity_type_filter.rb b/app/models/queries/members/filters/entity_type_filter.rb index 5ddab94d13fa..60f3be86a865 100644 --- a/app/models/queries/members/filters/entity_type_filter.rb +++ b/app/models/queries/members/filters/entity_type_filter.rb @@ -32,7 +32,10 @@ def type end def allowed_values - [[WorkPackage.name, WorkPackage.name]] + [ + [WorkPackage.name, WorkPackage.name], + [ProjectQuery.name, ProjectQuery.name] + ] end def self.key From 72804295a90fd0db104b738f71913e3c6bb12dbd Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Mon, 1 Jul 2024 12:17:53 +0200 Subject: [PATCH 15/62] Implement contracts for sharing project queries --- .../shares/project_queries/base_extension.rb | 45 +++++++++++++++++++ .../shares/project_queries/create_contract.rb | 35 +++++++++++++++ .../shares/project_queries/delete_contract.rb | 37 +++++++++++++++ .../shares/project_queries/update_contract.rb | 35 +++++++++++++++ .../project_query_strategy.rb | 9 ++-- 5 files changed, 155 insertions(+), 6 deletions(-) create mode 100644 app/contracts/shares/project_queries/base_extension.rb create mode 100644 app/contracts/shares/project_queries/create_contract.rb create mode 100644 app/contracts/shares/project_queries/delete_contract.rb create mode 100644 app/contracts/shares/project_queries/update_contract.rb diff --git a/app/contracts/shares/project_queries/base_extension.rb b/app/contracts/shares/project_queries/base_extension.rb new file mode 100644 index 000000000000..688fce333a90 --- /dev/null +++ b/app/contracts/shares/project_queries/base_extension.rb @@ -0,0 +1,45 @@ +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2010-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 Shares + module ProjectQueries + module BaseExtension + extend ActiveSupport::Concern + + private + + def user_allowed_to_manage? + model.entity.editable? + end + + def assignable_role_class + ProjectQueryRole + end + end + end +end diff --git a/app/contracts/shares/project_queries/create_contract.rb b/app/contracts/shares/project_queries/create_contract.rb new file mode 100644 index 000000000000..58eeb1fdf130 --- /dev/null +++ b/app/contracts/shares/project_queries/create_contract.rb @@ -0,0 +1,35 @@ +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2010-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 Shares + module ProjectQueries + class CreateContract < Shares::CreateContract + include Shares::ProjectQueries::BaseExtension + end + end +end diff --git a/app/contracts/shares/project_queries/delete_contract.rb b/app/contracts/shares/project_queries/delete_contract.rb new file mode 100644 index 000000000000..b5486e3425da --- /dev/null +++ b/app/contracts/shares/project_queries/delete_contract.rb @@ -0,0 +1,37 @@ +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2010-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 Shares + module ProjectQueries + class DeleteContract < Shares::DeleteContract + # DeleteContract has its own permission check and does not care about the role class, + # so we do not need to include the BaseExtension here. + delete_permission -> { model.entity.editable? } + end + end +end diff --git a/app/contracts/shares/project_queries/update_contract.rb b/app/contracts/shares/project_queries/update_contract.rb new file mode 100644 index 000000000000..5148b92951e0 --- /dev/null +++ b/app/contracts/shares/project_queries/update_contract.rb @@ -0,0 +1,35 @@ +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2010-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 Shares + module ProjectQueries + class UpdateContract < Shares::UpdateContract + include Shares::ProjectQueries::BaseExtension + end + end +end diff --git a/app/models/sharing_strategies/project_query_strategy.rb b/app/models/sharing_strategies/project_query_strategy.rb index cc009ecd605e..4bef025c5231 100644 --- a/app/models/sharing_strategies/project_query_strategy.rb +++ b/app/models/sharing_strategies/project_query_strategy.rb @@ -22,18 +22,15 @@ def sharing_manageable? end def create_contract_class - # Shares::WorkPackages::CreateContract - EmptyContract + Shares::ProjectQueries::CreateContract end def update_contract_class - # Shares::WorkPackages::UpdateContract - EmptyContract + Shares::ProjectQueries::UpdateContract end def delete_contract_class - # Shares::WorkPackages::DeleteContract - EmptyContract + Shares::ProjectQueries::DeleteContract end end end From ee78a83fd932ff4999f279c9805a85106de30a3f Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Mon, 1 Jul 2024 15:35:10 +0200 Subject: [PATCH 16/62] BaseStrategy to show what can be implemented --- app/controllers/shares_controller.rb | 7 ++- .../sharing_strategies/base_strategy.rb | 59 +++++++++++++++++++ .../project_query_strategy.rb | 38 +++++++++--- .../work_package_strategy.rb | 36 ++++++++--- 4 files changed, 122 insertions(+), 18 deletions(-) create mode 100644 app/models/sharing_strategies/base_strategy.rb diff --git a/app/controllers/shares_controller.rb b/app/controllers/shares_controller.rb index 71abd7fc8183..539416f72b1a 100644 --- a/app/controllers/shares_controller.rb +++ b/app/controllers/shares_controller.rb @@ -307,14 +307,15 @@ def respond_with_bulk_removed_shares def load_entity # rubocop:disable Metrics/AbcSize if params["work_package_id"] @entity = WorkPackage.visible.find(params["work_package_id"]) - @sharing_strategy = SharingStrategies::WorkPackageStrategy.new(work_package: @entity) + @sharing_strategy = SharingStrategies::WorkPackageStrategy.new(@entity, user: current_user) elsif params["project_query_id"] @entity = ProjectQuery.visible.find(params["project_query_id"]) - @sharing_strategy = SharingStrategies::ProjectQueryStrategy.new(project_query: @entity) + @sharing_strategy = SharingStrategies::ProjectQueryStrategy.new(@entity, user: current_user) else raise ArgumentError, <<~ERROR Nested the SharesController under an entity controller that is not yet configured to support sharing. - Edit the SharesController#load_entity method to load the entity from the correct parent. + Edit the SharesController#load_entity method to load the entity from the correct parent and specify what sharing + strategy should be applied. Params: #{params.to_unsafe_h} Request Path: #{request.path} diff --git a/app/models/sharing_strategies/base_strategy.rb b/app/models/sharing_strategies/base_strategy.rb new file mode 100644 index 000000000000..dcceb0aacac4 --- /dev/null +++ b/app/models/sharing_strategies/base_strategy.rb @@ -0,0 +1,59 @@ +#-- 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 SharingStrategies + class BaseStrategy + attr_reader :entity, user + + def initialize(entity, user: User.current) + @entity = entity + @user = user + end + + def available_roles + # format: [{ label: "Role name", value: 42, description: "Role description", default: true }] + raise NotImplementedError, "Override in a subclass and return an array of roles that should be displayed" + end + + def sharing_manageable? + raise NotImplementedError, "Override in a subclass and return true if the current user can manage sharing" + end + + def create_contract_class + raise NotImplementedError, "Override in a subclass and return the contract class for creating a share" + end + + def update_contract_class + raise NotImplementedError, "Override in a subclass and return the contract class for updating a share" + end + + def delete_contract_class + raise NotImplementedError, "Override in a subclass and return the contract class for deleting a share" + end + end +end diff --git a/app/models/sharing_strategies/project_query_strategy.rb b/app/models/sharing_strategies/project_query_strategy.rb index 4bef025c5231..c1b7b4551b05 100644 --- a/app/models/sharing_strategies/project_query_strategy.rb +++ b/app/models/sharing_strategies/project_query_strategy.rb @@ -1,11 +1,33 @@ -module SharingStrategies - class ProjectQueryStrategy - attr_reader :project_query - - def initialize(project_query:) - @project_query = project_query - end +#-- 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 SharingStrategies + class ProjectQueryStrategy < BaseStrategy def available_roles ProjectQueryRole.all.map.with_index do |role, index| { @@ -18,7 +40,7 @@ def available_roles end def sharing_manageable? - @project_query.editable? + @entity.editable? end def create_contract_class diff --git a/app/models/sharing_strategies/work_package_strategy.rb b/app/models/sharing_strategies/work_package_strategy.rb index 6b40ee390066..b5a3770c9eb8 100644 --- a/app/models/sharing_strategies/work_package_strategy.rb +++ b/app/models/sharing_strategies/work_package_strategy.rb @@ -1,11 +1,33 @@ +#-- 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 SharingStrategies class WorkPackageStrategy - attr_reader :work_package - - def initialize(work_package:) - @work_package = work_package - end - def available_roles role_mapping = WorkPackageRole.unscoped.pluck(:builtin, :id).to_h @@ -24,7 +46,7 @@ def available_roles end def sharing_manageable? - User.current.allowed_in_project?(:share_work_packages, @work_package.project) + user.allowed_in_project?(:share_work_packages, @entity.project) end def create_contract_class From a573110dafe213632b3b5da4b350baf4159ed9c6 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Mon, 1 Jul 2024 15:35:10 +0200 Subject: [PATCH 17/62] BaseStrategy to show what can be implemented --- app/models/sharing_strategies/base_strategy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/sharing_strategies/base_strategy.rb b/app/models/sharing_strategies/base_strategy.rb index dcceb0aacac4..08218942747a 100644 --- a/app/models/sharing_strategies/base_strategy.rb +++ b/app/models/sharing_strategies/base_strategy.rb @@ -28,7 +28,7 @@ module SharingStrategies class BaseStrategy - attr_reader :entity, user + attr_reader :entity, :user def initialize(entity, user: User.current) @entity = entity From 28d9de0553289b5e60d138266b370c5a6e39e914 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Mon, 1 Jul 2024 16:27:01 +0200 Subject: [PATCH 18/62] Remove redundant naming from Strategy --- app/models/sharing_strategies/base_strategy.rb | 2 +- app/models/sharing_strategies/project_query_strategy.rb | 2 +- app/models/sharing_strategies/work_package_strategy.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/sharing_strategies/base_strategy.rb b/app/models/sharing_strategies/base_strategy.rb index 08218942747a..6a8b2f7d7a76 100644 --- a/app/models/sharing_strategies/base_strategy.rb +++ b/app/models/sharing_strategies/base_strategy.rb @@ -40,7 +40,7 @@ def available_roles raise NotImplementedError, "Override in a subclass and return an array of roles that should be displayed" end - def sharing_manageable? + def manageable? raise NotImplementedError, "Override in a subclass and return true if the current user can manage sharing" end diff --git a/app/models/sharing_strategies/project_query_strategy.rb b/app/models/sharing_strategies/project_query_strategy.rb index c1b7b4551b05..203277b503b6 100644 --- a/app/models/sharing_strategies/project_query_strategy.rb +++ b/app/models/sharing_strategies/project_query_strategy.rb @@ -39,7 +39,7 @@ def available_roles end end - def sharing_manageable? + def manageable? @entity.editable? end diff --git a/app/models/sharing_strategies/work_package_strategy.rb b/app/models/sharing_strategies/work_package_strategy.rb index b5a3770c9eb8..1e59a99cb08f 100644 --- a/app/models/sharing_strategies/work_package_strategy.rb +++ b/app/models/sharing_strategies/work_package_strategy.rb @@ -45,7 +45,7 @@ def available_roles ] end - def sharing_manageable? + def manageable? user.allowed_in_project?(:share_work_packages, @entity.project) end From e3f2cc273cc7f5111eaf531cd3bccd18b27ad41b Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Mon, 1 Jul 2024 16:28:36 +0200 Subject: [PATCH 19/62] Only pass strategy into the modal and modal body --- app/components/shares/modal_body_component.rb | 16 ++++----- app/controllers/shares_controller.rb | 35 +++++++------------ app/views/shares/dialog.turbo_stream.erb | 6 +--- 3 files changed, 21 insertions(+), 36 deletions(-) diff --git a/app/components/shares/modal_body_component.rb b/app/components/shares/modal_body_component.rb index 588f60d32016..82755950de60 100644 --- a/app/components/shares/modal_body_component.rb +++ b/app/components/shares/modal_body_component.rb @@ -33,23 +33,21 @@ class ModalBodyComponent < ApplicationComponent # rubocop:disable OpenProject/Ad include OpTurbo::Streamable include OpPrimer::ComponentHelpers - attr_reader :entity, + attr_reader :strategy, + :entity, :shares, :available_roles, :sharing_manageable, :errors - def initialize(entity:, - shares:, - available_roles:, - sharing_manageable:, - errors: nil) + def initialize(strategy:, shares:, errors: nil) super - @entity = entity + @strategy = strategy + @entity = strategy.entity @shares = shares - @available_roles = available_roles - @sharing_manageable = sharing_manageable + @available_roles = strategy.available_roles + @sharing_manageable = strategy.manageable? @errors = errors end diff --git a/app/controllers/shares_controller.rb b/app/controllers/shares_controller.rb index 539416f72b1a..33b8e9672966 100644 --- a/app/controllers/shares_controller.rb +++ b/app/controllers/shares_controller.rb @@ -40,23 +40,14 @@ class SharesController < ApplicationController # TODO: Permission checks need to be implemented correctly depending on entity before_action :authorize - def dialog - @sharing_manageable = sharing_strategy.sharing_manageable? - @available_roles = sharing_strategy.available_roles - end + def dialog; end def index unless @query.valid? flash.now[:error] = query.errors.full_messages end - render Shares::ModalBodyComponent.new( - entity: @entity, - shares: @shares, - errors: @errors, - sharing_manageable: sharing_strategy.sharing_manageable?, - available_roles: sharing_strategy.available_roles - ), layout: nil + render Shares::ModalBodyComponent.new(strategy: sharing_strategy, shares: @shares, errors: @errors), layout: nil end def create # rubocop:disable Metrics/AbcSize,Metrics/PerceivedComplexity @@ -166,7 +157,7 @@ def respond_with_replace_modal entity: @entity, available_roles: sharing_strategy.available_roles, shares: @new_shares || load_shares, - sharing_manageable: sharing_strategy.sharing_manageable?, + sharing_manageable: sharing_strategy.manageable?, errors: @errors ) ) @@ -179,7 +170,7 @@ def respond_with_prepend_shares # rubocop:disable Metrics/AbcSize component: Shares::InviteUserFormComponent.new( entity: @entity, available_roles: sharing_strategy.available_roles, - sharing_manageable: sharing_strategy.sharing_manageable?, + sharing_manageable: sharing_strategy.manageable?, errors: @errors ) ) @@ -188,7 +179,7 @@ def respond_with_prepend_shares # rubocop:disable Metrics/AbcSize component: Shares::CounterComponent.new( entity: @entity, count: current_visible_member_count, - sharing_manageable: sharing_strategy.sharing_manageable? + sharing_manageable: sharing_strategy.manageable? ) ) @@ -197,12 +188,12 @@ def respond_with_prepend_shares # rubocop:disable Metrics/AbcSize component: Shares::ShareRowComponent.new( share:, available_roles: sharing_strategy.available_roles, - sharing_manageable: sharing_strategy.sharing_manageable? + sharing_manageable: sharing_strategy.manageable? ), target_component: Shares::ModalBodyComponent.new( entity: @entity, available_roles: sharing_strategy.available_roles, - sharing_manageable: sharing_strategy.sharing_manageable?, + sharing_manageable: sharing_strategy.manageable?, shares: load_shares, errors: @errors ) @@ -217,7 +208,7 @@ def respond_with_new_invite_form component: Shares::InviteUserFormComponent.new( entity: @entity, available_roles: sharing_strategy.available_roles, - sharing_manageable: sharing_strategy.sharing_manageable?, + sharing_manageable: sharing_strategy.manageable?, errors: @errors ) ) @@ -242,14 +233,14 @@ def respond_with_remove_share component: Shares::ShareRowComponent.new( share: @share, available_roles: sharing_strategy.available_roles, - sharing_manageable: sharing_strategy.sharing_manageable? + sharing_manageable: sharing_strategy.manageable? ) ) update_via_turbo_stream( component: Shares::CounterComponent.new( entity: @entity, count: current_visible_member_count, - sharing_manageable: sharing_strategy.sharing_manageable? + sharing_manageable: sharing_strategy.manageable? ) ) @@ -260,7 +251,7 @@ def respond_with_update_user_details update_via_turbo_stream( component: Shares::UserDetailsComponent.new( share: @share, - manager_mode: sharing_strategy.sharing_manageable?, + manager_mode: sharing_strategy.manageable?, invite_resent: true ) ) @@ -288,7 +279,7 @@ def respond_with_bulk_removed_shares component: Shares::ShareRowComponent.new( share:, available_roles: sharing_strategy.available_roles, - sharing_manageable: sharing_strategy.sharing_manageable? + sharing_manageable: sharing_strategy.manageable? ) ) end @@ -297,7 +288,7 @@ def respond_with_bulk_removed_shares component: Shares::CounterComponent.new( entity: @entity, count: current_visible_member_count, - sharing_manageable: sharing_strategy.sharing_manageable? + sharing_manageable: sharing_strategy.manageable? ) ) diff --git a/app/views/shares/dialog.turbo_stream.erb b/app/views/shares/dialog.turbo_stream.erb index ac31607f8f81..a9fb2332c8b7 100644 --- a/app/views/shares/dialog.turbo_stream.erb +++ b/app/views/shares/dialog.turbo_stream.erb @@ -4,11 +4,7 @@ size: :xlarge)) do |d| d.with_header(variant: :large) d.with_body do - render(Shares::ModalBodyComponent.new(entity: @entity, - shares: @shares, - errors: @errors, - sharing_manageable: @sharing_manageable, - available_roles: @available_roles)) + render(Shares::ModalBodyComponent.new(strategy: @sharing_strategy, shares: @shares, errors: @errors)) end end end From 218ee5bf82dbf5d694ccdc9e1a65e00a1db31f58 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Mon, 1 Jul 2024 16:28:56 +0200 Subject: [PATCH 20/62] Invite User Form uses strategy --- .../shares/invite_user_form_component.html.erb | 18 ++++++++---------- .../shares/invite_user_form_component.rb | 16 +++++++--------- .../shares/modal_body_component.html.erb | 5 +---- 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/app/components/shares/invite_user_form_component.html.erb b/app/components/shares/invite_user_form_component.html.erb index 5900c1a2ef47..6cfe10cc5061 100644 --- a/app/components/shares/invite_user_form_component.html.erb +++ b/app/components/shares/invite_user_form_component.html.erb @@ -1,17 +1,15 @@ <%= 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 @@ -19,7 +17,7 @@ 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' }) ) @@ -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 @@ -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 @@ -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 %> diff --git a/app/components/shares/invite_user_form_component.rb b/app/components/shares/invite_user_form_component.rb index 48ae8e0715ed..92cd7bf922bc 100644 --- a/app/components/shares/invite_user_form_component.rb +++ b/app/components/shares/invite_user_form_component.rb @@ -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 diff --git a/app/components/shares/modal_body_component.html.erb b/app/components/shares/modal_body_component.html.erb index 8f7252944dd2..05cde3aef1d6 100644 --- a/app/components/shares/modal_body_component.html.erb +++ b/app/components/shares/modal_body_component.html.erb @@ -2,10 +2,7 @@ component_wrapper(tag: 'turbo-frame') do flex_layout(data: { turbo: true }) do |modal_content| modal_content.with_row do - render(Shares::InviteUserFormComponent.new(entity: @entity, - available_roles: @available_roles, - sharing_manageable: @sharing_manageable, - errors: @errors)) + render(Shares::InviteUserFormComponent.new(strategy: strategy, errors: errors)) end modal_content.with_row(mt: 3, From 2c9a975544408ea4c4ba3e6d3bcf44cd1b7d9b67 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Mon, 1 Jul 2024 17:08:36 +0200 Subject: [PATCH 21/62] Use Strategy in all components --- .../bulk_permission_button_component.rb | 6 ++-- .../shares/counter_component.html.erb | 2 +- app/components/shares/counter_component.rb | 14 ++++------ .../shares/modal_body_component.html.erb | 23 ++++++--------- app/components/shares/modal_body_component.rb | 28 +++++++++---------- app/components/shares/share_row_component.rb | 17 ++++++----- app/models/members/scopes/of_entity.rb | 7 +++-- 7 files changed, 44 insertions(+), 53 deletions(-) diff --git a/app/components/shares/bulk_permission_button_component.rb b/app/components/shares/bulk_permission_button_component.rb index f9a89e186737..b42c5de1dad5 100644 --- a/app/components/shares/bulk_permission_button_component.rb +++ b/app/components/shares/bulk_permission_button_component.rb @@ -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 diff --git a/app/components/shares/counter_component.html.erb b/app/components/shares/counter_component.html.erb index e9281848a582..2e58b8d310de 100644 --- a/app/components/shares/counter_component.html.erb +++ b/app/components/shares/counter_component.html.erb @@ -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:)) diff --git a/app/components/shares/counter_component.rb b/app/components/shares/counter_component.rb index 145ee3b8c530..c2581bfb5dba 100644 --- a/app/components/shares/counter_component.rb +++ b/app/components/shares/counter_component.rb @@ -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 diff --git a/app/components/shares/modal_body_component.html.erb b/app/components/shares/modal_body_component.html.erb index 05cde3aef1d6..789054fb45f7 100644 --- a/app/components/shares/modal_body_component.html.erb +++ b/app/components/shares/modal_body_component.html.erb @@ -2,7 +2,7 @@ component_wrapper(tag: 'turbo-frame') do flex_layout(data: { turbo: true }) do |modal_content| modal_content.with_row do - render(Shares::InviteUserFormComponent.new(strategy: strategy, errors: errors)) + render(Shares::InviteUserFormComponent.new(strategy:, errors: errors)) end modal_content.with_row(mt: 3, @@ -13,9 +13,7 @@ border_box.with_header(color: :muted, data: { 'test-selector': 'op-share-dialog-header' }) do grid_layout('op-share-dialog-modal-body--header', tag: :div, align_items: :center) do |header_grid| header_grid.with_area(:counter, tag: :div) do - render(Shares::CounterComponent.new(entity: @entity, - count: @shares.size, - sharing_manageable: @sharing_manageable)) + render(Shares::CounterComponent.new(strategy:, count: @shares.size)) end header_grid.with_area(:actions, @@ -57,7 +55,7 @@ button.with_trailing_action_icon(icon: "triangle-down") I18n.t('sharing.filter.role') end - @available_roles.each do |role_hash| + strategy.available_roles.each do |role_hash| menu.with_item(label: role_hash[:label], href: filter_url(role_option: role_hash), method: :get, @@ -74,13 +72,11 @@ tag: :div, hidden: true, # Prevent flicker on initial render data: { 'shares--bulk-selection-target': 'bulkActions' }) do - if @sharing_manageable - concat( - render(Shares::BulkPermissionButtonComponent.new(entity: @entity, available_roles: @available_roles)) - ) + if strategy.manageable? + concat(render(Shares::BulkPermissionButtonComponent.new(strategy:))) concat( - form_with(url: url_for([:bulk, @entity, Member]), + form_with(url: url_for([:bulk, entity, Member]), method: :delete, data: { 'shares--bulk-selection-target': 'bulkForm' }) do render(Primer::Beta::IconButton.new(icon: "trash", @@ -95,7 +91,7 @@ end end - if @shares.blank? + if @shares.none? border_box.with_row do render(Primer::Beta::Blankslate.new) do |component| component.with_visual_icon(icon: blankslate_config[:icon], size: :medium) @@ -111,10 +107,7 @@ end else @shares.each do |share| - render(Shares::ShareRowComponent.new(share: share, - available_roles: @available_roles, - sharing_manageable: @sharing_manageable, - container: border_box)) + render(Shares::ShareRowComponent.new(share:, strategy:, container: border_box)) end end end diff --git a/app/components/shares/modal_body_component.rb b/app/components/shares/modal_body_component.rb index 82755950de60..de3a761b1404 100644 --- a/app/components/shares/modal_body_component.rb +++ b/app/components/shares/modal_body_component.rb @@ -36,8 +36,6 @@ class ModalBodyComponent < ApplicationComponent # rubocop:disable OpenProject/Ad attr_reader :strategy, :entity, :shares, - :available_roles, - :sharing_manageable, :errors def initialize(strategy:, shares:, errors: nil) @@ -46,8 +44,6 @@ def initialize(strategy:, shares:, errors: nil) @strategy = strategy @entity = strategy.entity @shares = shares - @available_roles = strategy.available_roles - @sharing_manageable = strategy.manageable? @errors = errors end @@ -104,15 +100,15 @@ def type_filter_options end end - def type_filter_option_active?(option) + def type_filter_option_active?(option) # rubocop:disable Metrics/AbcSize principal_type_filter_value = current_filter_value(params[:filters], "principal_type") project_member_filter_value = current_filter_value(params[:filters], "also_project_member") - return false if principal_type_filter_value.nil? || project_member_filter_value.nil? + return false if principal_type_filter_value.nil? || (project_scoped_entity? && project_member_filter_value.nil?) principal_type_checked = option[:value][:principal_type] == principal_type_filter_value - membership_selected = + membership_selected = !project_scoped_entity? || option[:value][:project_member] == ActiveRecord::Type::Boolean.new.cast(project_member_filter_value) principal_type_checked && membership_selected @@ -123,7 +119,7 @@ def role_filter_option_active?(option) return false if role_filter_value.nil? - selected_role = @available_roles.find { _1[:value] == option[:value] } + selected_role = strategy.available_roles.find { _1[:value] == option[:value] } selected_role[:value] == role_filter_value.to_i end @@ -181,14 +177,18 @@ def apply_type_filter(option) end def type_filter_for(option) - filter = [] - if ActiveRecord::Type::Boolean.new.cast(option[:value][:project_member]) - filter.push({ also_project_member: { operator: "=", values: [OpenProject::Database::DB_VALUE_TRUE] } }) - else - filter.push({ also_project_member: { operator: "=", values: [OpenProject::Database::DB_VALUE_FALSE] } }) + filter = [ + { principal_type: { operator: "=", values: [option[:value][:principal_type]] } } + ] + + if project_scoped_entity? + if ActiveRecord::Type::Boolean.new.cast(option[:value][:project_member]) + filter.push({ also_project_member: { operator: "=", values: [OpenProject::Database::DB_VALUE_TRUE] } }) + else + filter.push({ also_project_member: { operator: "=", values: [OpenProject::Database::DB_VALUE_FALSE] } }) + end end - filter.push({ principal_type: { operator: "=", values: [option[:value][:principal_type]] } }) filter end diff --git a/app/components/shares/share_row_component.rb b/app/components/shares/share_row_component.rb index 8f9a1dd6e53f..b047f1435290 100644 --- a/app/components/shares/share_row_component.rb +++ b/app/components/shares/share_row_component.rb @@ -34,17 +34,14 @@ class ShareRowComponent < ApplicationComponent # rubocop:disable OpenProject/Add include OpTurbo::Streamable include OpPrimer::ComponentHelpers - def initialize(share:, - available_roles:, - sharing_manageable:, - container: nil) + def initialize(share:, strategy:, container: nil) super + @strategy = strategy @share = share - @entity = share.entity + @entity = strategy.entity @principal = share.principal - @available_roles = available_roles - @sharing_manageable = sharing_manageable + @available_roles = strategy.available_roles @container = container end @@ -54,13 +51,15 @@ def wrapper_uniq_by private - attr_reader :share, :entity, :principal, :container, :available_roles + attr_reader :share, :entity, :principal, :container, :available_roles, :strategy def share_editable? @share_editable ||= User.current != share.principal && sharing_manageable? end - def sharing_manageable? = @sharing_manageable + def sharing_manageable? + strategy.manageable? + end def grid_css_classes if sharing_manageable? diff --git a/app/models/members/scopes/of_entity.rb b/app/models/members/scopes/of_entity.rb index b9178a060458..b8413a4d7bd3 100644 --- a/app/models/members/scopes/of_entity.rb +++ b/app/models/members/scopes/of_entity.rb @@ -33,8 +33,11 @@ module OfEntity class_methods do # Find all members of a specific Work Package def of_entity(entity) - of_any_entity - .where(entity:) + if entity.respond_to?(:project) + where(project: entity.project, entity:) + else + where(project: nil, entity:) + end end end end From 644515633f12bb4bfc8bc2dd916d25ba9b12155b Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 2 Jul 2024 08:34:03 +0200 Subject: [PATCH 22/62] WorkPackageStrategy was missing the BaseStrategy --- app/models/sharing_strategies/work_package_strategy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/sharing_strategies/work_package_strategy.rb b/app/models/sharing_strategies/work_package_strategy.rb index 1e59a99cb08f..1935b0d0c205 100644 --- a/app/models/sharing_strategies/work_package_strategy.rb +++ b/app/models/sharing_strategies/work_package_strategy.rb @@ -27,7 +27,7 @@ #++ module SharingStrategies - class WorkPackageStrategy + class WorkPackageStrategy < BaseStrategy def available_roles role_mapping = WorkPackageRole.unscoped.pluck(:builtin, :id).to_h From b4927ccac692da5877a33431f16219d11a9f6fbf Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 2 Jul 2024 10:02:16 +0200 Subject: [PATCH 23/62] Correctly pass all arguments for render calls --- app/controllers/shares_controller.rb | 40 +++++++++------------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/app/controllers/shares_controller.rb b/app/controllers/shares_controller.rb index 33b8e9672966..e7706b0c3d95 100644 --- a/app/controllers/shares_controller.rb +++ b/app/controllers/shares_controller.rb @@ -154,10 +154,8 @@ def create_or_update_share(user_id, role_ids) def respond_with_replace_modal replace_via_turbo_stream( component: Shares::ModalBodyComponent.new( - entity: @entity, - available_roles: sharing_strategy.available_roles, + strategy: sharing_strategy, shares: @new_shares || load_shares, - sharing_manageable: sharing_strategy.manageable?, errors: @errors ) ) @@ -165,21 +163,18 @@ def respond_with_replace_modal respond_with_turbo_streams end - def respond_with_prepend_shares # rubocop:disable Metrics/AbcSize + def respond_with_prepend_shares replace_via_turbo_stream( component: Shares::InviteUserFormComponent.new( - entity: @entity, - available_roles: sharing_strategy.available_roles, - sharing_manageable: sharing_strategy.manageable?, + strategy: sharing_strategy, errors: @errors ) ) update_via_turbo_stream( component: Shares::CounterComponent.new( - entity: @entity, - count: current_visible_member_count, - sharing_manageable: sharing_strategy.manageable? + strategy: sharing_strategy, + count: current_visible_member_count ) ) @@ -187,13 +182,10 @@ def respond_with_prepend_shares # rubocop:disable Metrics/AbcSize prepend_via_turbo_stream( component: Shares::ShareRowComponent.new( share:, - available_roles: sharing_strategy.available_roles, - sharing_manageable: sharing_strategy.manageable? + strategy: sharing_strategy ), target_component: Shares::ModalBodyComponent.new( - entity: @entity, - available_roles: sharing_strategy.available_roles, - sharing_manageable: sharing_strategy.manageable?, + strategy: sharing_strategy, shares: load_shares, errors: @errors ) @@ -206,9 +198,7 @@ def respond_with_prepend_shares # rubocop:disable Metrics/AbcSize def respond_with_new_invite_form replace_via_turbo_stream( component: Shares::InviteUserFormComponent.new( - entity: @entity, - available_roles: sharing_strategy.available_roles, - sharing_manageable: sharing_strategy.manageable?, + strategy: sharing_strategy, errors: @errors ) ) @@ -232,15 +222,13 @@ def respond_with_remove_share remove_via_turbo_stream( component: Shares::ShareRowComponent.new( share: @share, - available_roles: sharing_strategy.available_roles, - sharing_manageable: sharing_strategy.manageable? + strategy: sharing_strategy ) ) update_via_turbo_stream( component: Shares::CounterComponent.new( - entity: @entity, - count: current_visible_member_count, - sharing_manageable: sharing_strategy.manageable? + strategy: sharing_strategy, + count: current_visible_member_count ) ) @@ -278,17 +266,15 @@ def respond_with_bulk_removed_shares remove_via_turbo_stream( component: Shares::ShareRowComponent.new( share:, - available_roles: sharing_strategy.available_roles, - sharing_manageable: sharing_strategy.manageable? + strategy: sharing_strategy ) ) end update_via_turbo_stream( component: Shares::CounterComponent.new( - entity: @entity, count: current_visible_member_count, - sharing_manageable: sharing_strategy.manageable? + strategy: sharing_strategy ) ) From 444df516ced02b0d4ffa6fabca45492cb31fe4a3 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 2 Jul 2024 10:52:44 +0200 Subject: [PATCH 24/62] Extract blank slate config into its own component and allow overriding --- .../shares/empty_state_component.html.erb | 15 +++++ .../shares/empty_state_component.rb | 58 +++++++++++++++++++ .../shares/modal_body_component.html.erb | 14 ++--- app/components/shares/modal_body_component.rb | 14 ----- .../sharing_strategies/base_strategy.rb | 19 ++++++ 5 files changed, 96 insertions(+), 24 deletions(-) create mode 100644 app/components/shares/empty_state_component.html.erb create mode 100644 app/components/shares/empty_state_component.rb diff --git a/app/components/shares/empty_state_component.html.erb b/app/components/shares/empty_state_component.html.erb new file mode 100644 index 000000000000..64e117e461b4 --- /dev/null +++ b/app/components/shares/empty_state_component.html.erb @@ -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 %> diff --git a/app/components/shares/empty_state_component.rb b/app/components/shares/empty_state_component.rb new file mode 100644 index 000000000000..df896b878297 --- /dev/null +++ b/app/components/shares/empty_state_component.rb @@ -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 + 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 + @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 diff --git a/app/components/shares/modal_body_component.html.erb b/app/components/shares/modal_body_component.html.erb index 789054fb45f7..83b42696b142 100644 --- a/app/components/shares/modal_body_component.html.erb +++ b/app/components/shares/modal_body_component.html.erb @@ -93,17 +93,11 @@ if @shares.none? border_box.with_row do - 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)) { blankslate_config[:description_text] } - end - end + if strategy.custom_empty_state_component? + render(strategy.empty_state_component.new(strategy: strategy)) + else + render(Shares::EmptyStateComponent.new(strategy: strategy)) end - end end else @shares.each do |share| diff --git a/app/components/shares/modal_body_component.rb b/app/components/shares/modal_body_component.rb index de3a761b1404..d1b96c8f72a9 100644 --- a/app/components/shares/modal_body_component.rb +++ b/app/components/shares/modal_body_component.rb @@ -65,20 +65,6 @@ def insert_target_modifier_id "op-share-dialog-active-shares" end - def blankslate_config # rubocop:disable Metrics/AbcSize - @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 - def type_filter_options if project_scoped_entity? [ diff --git a/app/models/sharing_strategies/base_strategy.rb b/app/models/sharing_strategies/base_strategy.rb index 6a8b2f7d7a76..e92f1d362c84 100644 --- a/app/models/sharing_strategies/base_strategy.rb +++ b/app/models/sharing_strategies/base_strategy.rb @@ -55,5 +55,24 @@ def update_contract_class def delete_contract_class raise NotImplementedError, "Override in a subclass and return the contract class for deleting a share" end + + def custom_body_component? + additional_body_component.present? + end + + # Override by returning a component class that should be rendered in the sharing dialog above the table of shares + def additional_body_component + nil + end + + def custom_empty_state_component? + empty_state_component.present? + end + + # Override by returning a component class that should be rendered in the sharing dialog instead of the table of shares + # when there is no share yet + def empty_state_component + nil + end end end From 5f46802e69d19d5245affbf653c8b2e4545a50f4 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 2 Jul 2024 10:55:18 +0200 Subject: [PATCH 25/62] Allow adding custom modal component via strategy --- app/components/shares/modal_body_component.html.erb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/components/shares/modal_body_component.html.erb b/app/components/shares/modal_body_component.html.erb index 83b42696b142..e0c2457ebce6 100644 --- a/app/components/shares/modal_body_component.html.erb +++ b/app/components/shares/modal_body_component.html.erb @@ -1,6 +1,10 @@ <%= component_wrapper(tag: 'turbo-frame') do flex_layout(data: { turbo: true }) do |modal_content| + if strategy.custom_body_component? + render(strategy.additional_body_component.new(strategy: strategy, modal_body_container: modal_content)) + end + modal_content.with_row do render(Shares::InviteUserFormComponent.new(strategy:, errors: errors)) end From dccbdcaefb6183ce7bbf6f60af9b8f8a706dcbc0 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 2 Jul 2024 11:29:06 +0200 Subject: [PATCH 26/62] Add a `keepOpenOnSubmit` attribute to the dialog and remove duplicate code --- app/views/shares/dialog.turbo_stream.erb | 1 + frontend/src/turbo/setup.ts | 12 ------------ frontend/src/turbo/turbo-event-listeners.ts | 8 ++++++-- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/app/views/shares/dialog.turbo_stream.erb b/app/views/shares/dialog.turbo_stream.erb index a9fb2332c8b7..8d44b236e467 100644 --- a/app/views/shares/dialog.turbo_stream.erb +++ b/app/views/shares/dialog.turbo_stream.erb @@ -1,6 +1,7 @@ <%= turbo_stream.dialog do render(Primer::Alpha::Dialog.new(title: t(:label_share_project_list), id: 'share_list', + data: { 'keep-open-on-submit': true }, size: :xlarge)) do |d| d.with_header(variant: :large) d.with_body do diff --git a/frontend/src/turbo/setup.ts b/frontend/src/turbo/setup.ts index 0f10dd45405e..9a3f5d27a879 100644 --- a/frontend/src/turbo/setup.ts +++ b/frontend/src/turbo/setup.ts @@ -18,15 +18,3 @@ document.addEventListener('turbo:frame-missing', (event:CustomEvent) => { event.preventDefault(); visit(response.url); }); - -// Close the primer dialog when the form inside has been submitted with a success response -// It is necessary to close the primer dialog using the `close()` method, otherwise -// it will leave an overflow:hidden attribute on the body, which prevents scrolling on the page. -document.addEventListener('turbo:submit-end', (event:CustomEvent) => { - const { detail: { success }, target } = event as { detail:{ success:boolean }, target:EventTarget }; - - if (success && target instanceof HTMLFormElement) { - const dialog = target.closest('modal-dialog') as ModalDialogElement; - dialog && dialog.close(true); - } -}); diff --git a/frontend/src/turbo/turbo-event-listeners.ts b/frontend/src/turbo/turbo-event-listeners.ts index 23a76c58a1c1..3ef186bc28e9 100644 --- a/frontend/src/turbo/turbo-event-listeners.ts +++ b/frontend/src/turbo/turbo-event-listeners.ts @@ -1,5 +1,9 @@ export function addTurboEventListeners() { - // Close the primer dialog when the form inside has been submitted with a success response + // Close the primer dialog when the form inside has been submitted with a success response. + // + // If you want to keep the dialog open even after a successful form submission, you can add the + // `data-keep-open-on-submit="true"` attribute to the dialog element. + // // It is necessary to close the primer dialog using the `close()` method, otherwise // it will leave an overflow:hidden attribute on the body, which prevents scrolling on the page. document.addEventListener('turbo:submit-end', (event:CustomEvent) => { @@ -7,7 +11,7 @@ export function addTurboEventListeners() { if (success && target instanceof HTMLFormElement) { const dialog = target.closest('dialog') as HTMLDialogElement; - dialog && dialog.close(); + dialog && dialog.dataset.keepOpenOnSubmit !== 'true' && dialog.close(); } }); } From 4f93c2e21a2a0dcb95453bffb233cd13e1b5a056 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 2 Jul 2024 11:41:51 +0200 Subject: [PATCH 27/62] Clean up JS code --- frontend/src/turbo/setup.ts | 1 - frontend/src/turbo/turbo-event-listeners.ts | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/turbo/setup.ts b/frontend/src/turbo/setup.ts index 9a3f5d27a879..9bd905bbb9a1 100644 --- a/frontend/src/turbo/setup.ts +++ b/frontend/src/turbo/setup.ts @@ -2,7 +2,6 @@ import '../typings/shims.d.ts'; import * as Turbo from '@hotwired/turbo'; import { registerDialogStreamAction } from './dialog-stream-action'; import { addTurboEventListeners } from './turbo-event-listeners'; -import { ModalDialogElement } from '@openproject/primer-view-components/app/components/primer/alpha/modal_dialog'; // Disable default turbo-drive for now as we don't need it for now AND it breaks angular routing Turbo.session.drive = false; diff --git a/frontend/src/turbo/turbo-event-listeners.ts b/frontend/src/turbo/turbo-event-listeners.ts index 3ef186bc28e9..3e9b24c02341 100644 --- a/frontend/src/turbo/turbo-event-listeners.ts +++ b/frontend/src/turbo/turbo-event-listeners.ts @@ -11,7 +11,10 @@ export function addTurboEventListeners() { if (success && target instanceof HTMLFormElement) { const dialog = target.closest('dialog') as HTMLDialogElement; - dialog && dialog.dataset.keepOpenOnSubmit !== 'true' && dialog.close(); + + if (dialog && dialog.dataset.keepOpenOnSubmit !== 'true') { + dialog.close(); + } } }); } From 4a60d82d1dfc4fc26e0e34a62de8d31eb7ef8043 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 2 Jul 2024 11:48:47 +0200 Subject: [PATCH 28/62] Use correct ID for the modal dialog --- app/views/shares/dialog.turbo_stream.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shares/dialog.turbo_stream.erb b/app/views/shares/dialog.turbo_stream.erb index 8d44b236e467..7c16bd93ef15 100644 --- a/app/views/shares/dialog.turbo_stream.erb +++ b/app/views/shares/dialog.turbo_stream.erb @@ -1,6 +1,6 @@ <%= turbo_stream.dialog do render(Primer::Alpha::Dialog.new(title: t(:label_share_project_list), - id: 'share_list', + id: 'sharing-modal', data: { 'keep-open-on-submit': true }, size: :xlarge)) do |d| d.with_header(variant: :large) From 5635e36e8e67e8cdcf0c6fddf191e621fd221752 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 2 Jul 2024 12:14:48 +0200 Subject: [PATCH 29/62] Add an injectable body for the project modal --- .../shares/modal_body_component.html.erb | 2 +- .../public_flag_component.html.erb | 1 + .../project_queries/public_flag_component.rb | 49 +++++++++++++++++++ .../project_query_strategy.rb | 4 ++ 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 app/components/shares/project_queries/public_flag_component.html.erb create mode 100644 app/components/shares/project_queries/public_flag_component.rb diff --git a/app/components/shares/modal_body_component.html.erb b/app/components/shares/modal_body_component.html.erb index e0c2457ebce6..2ee90bfccba5 100644 --- a/app/components/shares/modal_body_component.html.erb +++ b/app/components/shares/modal_body_component.html.erb @@ -5,7 +5,7 @@ render(strategy.additional_body_component.new(strategy: strategy, modal_body_container: modal_content)) end - modal_content.with_row do + modal_content.with_row(mt: (strategy.custom_body_component? ? 3 : nil)) do render(Shares::InviteUserFormComponent.new(strategy:, errors: errors)) end diff --git a/app/components/shares/project_queries/public_flag_component.html.erb b/app/components/shares/project_queries/public_flag_component.html.erb new file mode 100644 index 000000000000..3dbc32862bcb --- /dev/null +++ b/app/components/shares/project_queries/public_flag_component.html.erb @@ -0,0 +1 @@ +<%= container.with_row { "Hallo, das ist ein Test" } %> diff --git a/app/components/shares/project_queries/public_flag_component.rb b/app/components/shares/project_queries/public_flag_component.rb new file mode 100644 index 000000000000..4dad89356b6d --- /dev/null +++ b/app/components/shares/project_queries/public_flag_component.rb @@ -0,0 +1,49 @@ +# 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. +# ++ + +module Shares + module ProjectQueries + class PublicFlagComponent < ApplicationComponent # rubocop:disable OpenProject/AddPreviewForViewComponent + include OpTurbo::Streamable + include OpPrimer::ComponentHelpers + + def initialize(strategy:, modal_body_container:) + super + + @strategy = strategy + @container = modal_body_container + end + + private + + attr_reader :strategy, :container + end + end +end diff --git a/app/models/sharing_strategies/project_query_strategy.rb b/app/models/sharing_strategies/project_query_strategy.rb index 203277b503b6..c9ad2c383e47 100644 --- a/app/models/sharing_strategies/project_query_strategy.rb +++ b/app/models/sharing_strategies/project_query_strategy.rb @@ -54,5 +54,9 @@ def update_contract_class def delete_contract_class Shares::ProjectQueries::DeleteContract end + + def additional_body_component + Shares::ProjectQueries::PublicFlagComponent + end end end From 78215ce1d80a577221d19ab24792ec65e7e9426c Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 2 Jul 2024 12:32:42 +0200 Subject: [PATCH 30/62] Do not display my own public queries twice --- app/menus/projects/menu.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/menus/projects/menu.rb b/app/menus/projects/menu.rb index 1726f9e8e01d..f7c28f20dd19 100644 --- a/app/menus/projects/menu.rb +++ b/app/menus/projects/menu.rb @@ -98,7 +98,7 @@ def static_filters(ids) def public_filters persisted_filters - .select(&:public?) + .select { |query| query.public? && query.user != current_user } .map { |query| menu_item(query.name, query_id: query.id) } end From 327a61492256aec85dc96983757c16d66d4da047 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 2 Jul 2024 12:34:13 +0200 Subject: [PATCH 31/62] Hide sharing behind the feature flag --- app/components/projects/index_page_header_component.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/projects/index_page_header_component.html.erb b/app/components/projects/index_page_header_component.html.erb index 63c90ec6b29a..df5641ccd3ec 100644 --- a/app/components/projects/index_page_header_component.html.erb +++ b/app/components/projects/index_page_header_component.html.erb @@ -21,7 +21,7 @@ ) end - if query.persisted? + if query.persisted? && OpenProject::FeatureDecisions.project_list_sharing_active? header.with_action_icon_button( tag: :a, href: dialog_project_query_members_path(query), From 23990f04d41ccf4dc35dffb1014b553f06aefbde Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 2 Jul 2024 12:35:06 +0200 Subject: [PATCH 32/62] Only add one endpoint to toggle public flag on product query --- .../index_page_header_component.html.erb | 23 ------------------- .../projects/index_page_header_component.rb | 6 ----- .../projects/queries_controller.rb | 19 ++++++--------- config/initializers/permissions.rb | 2 +- config/routes.rb | 4 +--- 5 files changed, 9 insertions(+), 45 deletions(-) diff --git a/app/components/projects/index_page_header_component.html.erb b/app/components/projects/index_page_header_component.html.erb index df5641ccd3ec..8762ae35bd2a 100644 --- a/app/components/projects/index_page_header_component.html.erb +++ b/app/components/projects/index_page_header_component.html.erb @@ -101,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, diff --git a/app/components/projects/index_page_header_component.rb b/app/components/projects/index_page_header_component.rb index 6d4d5b121199..77b94c0b286d 100644 --- a/app/components/projects/index_page_header_component.rb +++ b/app/components/projects/index_page_header_component.rb @@ -96,12 +96,6 @@ def can_rename? end end - def can_publish? - OpenProject::FeatureDecisions.project_list_sharing_active? && - current_user.allowed_globally?(:manage_public_project_queries) && - query.persisted? - end - def show_state? state == :show end diff --git a/app/controllers/projects/queries_controller.rb b/app/controllers/projects/queries_controller.rb index abbc67b2c4fc..e1111712033b 100644 --- a/app/controllers/projects/queries_controller.rb +++ b/app/controllers/projects/queries_controller.rb @@ -30,9 +30,9 @@ class Projects::QueriesController < ApplicationController include Projects::QueryLoading # No need for a more specific authorization check. That is carried out in the contracts. - no_authorization_required! :show, :new, :create, :rename, :update, :publish, :unpublish, :destroy + no_authorization_required! :show, :new, :create, :rename, :update, :toggle_public, :destroy before_action :require_login - before_action :find_query, only: %i[show rename update destroy publish unpublish] + before_action :find_query, only: %i[show rename update destroy toggle_public] before_action :build_query_or_deny_access, only: %i[new create] current_menu_item [:new, :rename, :create, :update] do @@ -71,20 +71,15 @@ def update render_result(call, success_i18n_key: "lists.update.success", error_i18n_key: "lists.update.failure") end - def publish - call = Queries::Projects::ProjectQueries::PublishService - .new(user: current_user, model: @query) - .call(public: true) - - render_result(call, success_i18n_key: "lists.publish.success", error_i18n_key: "lists.publish.failure") - end + def toggle_public + to_be_public = !@query.public? + i18n_key = to_be_public ? "lists.publish" : "lists.unpublish" - def unpublish call = Queries::Projects::ProjectQueries::PublishService .new(user: current_user, model: @query) - .call(public: false) + .call(public: to_be_public) - render_result(call, success_i18n_key: "lists.unpublish.success", error_i18n_key: "lists.unpublish.failure") + render_result(call, success_i18n_key: "#{i18n_key}.success", error_i18n_key: "#{i18n_key}.failure") end def destroy diff --git a/config/initializers/permissions.rb b/config/initializers/permissions.rb index 51abafd3c0be..89482abc2c2e 100644 --- a/config/initializers/permissions.rb +++ b/config/initializers/permissions.rb @@ -180,7 +180,7 @@ map.permission :manage_public_project_queries, { - "projects/queries": %i[publish unpublish] + "projects/queries": %i[toggle_public] }, permissible_on: :global, require: :loggedin, diff --git a/config/routes.rb b/config/routes.rb index 4ee44cf9a21f..00cc6837a746 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -201,9 +201,7 @@ member do get :rename - - post :publish - post :unpublish + post :toggle_public end end From 1b5ecb99147d84e1af8add67c29951775a6cda28 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 2 Jul 2024 12:57:51 +0200 Subject: [PATCH 33/62] Public lists should be shown in public, not private if owned --- app/menus/projects/menu.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/menus/projects/menu.rb b/app/menus/projects/menu.rb index f7c28f20dd19..b19ba2af9623 100644 --- a/app/menus/projects/menu.rb +++ b/app/menus/projects/menu.rb @@ -98,13 +98,13 @@ def static_filters(ids) def public_filters persisted_filters - .select { |query| query.public? && query.user != current_user } + .select(&:public?) .map { |query| menu_item(query.name, query_id: query.id) } end def my_filters persisted_filters - .select { |query| query.user == current_user } + .select { |query| query.user == current_user && !query.public? } .map { |query| menu_item(query.name, query_id: query.id) } end From 58156a46d89ce1bd3688819268934363ac07eeeb Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 2 Jul 2024 13:08:20 +0200 Subject: [PATCH 34/62] Implement the publish button --- .../public_flag_component.html.erb | 16 +++++++++++++++- .../project_queries/public_flag_component.rb | 12 ++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/app/components/shares/project_queries/public_flag_component.html.erb b/app/components/shares/project_queries/public_flag_component.html.erb index 3dbc32862bcb..d218a2774b29 100644 --- a/app/components/shares/project_queries/public_flag_component.html.erb +++ b/app/components/shares/project_queries/public_flag_component.html.erb @@ -1 +1,15 @@ -<%= container.with_row { "Hallo, das ist ein Test" } %> +<%= +container.with_row(mt: 3) do + render(Primer::Forms::ToggleSwitchForm.new( + name: "publish_project_query", + # TODO: Proper translations + label: "Make the query public", + caption: "All users will have access", + src: toggle_public_flag_link, + csrf_token: form_authenticity_token, + status_label_position: :start, + checked:, + enabled: can_publish? + )) +end +%> diff --git a/app/components/shares/project_queries/public_flag_component.rb b/app/components/shares/project_queries/public_flag_component.rb index 4dad89356b6d..0c00669ce60e 100644 --- a/app/components/shares/project_queries/public_flag_component.rb +++ b/app/components/shares/project_queries/public_flag_component.rb @@ -44,6 +44,18 @@ def initialize(strategy:, modal_body_container:) private attr_reader :strategy, :container + + def toggle_public_flag_link + toggle_public_project_query_path(strategy.entity) + end + + def checked + strategy.entity.public? + end + + def can_publish? + User.current.allowed_globally?(:manage_public_project_queries) + end end end end From e4da1d0754cd076daf2bb761522568326117c556 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 2 Jul 2024 13:41:25 +0200 Subject: [PATCH 35/62] Add custom permission checks based on SharingStrategy --- app/controllers/shares_controller.rb | 17 +++++++++++++++-- app/models/sharing_strategies/base_strategy.rb | 5 +++++ .../project_query_strategy.rb | 4 ++++ .../sharing_strategies/work_package_strategy.rb | 4 ++++ config/initializers/permissions.rb | 7 ++----- 5 files changed, 30 insertions(+), 7 deletions(-) diff --git a/app/controllers/shares_controller.rb b/app/controllers/shares_controller.rb index e7706b0c3d95..059fd2cefd2c 100644 --- a/app/controllers/shares_controller.rb +++ b/app/controllers/shares_controller.rb @@ -37,8 +37,9 @@ class SharesController < ApplicationController before_action :load_share, only: %i[destroy update resend_invite] before_action :enterprise_check, only: %i[index] - # TODO: Permission checks need to be implemented correctly depending on entity - before_action :authorize + before_action :check_if_manageable, except: %i[index dialog] + before_action :check_if_viewable, only: %i[index dialog] + authorization_checked! :dialog, :index, :create, :update, :destroy, :resend_invite, :bulk_update, :bulk_destroy def dialog; end @@ -131,6 +132,18 @@ def bulk_destroy attr_reader :sharing_strategy + def check_if_viewable + return if sharing_strategy.viewable? || sharing_strategy.manageable? + + render_403 + end + + def check_if_manageable + return if sharing_strategy.manageable? + + render_403 + end + def enterprise_check return if EnterpriseToken.allows_to?(:work_package_sharing) diff --git a/app/models/sharing_strategies/base_strategy.rb b/app/models/sharing_strategies/base_strategy.rb index e92f1d362c84..6b1a82fb59f8 100644 --- a/app/models/sharing_strategies/base_strategy.rb +++ b/app/models/sharing_strategies/base_strategy.rb @@ -40,6 +40,11 @@ def available_roles raise NotImplementedError, "Override in a subclass and return an array of roles that should be displayed" end + def vieable? + raise NotImplementedError, + "Override in a subclass and return true if the current user can view who the entity is shared with" + end + def manageable? raise NotImplementedError, "Override in a subclass and return true if the current user can manage sharing" end diff --git a/app/models/sharing_strategies/project_query_strategy.rb b/app/models/sharing_strategies/project_query_strategy.rb index c9ad2c383e47..279bd32a0a8b 100644 --- a/app/models/sharing_strategies/project_query_strategy.rb +++ b/app/models/sharing_strategies/project_query_strategy.rb @@ -43,6 +43,10 @@ def manageable? @entity.editable? end + def viewable? + @entity.visible? + end + def create_contract_class Shares::ProjectQueries::CreateContract end diff --git a/app/models/sharing_strategies/work_package_strategy.rb b/app/models/sharing_strategies/work_package_strategy.rb index 1935b0d0c205..3d24ef89a816 100644 --- a/app/models/sharing_strategies/work_package_strategy.rb +++ b/app/models/sharing_strategies/work_package_strategy.rb @@ -49,6 +49,10 @@ def manageable? user.allowed_in_project?(:share_work_packages, @entity.project) end + def viewable? + user.allowed_in_project?(:view_shared_work_packages, @entity.project) + end + def create_contract_class Shares::WorkPackages::CreateContract end diff --git a/config/initializers/permissions.rb b/config/initializers/permissions.rb index 89482abc2c2e..896a1c495add 100644 --- a/config/initializers/permissions.rb +++ b/config/initializers/permissions.rb @@ -330,17 +330,14 @@ map.permission :share_work_packages, { - members: %i[destroy_by_principal], - shares: %i[dialog index create destroy update resend_invite bulk_update bulk_destroy] + members: %i[destroy_by_principal] }, permissible_on: :project, dependencies: %i[edit_work_packages view_shared_work_packages], require: :member map.permission :view_shared_work_packages, - { - shares: %i[index] - }, + {}, permissible_on: :project, require: :member, contract_actions: { work_package_shares: %i[index] } From 018ff2c6770e80432a6fe1b35e6500bfca5dd11e Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 2 Jul 2024 15:04:09 +0200 Subject: [PATCH 36/62] Fix toggle public specs --- app/controllers/members_controller.rb | 4 +- .../projects/queries_controller_spec.rb | 85 ++----------------- 2 files changed, 8 insertions(+), 81 deletions(-) diff --git a/app/controllers/members_controller.rb b/app/controllers/members_controller.rb index 0a0a3179c56f..7fbba42d4f5a 100644 --- a/app/controllers/members_controller.rb +++ b/app/controllers/members_controller.rb @@ -144,8 +144,8 @@ def members_table_options(roles) available_roles: roles, authorize_update: authorize_for("members", :update), authorize_delete: authorize_for("members", :destroy), - authorize_work_package_shares_view: authorize_for("shares", :update), - authorize_work_package_shares_delete: authorize_for("shares", :bulk_destroy), + authorize_work_package_shares_view: current_user.allowed_in_project?(:view_shared_work_packages, @project), + authorize_work_package_shares_delete: current_user.allowed_in_project?(:share_work_packages, @project), authorize_manage_user: current_user.allowed_globally?(:manage_user), is_filtered: Members::UserFilterComponent.filtered?(params), shared_role_name: diff --git a/spec/controllers/projects/queries_controller_spec.rb b/spec/controllers/projects/queries_controller_spec.rb index dc8a82ad9f1b..9793088a9fb9 100644 --- a/spec/controllers/projects/queries_controller_spec.rb +++ b/spec/controllers/projects/queries_controller_spec.rb @@ -263,11 +263,11 @@ end end - describe "#publish" do + describe "#toggle_public" do let(:service_class) { Queries::Projects::ProjectQueries::PublishService } it "requires login" do - put "publish", params: { id: 42 } + post "toggle_public", params: { id: 42 } expect(response).not_to be_successful end @@ -292,7 +292,7 @@ end it "calls publish service on query" do - put "publish", params: { id: 42 } + post "toggle_public", params: { id: 42 } expect(service_instance).to have_received(:call).with(query_params) end @@ -301,7 +301,7 @@ it "redirects to projects" do allow(I18n).to receive(:t).with("lists.publish.success").and_return("foo") - put "publish", params: { id: 42 } + post "toggle_public", params: { id: 42 } expect(flash[:notice]).to eq("foo") expect(response).to redirect_to(projects_path(query_id: query.id)) @@ -319,7 +319,7 @@ it "renders projects/index" do allow(I18n).to receive(:t).with("lists.publish.failure", errors: "something\nwent\nwrong").and_return("bar") - put "publish", params: { id: 42 } + post "toggle_public", params: { id: 42 } expect(flash[:error]).to eq("bar") expect(response).to render_template("projects/index") @@ -328,80 +328,7 @@ it "passes variables to template" do allow(controller).to receive(:render).and_call_original - put "update", params: { id: 42 } - - expect(controller).to have_received(:render).with(include(locals: { query:, state: :edit })) - end - end - end - end - - describe "#unpublish" do - let(:service_class) { Queries::Projects::ProjectQueries::PublishService } - - it "requires login" do - put "unpublish", params: { id: 42 } - - expect(response).not_to be_successful - end - - context "when logged in" do - let(:query) { build_stubbed(:project_query, user:) } - let(:query_id) { "42" } - let(:query_params) { { public: false } } - let(:service_instance) { instance_double(service_class) } - let(:service_result) { instance_double(ServiceResult, success?: success?, result: query) } - let(:success?) { true } - - before do - allow(controller).to receive(:permitted_query_params).and_return(query_params) - scope = instance_double(ActiveRecord::Relation) - allow(ProjectQuery).to receive(:visible).and_return(scope) - allow(scope).to receive(:find).with(query_id).and_return(query) - allow(service_class).to receive(:new).with(model: query, user:).and_return(service_instance) - allow(service_instance).to receive(:call).with(query_params).and_return(service_result) - - login_as user - end - - it "calls publish service on query" do - put "unpublish", params: { id: 42 } - - expect(service_instance).to have_received(:call).with(query_params) - end - - context "when service call succeeds" do - it "redirects to projects" do - allow(I18n).to receive(:t).with("lists.unpublish.success").and_return("foo") - - put "unpublish", params: { id: 42 } - - expect(flash[:notice]).to eq("foo") - expect(response).to redirect_to(projects_path(query_id: query.id)) - end - end - - context "when service call fails" do - let(:success?) { false } - let(:errors) { instance_double(ActiveModel::Errors, full_messages: ["something", "went", "wrong"]) } - - before do - allow(service_result).to receive(:errors).and_return(errors) - end - - it "renders projects/index" do - allow(I18n).to receive(:t).with("lists.unpublish.failure", errors: "something\nwent\nwrong").and_return("bar") - - put "unpublish", params: { id: 42 } - - expect(flash[:error]).to eq("bar") - expect(response).to render_template("projects/index") - end - - it "passes variables to template" do - allow(controller).to receive(:render).and_call_original - - put "unpublish", params: { id: 42 } + post "toggle_public", params: { id: 42 } expect(controller).to have_received(:render).with(include(locals: { query:, state: :edit })) end From 55491c4643a165dfd03700627fe1c2ce6fa782c2 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 2 Jul 2024 15:42:21 +0200 Subject: [PATCH 37/62] Use the `#editable?` method in Projects::IndexPageHeaderComponent --- .../projects/index_page_header_component.rb | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/app/components/projects/index_page_header_component.rb b/app/components/projects/index_page_header_component.rb index 77b94c0b286d..84ebd067913e 100644 --- a/app/components/projects/index_page_header_component.rb +++ b/app/components/projects/index_page_header_component.rb @@ -77,11 +77,7 @@ 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? @@ -89,11 +85,7 @@ def can_rename? 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 + query.editable? end def show_state? From f7145b7fb66b5438cde2918815d73f12f69eb9f5 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 2 Jul 2024 15:54:00 +0200 Subject: [PATCH 38/62] Fix permission check for non-project scoped permissions --- app/services/authorization/user_permissible_service.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/services/authorization/user_permissible_service.rb b/app/services/authorization/user_permissible_service.rb index a3c8e2629b14..ec7a2bc53015 100644 --- a/app/services/authorization/user_permissible_service.rb +++ b/app/services/authorization/user_permissible_service.rb @@ -112,7 +112,9 @@ def allowed_in_single_standalone_entity?(permissions, entity) return false if entity.nil? return true if admin_and_all_granted_to_admin?(permissions) - cached_permissions(entity).intersect?(permissions) + permission_names = permissions.map { |perm| perm.name.to_sym } + + cached_permissions(entity).intersect?(permission_names) end def allowed_in_any_project_scoped_entity?(permissions, entity_class, in_project:) From 58fd4fe0faba3d4d5e9820a514527164f805082b Mon Sep 17 00:00:00 2001 From: Aaron Contreras Date: Tue, 2 Jul 2024 11:05:10 -0500 Subject: [PATCH 39/62] Hide share button in header component if user can't view shares --- app/components/projects/index_page_header_component.html.erb | 2 +- app/components/projects/index_page_header_component.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/components/projects/index_page_header_component.html.erb b/app/components/projects/index_page_header_component.html.erb index 8762ae35bd2a..357fa53cdf2b 100644 --- a/app/components/projects/index_page_header_component.html.erb +++ b/app/components/projects/index_page_header_component.html.erb @@ -21,7 +21,7 @@ ) end - if query.persisted? && OpenProject::FeatureDecisions.project_list_sharing_active? + if query.persisted? && OpenProject::FeatureDecisions.project_list_sharing_active? && can_view_shares? header.with_action_icon_button( tag: :a, href: dialog_project_query_members_path(query), diff --git a/app/components/projects/index_page_header_component.rb b/app/components/projects/index_page_header_component.rb index 84ebd067913e..660028396667 100644 --- a/app/components/projects/index_page_header_component.rb +++ b/app/components/projects/index_page_header_component.rb @@ -88,6 +88,10 @@ def can_rename? query.editable? end + def can_view_shares? + query.visible? || query.editable? + end + def show_state? state == :show end From 2b24c4dfec3bababb51b5b2db5df86f154bee476 Mon Sep 17 00:00:00 2001 From: Aaron Contreras Date: Tue, 2 Jul 2024 11:28:01 -0500 Subject: [PATCH 40/62] Render a custom empty state component when the project query is public --- .../shares/modal_body_component.html.erb | 2 +- .../empty_state_component.html.erb | 15 +++++ .../project_queries/empty_state_component.rb | 60 +++++++++++++++++++ .../project_query_strategy.rb | 4 ++ 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 app/components/shares/project_queries/empty_state_component.html.erb create mode 100644 app/components/shares/project_queries/empty_state_component.rb diff --git a/app/components/shares/modal_body_component.html.erb b/app/components/shares/modal_body_component.html.erb index 2ee90bfccba5..2995901dc0d7 100644 --- a/app/components/shares/modal_body_component.html.erb +++ b/app/components/shares/modal_body_component.html.erb @@ -101,7 +101,7 @@ render(strategy.empty_state_component.new(strategy: strategy)) else render(Shares::EmptyStateComponent.new(strategy: strategy)) - end + end end else @shares.each do |share| diff --git a/app/components/shares/project_queries/empty_state_component.html.erb b/app/components/shares/project_queries/empty_state_component.html.erb new file mode 100644 index 000000000000..64e117e461b4 --- /dev/null +++ b/app/components/shares/project_queries/empty_state_component.html.erb @@ -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 %> diff --git a/app/components/shares/project_queries/empty_state_component.rb b/app/components/shares/project_queries/empty_state_component.rb new file mode 100644 index 000000000000..228cd84d742a --- /dev/null +++ b/app/components/shares/project_queries/empty_state_component.rb @@ -0,0 +1,60 @@ +#-- 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 + module ProjectQueries + class EmptyStateComponent < ApplicationComponent # rubocop:disable OpenProject/AddPreviewForViewComponent + 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 + @blankslate_config ||= {}.tap do |config| + if params[:filters].blank? + config[:icon] = :people + config[:heading_text] = "ABC" + 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 +end diff --git a/app/models/sharing_strategies/project_query_strategy.rb b/app/models/sharing_strategies/project_query_strategy.rb index 279bd32a0a8b..163397655b05 100644 --- a/app/models/sharing_strategies/project_query_strategy.rb +++ b/app/models/sharing_strategies/project_query_strategy.rb @@ -62,5 +62,9 @@ def delete_contract_class def additional_body_component Shares::ProjectQueries::PublicFlagComponent end + + def empty_state_component + Shares::ProjectQueries::EmptyStateComponent if @entity.public? + end end end From 9cf1e0880135c388d6356dab04b81ac21f0c4e27 Mon Sep 17 00:00:00 2001 From: Aaron Contreras Date: Tue, 2 Jul 2024 11:37:34 -0500 Subject: [PATCH 41/62] Add translations to custom empty state component --- .../shares/project_queries/empty_state_component.rb | 6 +++--- config/locales/en.yml | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/components/shares/project_queries/empty_state_component.rb b/app/components/shares/project_queries/empty_state_component.rb index 228cd84d742a..36e986bcb260 100644 --- a/app/components/shares/project_queries/empty_state_component.rb +++ b/app/components/shares/project_queries/empty_state_component.rb @@ -42,12 +42,12 @@ def initialize(strategy:) attr_reader :strategy, :entity - def blankslate_config # rubocop:disable Metrics/AbcSize + def blankslate_config @blankslate_config ||= {}.tap do |config| if params[:filters].blank? config[:icon] = :people - config[:heading_text] = "ABC" - config[:description_text] = I18n.t("sharing.text_empty_state_description", entity: @entity.class.model_name.human) + config[:heading_text] = I18n.t("sharing.project_queries.text_empty_state_header") + config[:description_text] = I18n.t("sharing.project_queries.text_empty_state_description") else config[:icon] = :search config[:heading_text] = I18n.t("sharing.text_empty_search_header") diff --git a/config/locales/en.yml b/config/locales/en.yml index 33335389eea0..5eeb15bf9965 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -3671,7 +3671,9 @@ en: additional_privileges_project: "Might have additional privileges (as project member)" additional_privileges_group: "Might have additional privileges (as group member)" additional_privileges_project_or_group: "Might have additional privileges (as project or group member)" - + project_queries: + text_empty_state_header: "Shared with everyone" + text_empty_state_description: "Everyone can view this project list. You can also add individual users with extra permissions." working_days: info: > Days that are not selected are skipped when scheduling work packages (and not included in the day count). From 09fea527d2bdba9774ae517bca10818e3b3153bc Mon Sep 17 00:00:00 2001 From: Aaron Contreras Date: Tue, 2 Jul 2024 11:56:09 -0500 Subject: [PATCH 42/62] Revert "Hide share button in header component if user can't view shares" This reverts commit 58fd4fe0faba3d4d5e9820a514527164f805082b. --- app/components/projects/index_page_header_component.html.erb | 2 +- app/components/projects/index_page_header_component.rb | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/app/components/projects/index_page_header_component.html.erb b/app/components/projects/index_page_header_component.html.erb index 357fa53cdf2b..8762ae35bd2a 100644 --- a/app/components/projects/index_page_header_component.html.erb +++ b/app/components/projects/index_page_header_component.html.erb @@ -21,7 +21,7 @@ ) end - if query.persisted? && OpenProject::FeatureDecisions.project_list_sharing_active? && can_view_shares? + if query.persisted? && OpenProject::FeatureDecisions.project_list_sharing_active? header.with_action_icon_button( tag: :a, href: dialog_project_query_members_path(query), diff --git a/app/components/projects/index_page_header_component.rb b/app/components/projects/index_page_header_component.rb index 660028396667..84ebd067913e 100644 --- a/app/components/projects/index_page_header_component.rb +++ b/app/components/projects/index_page_header_component.rb @@ -88,10 +88,6 @@ def can_rename? query.editable? end - def can_view_shares? - query.visible? || query.editable? - end - def show_state? state == :show end From f167e736c1fbaa0f02c45f83381335b1b1bc17da Mon Sep 17 00:00:00 2001 From: Aaron Contreras Date: Tue, 2 Jul 2024 12:02:08 -0500 Subject: [PATCH 43/62] i18n role names and descriptions for project query sharing --- .../project_query_strategy.rb | 19 +++++++++++-------- config/locales/en.yml | 5 +++++ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/app/models/sharing_strategies/project_query_strategy.rb b/app/models/sharing_strategies/project_query_strategy.rb index 163397655b05..981b3fa73593 100644 --- a/app/models/sharing_strategies/project_query_strategy.rb +++ b/app/models/sharing_strategies/project_query_strategy.rb @@ -29,14 +29,17 @@ module SharingStrategies class ProjectQueryStrategy < BaseStrategy def available_roles - ProjectQueryRole.all.map.with_index do |role, index| - { - label: role.name, - value: role.id, - description: "#{role.name} description", # TODO: Figure out from where we can get the description - default: index.zero? - } - end + role_mapping = ProjectQueryRole.pluck(:builtin, :id).to_h + + [ + { label: I18n.t("sharing.project_queries.permissions.edit"), + value: role_mapping[Role::BUILTIN_PROJECT_QUERY_EDIT], + description: I18n.t("sharing.project_queries.permissions.edit_description") }, + { label: I18n.t("sharing.project_queries.permissions.view"), + value: role_mapping[Role::BUILTIN_PROJECT_QUERY_VIEW], + description: I18n.t("sharing.project_queries.permissions.view_description"), + default: true } + ] end def manageable? diff --git a/config/locales/en.yml b/config/locales/en.yml index 5eeb15bf9965..ce8dfa601eaa 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -3674,6 +3674,11 @@ en: project_queries: text_empty_state_header: "Shared with everyone" text_empty_state_description: "Everyone can view this project list. You can also add individual users with extra permissions." + permissions: + view: "View" + view_description: "TODO: Add me" + edit: "Edit" + edit_description: "TODO: Add me as well" working_days: info: > Days that are not selected are skipped when scheduling work packages (and not included in the day count). From 2cb3e1012cbb44cce969dfb758f53f9c16a8ffa2 Mon Sep 17 00:00:00 2001 From: Aaron Contreras Date: Tue, 2 Jul 2024 12:06:04 -0500 Subject: [PATCH 44/62] Correct copyright --- app/contracts/shares/project_queries/base_extension.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/contracts/shares/project_queries/base_extension.rb b/app/contracts/shares/project_queries/base_extension.rb index 688fce333a90..7392139fe6f3 100644 --- a/app/contracts/shares/project_queries/base_extension.rb +++ b/app/contracts/shares/project_queries/base_extension.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. From 4020db70a1a2f386c7f4909af36e0fa7d120bdb8 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Wed, 3 Jul 2024 07:40:59 +0200 Subject: [PATCH 45/62] Fix copyright notices that still mention 2023 --- app/components/filter/filter_button_component.rb | 2 +- app/components/filter/filter_component.rb | 2 +- app/components/members/index_page_header_component.rb | 2 +- app/components/projects/disk_usage_information_component.rb | 2 +- app/components/projects/index_page_header_component.rb | 2 +- app/components/projects/projects_filters_component.rb | 2 +- .../index_page_header_component.rb | 2 +- app/contracts/shares/base_contract.rb | 2 +- app/contracts/shares/create_contract.rb | 2 +- app/contracts/shares/delete_contract.rb | 2 +- app/contracts/shares/project_queries/create_contract.rb | 2 +- app/contracts/shares/project_queries/delete_contract.rb | 2 +- app/contracts/shares/project_queries/update_contract.rb | 2 +- app/contracts/shares/update_contract.rb | 2 +- app/contracts/shares/work_packages/base_extension.rb | 2 +- app/contracts/shares/work_packages/create_contract.rb | 2 +- app/contracts/shares/work_packages/delete_contract.rb | 2 +- app/contracts/shares/work_packages/update_contract.rb | 2 +- app/controllers/projects/menus_controller.rb | 2 +- app/controllers/shares_controller.rb | 2 +- app/helpers/wiki_pages/at_version.rb | 2 +- app/menus/submenu.rb | 2 +- app/models/custom_fields_project.rb | 2 +- app/models/journable/with_historic_attributes/loader.rb | 2 +- app/models/members/scopes/of_any_entity.rb | 2 +- app/models/members/scopes/of_any_work_package.rb | 2 +- app/models/members/scopes/of_anything_in_project.rb | 2 +- app/models/members/scopes/of_entity.rb | 2 +- app/models/members/scopes/of_work_package.rb | 2 +- app/models/members/scopes/without_inherited_roles.rb | 2 +- app/models/principals/scopes/having_entity_membership.rb | 2 +- app/models/project_queries/scopes/allowed_to.rb | 2 +- app/models/project_role.rb | 2 +- app/models/projects/scopes/allowed_to.rb | 2 +- app/models/projects/scopes/available_custom_fields.rb | 2 +- app/models/projects/scopes/visible.rb | 2 +- .../strategies/work_packages/shared_with_user/list_optional.rb | 2 +- .../queries/operators/work_packages/shared_with_user/any.rb | 2 +- app/models/queries/serialization/orders.rb | 2 +- app/models/queries/unpersisted_query.rb | 2 +- app/models/work_packages/scopes/allowed_to.rb | 2 +- app/services/members/concerns/role_assignment.rb | 2 +- app/services/shares/create_or_update_service.rb | 2 +- app/services/shares/create_service.rb | 2 +- app/services/shares/delete_role_service.rb | 2 +- app/services/shares/delete_service.rb | 2 +- app/services/shares/set_attributes_service.rb | 2 +- app/services/shares/update_service.rb | 2 +- .../v3/notifications/notification_sql_collection_representer.rb | 2 +- .../v3/work_packages/work_package_at_timestamp_representer.rb | 2 +- lib/api/v3/work_packages/work_package_deleted_representer.rb | 2 +- .../lib/acts/journalized/journable_differ.rb | 2 +- modules/bim/app/controllers/bim/menus_controller.rb | 2 +- modules/bim/app/menus/bim/menu.rb | 2 +- modules/boards/app/controllers/boards/menus_controller.rb | 2 +- modules/boards/app/menus/boards/menu.rb | 2 +- modules/calendar/app/controllers/calendar/menus_controller.rb | 2 +- modules/calendar/app/menus/calendar/menu.rb | 2 +- modules/gantt/app/controllers/gantt/menus_controller.rb | 2 +- modules/gantt/app/menus/gantt/menu.rb | 2 +- .../app/components/meetings/meeting_filter_button_component.rb | 2 +- .../app/components/meetings/meeting_filters_component.rb | 2 +- modules/meeting/app/controllers/meetings/menus_controller.rb | 2 +- modules/meeting/app/menus/meetings/menu.rb | 2 +- .../reporting/app/controllers/cost_reports/menus_controller.rb | 2 +- modules/reporting/app/menus/cost_reports/menu.rb | 2 +- .../app/controllers/team_planner/menus_controller.rb | 2 +- modules/team_planner/app/menus/team_planner/menu.rb | 2 +- spec/components/shares/user_details_component_spec.rb | 2 +- spec/contracts/relations/shared_contract_examples.rb | 2 +- spec/contracts/relations/update_contract_spec.rb | 2 +- spec/contracts/shares/work_packages/create_contract_spec.rb | 2 +- spec/contracts/shares/work_packages/delete_contract_spec.rb | 2 +- spec/contracts/shares/work_packages/shared_contract_examples.rb | 2 +- spec/contracts/shares/work_packages/update_contract_spec.rb | 2 +- spec/features/work_packages/share/filter_spec.rb | 2 +- spec/features/work_packages/share/multi_invite_spec.rb | 2 +- .../work_packages/share/share_account_activation_spec.rb | 2 +- spec/features/work_packages/share/share_spec.rb | 2 +- spec/features/workflows/missing_for_sharing_wp_spec.rb | 2 +- spec/helpers/wiki_pages/at_version_spec.rb | 2 +- spec/lib/acts_as_journalized/journable_differ_spec.rb | 2 +- .../work_package_at_timestamp_representer_rendering_spec.rb | 2 +- spec/models/members/scopes/global_spec.rb | 2 +- spec/models/members/scopes/of_any_entity_spec.rb | 2 +- spec/models/members/scopes/of_any_project_spec.rb | 2 +- spec/models/members/scopes/of_any_work_package_spec.rb | 2 +- spec/models/members/scopes/of_anything_in_project_spec.rb | 2 +- spec/models/members/scopes/of_project_spec.rb | 2 +- spec/models/members/scopes/of_work_package_spec.rb | 2 +- spec/models/members/scopes/visible_spec.rb | 2 +- .../members/scopes/with_shared_work_packages_info_spec.rb | 2 +- spec/models/members/scopes/without_inherited_roles_spec.rb | 2 +- spec/models/principals/scopes/having_entity_membership_spec.rb | 2 +- spec/models/project_role_spec.rb | 2 +- spec/models/projects/scopes/available_custom_fields_spec.rb | 2 +- spec/models/projects/scopes/visible_spec.rb | 2 +- spec/services/shares/delete_role_service_spec.rb | 2 +- spec/services/shares/delete_service_spec.rb | 2 +- spec/services/shares/set_attributes_service_spec.rb | 2 +- .../components/projects/project_custom_fields/edit_dialog.rb | 2 +- spec/support/components/work_packages/share_modal.rb | 2 +- 102 files changed, 102 insertions(+), 102 deletions(-) diff --git a/app/components/filter/filter_button_component.rb b/app/components/filter/filter_button_component.rb index e884b254dcb9..4df3f77cbba8 100644 --- a/app/components/filter/filter_button_component.rb +++ b/app/components/filter/filter_button_component.rb @@ -2,7 +2,7 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/components/filter/filter_component.rb b/app/components/filter/filter_component.rb index ea313e4d1975..01f58a5bdf53 100644 --- a/app/components/filter/filter_component.rb +++ b/app/components/filter/filter_component.rb @@ -2,7 +2,7 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/components/members/index_page_header_component.rb b/app/components/members/index_page_header_component.rb index 70c416a30191..167c97d4ad35 100644 --- a/app/components/members/index_page_header_component.rb +++ b/app/components/members/index_page_header_component.rb @@ -2,7 +2,7 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/components/projects/disk_usage_information_component.rb b/app/components/projects/disk_usage_information_component.rb index 6a6984e64fe9..a3cbd00e5efb 100644 --- a/app/components/projects/disk_usage_information_component.rb +++ b/app/components/projects/disk_usage_information_component.rb @@ -2,7 +2,7 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/components/projects/index_page_header_component.rb b/app/components/projects/index_page_header_component.rb index 84ebd067913e..119efbefd99f 100644 --- a/app/components/projects/index_page_header_component.rb +++ b/app/components/projects/index_page_header_component.rb @@ -2,7 +2,7 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/components/projects/projects_filters_component.rb b/app/components/projects/projects_filters_component.rb index e467c186dd1f..6f902b19f546 100644 --- a/app/components/projects/projects_filters_component.rb +++ b/app/components/projects/projects_filters_component.rb @@ -2,7 +2,7 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/components/projects/settings/project_custom_field_sections/index_page_header_component.rb b/app/components/projects/settings/project_custom_field_sections/index_page_header_component.rb index 645e9f17f431..013625ee165b 100644 --- a/app/components/projects/settings/project_custom_field_sections/index_page_header_component.rb +++ b/app/components/projects/settings/project_custom_field_sections/index_page_header_component.rb @@ -2,7 +2,7 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/contracts/shares/base_contract.rb b/app/contracts/shares/base_contract.rb index 020973c6dd03..856094e1f044 100644 --- a/app/contracts/shares/base_contract.rb +++ b/app/contracts/shares/base_contract.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/contracts/shares/create_contract.rb b/app/contracts/shares/create_contract.rb index 190af5d6ea5e..9d8941d17628 100644 --- a/app/contracts/shares/create_contract.rb +++ b/app/contracts/shares/create_contract.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/contracts/shares/delete_contract.rb b/app/contracts/shares/delete_contract.rb index 165815bb1fb7..69cdc52d8b1d 100644 --- a/app/contracts/shares/delete_contract.rb +++ b/app/contracts/shares/delete_contract.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/contracts/shares/project_queries/create_contract.rb b/app/contracts/shares/project_queries/create_contract.rb index 58eeb1fdf130..d9ce1351d68d 100644 --- a/app/contracts/shares/project_queries/create_contract.rb +++ b/app/contracts/shares/project_queries/create_contract.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/contracts/shares/project_queries/delete_contract.rb b/app/contracts/shares/project_queries/delete_contract.rb index b5486e3425da..925dc2d18e76 100644 --- a/app/contracts/shares/project_queries/delete_contract.rb +++ b/app/contracts/shares/project_queries/delete_contract.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/contracts/shares/project_queries/update_contract.rb b/app/contracts/shares/project_queries/update_contract.rb index 5148b92951e0..eac81bc4e190 100644 --- a/app/contracts/shares/project_queries/update_contract.rb +++ b/app/contracts/shares/project_queries/update_contract.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/contracts/shares/update_contract.rb b/app/contracts/shares/update_contract.rb index 355a739d558c..565c519bcb8e 100644 --- a/app/contracts/shares/update_contract.rb +++ b/app/contracts/shares/update_contract.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/contracts/shares/work_packages/base_extension.rb b/app/contracts/shares/work_packages/base_extension.rb index 6fa99c6a5300..7f7fb016b4fc 100644 --- a/app/contracts/shares/work_packages/base_extension.rb +++ b/app/contracts/shares/work_packages/base_extension.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/contracts/shares/work_packages/create_contract.rb b/app/contracts/shares/work_packages/create_contract.rb index eced07e71743..d0cc27f013d5 100644 --- a/app/contracts/shares/work_packages/create_contract.rb +++ b/app/contracts/shares/work_packages/create_contract.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/contracts/shares/work_packages/delete_contract.rb b/app/contracts/shares/work_packages/delete_contract.rb index a2ac75399add..01bd4ebd1520 100644 --- a/app/contracts/shares/work_packages/delete_contract.rb +++ b/app/contracts/shares/work_packages/delete_contract.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/contracts/shares/work_packages/update_contract.rb b/app/contracts/shares/work_packages/update_contract.rb index f38d818cda01..7b2dc5d9e907 100644 --- a/app/contracts/shares/work_packages/update_contract.rb +++ b/app/contracts/shares/work_packages/update_contract.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/controllers/projects/menus_controller.rb b/app/controllers/projects/menus_controller.rb index e339bbc5a180..2b6e5324349b 100644 --- a/app/controllers/projects/menus_controller.rb +++ b/app/controllers/projects/menus_controller.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/controllers/shares_controller.rb b/app/controllers/shares_controller.rb index 059fd2cefd2c..5eebc6cef77e 100644 --- a/app/controllers/shares_controller.rb +++ b/app/controllers/shares_controller.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/helpers/wiki_pages/at_version.rb b/app/helpers/wiki_pages/at_version.rb index e7701291ba98..00cfd4ce8e5a 100644 --- a/app/helpers/wiki_pages/at_version.rb +++ b/app/helpers/wiki_pages/at_version.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/menus/submenu.rb b/app/menus/submenu.rb index 0e495d4672d1..0cdbcbbb8611 100644 --- a/app/menus/submenu.rb +++ b/app/menus/submenu.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/models/custom_fields_project.rb b/app/models/custom_fields_project.rb index 4aca68d63f3a..7be8bef4502a 100644 --- a/app/models/custom_fields_project.rb +++ b/app/models/custom_fields_project.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/models/journable/with_historic_attributes/loader.rb b/app/models/journable/with_historic_attributes/loader.rb index c0b8f146982c..1749bbcc4d9a 100644 --- a/app/models/journable/with_historic_attributes/loader.rb +++ b/app/models/journable/with_historic_attributes/loader.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/models/members/scopes/of_any_entity.rb b/app/models/members/scopes/of_any_entity.rb index 2d049e10dca7..11c4e5823933 100644 --- a/app/models/members/scopes/of_any_entity.rb +++ b/app/models/members/scopes/of_any_entity.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/models/members/scopes/of_any_work_package.rb b/app/models/members/scopes/of_any_work_package.rb index ac4bd1950962..b7beb0be5ed9 100644 --- a/app/models/members/scopes/of_any_work_package.rb +++ b/app/models/members/scopes/of_any_work_package.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/models/members/scopes/of_anything_in_project.rb b/app/models/members/scopes/of_anything_in_project.rb index 9759046d892d..2e7b5892d039 100644 --- a/app/models/members/scopes/of_anything_in_project.rb +++ b/app/models/members/scopes/of_anything_in_project.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/models/members/scopes/of_entity.rb b/app/models/members/scopes/of_entity.rb index b8413a4d7bd3..4ebf97108f6b 100644 --- a/app/models/members/scopes/of_entity.rb +++ b/app/models/members/scopes/of_entity.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/models/members/scopes/of_work_package.rb b/app/models/members/scopes/of_work_package.rb index 80c6e5103c0b..ba4a9b896e7e 100644 --- a/app/models/members/scopes/of_work_package.rb +++ b/app/models/members/scopes/of_work_package.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/models/members/scopes/without_inherited_roles.rb b/app/models/members/scopes/without_inherited_roles.rb index d848d1f275db..4b99a90216ca 100644 --- a/app/models/members/scopes/without_inherited_roles.rb +++ b/app/models/members/scopes/without_inherited_roles.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/models/principals/scopes/having_entity_membership.rb b/app/models/principals/scopes/having_entity_membership.rb index 7e520654d87b..449403181f71 100644 --- a/app/models/principals/scopes/having_entity_membership.rb +++ b/app/models/principals/scopes/having_entity_membership.rb @@ -2,7 +2,7 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/models/project_queries/scopes/allowed_to.rb b/app/models/project_queries/scopes/allowed_to.rb index d1686cc37008..91629e53bc4d 100644 --- a/app/models/project_queries/scopes/allowed_to.rb +++ b/app/models/project_queries/scopes/allowed_to.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/models/project_role.rb b/app/models/project_role.rb index 45daaf597db9..ce7cf3eb9853 100644 --- a/app/models/project_role.rb +++ b/app/models/project_role.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/models/projects/scopes/allowed_to.rb b/app/models/projects/scopes/allowed_to.rb index a832621b7c90..e1596abe33eb 100644 --- a/app/models/projects/scopes/allowed_to.rb +++ b/app/models/projects/scopes/allowed_to.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/models/projects/scopes/available_custom_fields.rb b/app/models/projects/scopes/available_custom_fields.rb index 5ce046be42b1..7a4834884fc0 100644 --- a/app/models/projects/scopes/available_custom_fields.rb +++ b/app/models/projects/scopes/available_custom_fields.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/models/projects/scopes/visible.rb b/app/models/projects/scopes/visible.rb index ddffd7bdbb48..fa2810fdd76d 100644 --- a/app/models/projects/scopes/visible.rb +++ b/app/models/projects/scopes/visible.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/models/queries/filters/strategies/work_packages/shared_with_user/list_optional.rb b/app/models/queries/filters/strategies/work_packages/shared_with_user/list_optional.rb index 3252fc0f7d0e..1bae91005a19 100644 --- a/app/models/queries/filters/strategies/work_packages/shared_with_user/list_optional.rb +++ b/app/models/queries/filters/strategies/work_packages/shared_with_user/list_optional.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/models/queries/operators/work_packages/shared_with_user/any.rb b/app/models/queries/operators/work_packages/shared_with_user/any.rb index c797a3cfb67d..0d0e06c76cf9 100644 --- a/app/models/queries/operators/work_packages/shared_with_user/any.rb +++ b/app/models/queries/operators/work_packages/shared_with_user/any.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/models/queries/serialization/orders.rb b/app/models/queries/serialization/orders.rb index 17f11f75a60e..80be493caaf7 100644 --- a/app/models/queries/serialization/orders.rb +++ b/app/models/queries/serialization/orders.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/models/queries/unpersisted_query.rb b/app/models/queries/unpersisted_query.rb index 5b7ddf6df480..570e0d20544e 100644 --- a/app/models/queries/unpersisted_query.rb +++ b/app/models/queries/unpersisted_query.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/models/work_packages/scopes/allowed_to.rb b/app/models/work_packages/scopes/allowed_to.rb index ea6a8b7c4261..45a26ca41af5 100644 --- a/app/models/work_packages/scopes/allowed_to.rb +++ b/app/models/work_packages/scopes/allowed_to.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/services/members/concerns/role_assignment.rb b/app/services/members/concerns/role_assignment.rb index 9de2a625b0a9..8e3befb32e33 100644 --- a/app/services/members/concerns/role_assignment.rb +++ b/app/services/members/concerns/role_assignment.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/services/shares/create_or_update_service.rb b/app/services/shares/create_or_update_service.rb index becdd8681719..ad9b60f0eba1 100644 --- a/app/services/shares/create_or_update_service.rb +++ b/app/services/shares/create_or_update_service.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/services/shares/create_service.rb b/app/services/shares/create_service.rb index 4f6b11f5e889..6b045190f334 100644 --- a/app/services/shares/create_service.rb +++ b/app/services/shares/create_service.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/services/shares/delete_role_service.rb b/app/services/shares/delete_role_service.rb index edde8919863c..c0820994eaf9 100644 --- a/app/services/shares/delete_role_service.rb +++ b/app/services/shares/delete_role_service.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/services/shares/delete_service.rb b/app/services/shares/delete_service.rb index b935e618a04d..88baee6fd386 100644 --- a/app/services/shares/delete_service.rb +++ b/app/services/shares/delete_service.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/services/shares/set_attributes_service.rb b/app/services/shares/set_attributes_service.rb index 1ef7f078e45f..c241961bddf5 100644 --- a/app/services/shares/set_attributes_service.rb +++ b/app/services/shares/set_attributes_service.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/app/services/shares/update_service.rb b/app/services/shares/update_service.rb index a065256d24d5..d38e65a2ea67 100644 --- a/app/services/shares/update_service.rb +++ b/app/services/shares/update_service.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/lib/api/v3/notifications/notification_sql_collection_representer.rb b/lib/api/v3/notifications/notification_sql_collection_representer.rb index 449dbe609a00..f64199928453 100644 --- a/lib/api/v3/notifications/notification_sql_collection_representer.rb +++ b/lib/api/v3/notifications/notification_sql_collection_representer.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/lib/api/v3/work_packages/work_package_at_timestamp_representer.rb b/lib/api/v3/work_packages/work_package_at_timestamp_representer.rb index 0f1db7c44668..a5944853977e 100644 --- a/lib/api/v3/work_packages/work_package_at_timestamp_representer.rb +++ b/lib/api/v3/work_packages/work_package_at_timestamp_representer.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/lib/api/v3/work_packages/work_package_deleted_representer.rb b/lib/api/v3/work_packages/work_package_deleted_representer.rb index a7b452be7c9d..adb766956e5b 100644 --- a/lib/api/v3/work_packages/work_package_deleted_representer.rb +++ b/lib/api/v3/work_packages/work_package_deleted_representer.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/lib_static/plugins/acts_as_journalized/lib/acts/journalized/journable_differ.rb b/lib_static/plugins/acts_as_journalized/lib/acts/journalized/journable_differ.rb index 35f9c13a5f3e..8c7c158910b1 100644 --- a/lib_static/plugins/acts_as_journalized/lib/acts/journalized/journable_differ.rb +++ b/lib_static/plugins/acts_as_journalized/lib/acts/journalized/journable_differ.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/modules/bim/app/controllers/bim/menus_controller.rb b/modules/bim/app/controllers/bim/menus_controller.rb index 5ac4a2eeea95..daf4c318ee5b 100644 --- a/modules/bim/app/controllers/bim/menus_controller.rb +++ b/modules/bim/app/controllers/bim/menus_controller.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/modules/bim/app/menus/bim/menu.rb b/modules/bim/app/menus/bim/menu.rb index 0041f2901ce8..29ae5e8c6b24 100644 --- a/modules/bim/app/menus/bim/menu.rb +++ b/modules/bim/app/menus/bim/menu.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/modules/boards/app/controllers/boards/menus_controller.rb b/modules/boards/app/controllers/boards/menus_controller.rb index 341eb1ed2518..33334e66af7c 100644 --- a/modules/boards/app/controllers/boards/menus_controller.rb +++ b/modules/boards/app/controllers/boards/menus_controller.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/modules/boards/app/menus/boards/menu.rb b/modules/boards/app/menus/boards/menu.rb index 75eba55b77b6..b907c5344621 100644 --- a/modules/boards/app/menus/boards/menu.rb +++ b/modules/boards/app/menus/boards/menu.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/modules/calendar/app/controllers/calendar/menus_controller.rb b/modules/calendar/app/controllers/calendar/menus_controller.rb index 41da3240b9b9..e2a6a1939392 100644 --- a/modules/calendar/app/controllers/calendar/menus_controller.rb +++ b/modules/calendar/app/controllers/calendar/menus_controller.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/modules/calendar/app/menus/calendar/menu.rb b/modules/calendar/app/menus/calendar/menu.rb index b0c7d257d3e9..68864bb0a85e 100644 --- a/modules/calendar/app/menus/calendar/menu.rb +++ b/modules/calendar/app/menus/calendar/menu.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/modules/gantt/app/controllers/gantt/menus_controller.rb b/modules/gantt/app/controllers/gantt/menus_controller.rb index e0e917a6327d..6d9656fde5f3 100644 --- a/modules/gantt/app/controllers/gantt/menus_controller.rb +++ b/modules/gantt/app/controllers/gantt/menus_controller.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/modules/gantt/app/menus/gantt/menu.rb b/modules/gantt/app/menus/gantt/menu.rb index adaef1f82946..b42544eb0309 100644 --- a/modules/gantt/app/menus/gantt/menu.rb +++ b/modules/gantt/app/menus/gantt/menu.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/modules/meeting/app/components/meetings/meeting_filter_button_component.rb b/modules/meeting/app/components/meetings/meeting_filter_button_component.rb index 954d482d97e7..e834f90796a0 100644 --- a/modules/meeting/app/components/meetings/meeting_filter_button_component.rb +++ b/modules/meeting/app/components/meetings/meeting_filter_button_component.rb @@ -2,7 +2,7 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/modules/meeting/app/components/meetings/meeting_filters_component.rb b/modules/meeting/app/components/meetings/meeting_filters_component.rb index 8c333aa7cb53..1f22fc556df1 100644 --- a/modules/meeting/app/components/meetings/meeting_filters_component.rb +++ b/modules/meeting/app/components/meetings/meeting_filters_component.rb @@ -2,7 +2,7 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/modules/meeting/app/controllers/meetings/menus_controller.rb b/modules/meeting/app/controllers/meetings/menus_controller.rb index 51c3fd71435e..6e934089404f 100644 --- a/modules/meeting/app/controllers/meetings/menus_controller.rb +++ b/modules/meeting/app/controllers/meetings/menus_controller.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/modules/meeting/app/menus/meetings/menu.rb b/modules/meeting/app/menus/meetings/menu.rb index 6528374a1ebe..876c0cd8a74b 100644 --- a/modules/meeting/app/menus/meetings/menu.rb +++ b/modules/meeting/app/menus/meetings/menu.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/modules/reporting/app/controllers/cost_reports/menus_controller.rb b/modules/reporting/app/controllers/cost_reports/menus_controller.rb index a886c94f05e8..83304576e529 100644 --- a/modules/reporting/app/controllers/cost_reports/menus_controller.rb +++ b/modules/reporting/app/controllers/cost_reports/menus_controller.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/modules/reporting/app/menus/cost_reports/menu.rb b/modules/reporting/app/menus/cost_reports/menu.rb index fabd29c03f6e..f65d4ca26e4f 100644 --- a/modules/reporting/app/menus/cost_reports/menu.rb +++ b/modules/reporting/app/menus/cost_reports/menu.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/modules/team_planner/app/controllers/team_planner/menus_controller.rb b/modules/team_planner/app/controllers/team_planner/menus_controller.rb index e782768bac5a..80682f01b9ff 100644 --- a/modules/team_planner/app/controllers/team_planner/menus_controller.rb +++ b/modules/team_planner/app/controllers/team_planner/menus_controller.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/modules/team_planner/app/menus/team_planner/menu.rb b/modules/team_planner/app/menus/team_planner/menu.rb index 7041404b2872..22f56db1118c 100644 --- a/modules/team_planner/app/menus/team_planner/menu.rb +++ b/modules/team_planner/app/menus/team_planner/menu.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/components/shares/user_details_component_spec.rb b/spec/components/shares/user_details_component_spec.rb index 5eefdb309e62..ea1f795bba2d 100644 --- a/spec/components/shares/user_details_component_spec.rb +++ b/spec/components/shares/user_details_component_spec.rb @@ -2,7 +2,7 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/contracts/relations/shared_contract_examples.rb b/spec/contracts/relations/shared_contract_examples.rb index ab8153029c8f..28531260fd1d 100644 --- a/spec/contracts/relations/shared_contract_examples.rb +++ b/spec/contracts/relations/shared_contract_examples.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/contracts/relations/update_contract_spec.rb b/spec/contracts/relations/update_contract_spec.rb index c18527dd625e..87b4b3cb75b3 100644 --- a/spec/contracts/relations/update_contract_spec.rb +++ b/spec/contracts/relations/update_contract_spec.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/contracts/shares/work_packages/create_contract_spec.rb b/spec/contracts/shares/work_packages/create_contract_spec.rb index 0af0bd377379..ff925c4bd4ae 100644 --- a/spec/contracts/shares/work_packages/create_contract_spec.rb +++ b/spec/contracts/shares/work_packages/create_contract_spec.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/contracts/shares/work_packages/delete_contract_spec.rb b/spec/contracts/shares/work_packages/delete_contract_spec.rb index 648466cd27d6..d4c7cdf7c377 100644 --- a/spec/contracts/shares/work_packages/delete_contract_spec.rb +++ b/spec/contracts/shares/work_packages/delete_contract_spec.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/contracts/shares/work_packages/shared_contract_examples.rb b/spec/contracts/shares/work_packages/shared_contract_examples.rb index fe14e69d2afe..b81449c6cd3f 100644 --- a/spec/contracts/shares/work_packages/shared_contract_examples.rb +++ b/spec/contracts/shares/work_packages/shared_contract_examples.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/contracts/shares/work_packages/update_contract_spec.rb b/spec/contracts/shares/work_packages/update_contract_spec.rb index ae5bad5ee273..4e4de90b504c 100644 --- a/spec/contracts/shares/work_packages/update_contract_spec.rb +++ b/spec/contracts/shares/work_packages/update_contract_spec.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/features/work_packages/share/filter_spec.rb b/spec/features/work_packages/share/filter_spec.rb index 741725934eea..cd69ca2bb895 100644 --- a/spec/features/work_packages/share/filter_spec.rb +++ b/spec/features/work_packages/share/filter_spec.rb @@ -2,7 +2,7 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/features/work_packages/share/multi_invite_spec.rb b/spec/features/work_packages/share/multi_invite_spec.rb index 0b6f77f1e687..e45317dfe51b 100644 --- a/spec/features/work_packages/share/multi_invite_spec.rb +++ b/spec/features/work_packages/share/multi_invite_spec.rb @@ -2,7 +2,7 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/features/work_packages/share/share_account_activation_spec.rb b/spec/features/work_packages/share/share_account_activation_spec.rb index be19c842df6d..1c857ee95510 100644 --- a/spec/features/work_packages/share/share_account_activation_spec.rb +++ b/spec/features/work_packages/share/share_account_activation_spec.rb @@ -2,7 +2,7 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/features/work_packages/share/share_spec.rb b/spec/features/work_packages/share/share_spec.rb index 2151b4fad217..1fa38bb2d47a 100644 --- a/spec/features/work_packages/share/share_spec.rb +++ b/spec/features/work_packages/share/share_spec.rb @@ -2,7 +2,7 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/features/workflows/missing_for_sharing_wp_spec.rb b/spec/features/workflows/missing_for_sharing_wp_spec.rb index 242f36d729b9..31ccec8d3400 100644 --- a/spec/features/workflows/missing_for_sharing_wp_spec.rb +++ b/spec/features/workflows/missing_for_sharing_wp_spec.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/helpers/wiki_pages/at_version_spec.rb b/spec/helpers/wiki_pages/at_version_spec.rb index 4b8eaf1224fa..34a4c48beeb2 100644 --- a/spec/helpers/wiki_pages/at_version_spec.rb +++ b/spec/helpers/wiki_pages/at_version_spec.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/lib/acts_as_journalized/journable_differ_spec.rb b/spec/lib/acts_as_journalized/journable_differ_spec.rb index 7e6fc6bd6b8c..63893ce04c9d 100644 --- a/spec/lib/acts_as_journalized/journable_differ_spec.rb +++ b/spec/lib/acts_as_journalized/journable_differ_spec.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/lib/api/v3/work_packages/work_package_at_timestamp_representer_rendering_spec.rb b/spec/lib/api/v3/work_packages/work_package_at_timestamp_representer_rendering_spec.rb index 3f51f419ea84..78280e53a0bd 100644 --- a/spec/lib/api/v3/work_packages/work_package_at_timestamp_representer_rendering_spec.rb +++ b/spec/lib/api/v3/work_packages/work_package_at_timestamp_representer_rendering_spec.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/models/members/scopes/global_spec.rb b/spec/models/members/scopes/global_spec.rb index b179887e8263..277cb3cc7c99 100644 --- a/spec/models/members/scopes/global_spec.rb +++ b/spec/models/members/scopes/global_spec.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/models/members/scopes/of_any_entity_spec.rb b/spec/models/members/scopes/of_any_entity_spec.rb index 7000add2fa9e..a2b91106a299 100644 --- a/spec/models/members/scopes/of_any_entity_spec.rb +++ b/spec/models/members/scopes/of_any_entity_spec.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/models/members/scopes/of_any_project_spec.rb b/spec/models/members/scopes/of_any_project_spec.rb index cfae1b066c24..c3487f1a3ebf 100644 --- a/spec/models/members/scopes/of_any_project_spec.rb +++ b/spec/models/members/scopes/of_any_project_spec.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/models/members/scopes/of_any_work_package_spec.rb b/spec/models/members/scopes/of_any_work_package_spec.rb index 78ca28dc0af6..d8f9fceee130 100644 --- a/spec/models/members/scopes/of_any_work_package_spec.rb +++ b/spec/models/members/scopes/of_any_work_package_spec.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/models/members/scopes/of_anything_in_project_spec.rb b/spec/models/members/scopes/of_anything_in_project_spec.rb index 18c54c8908de..11f46a1e61ae 100644 --- a/spec/models/members/scopes/of_anything_in_project_spec.rb +++ b/spec/models/members/scopes/of_anything_in_project_spec.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/models/members/scopes/of_project_spec.rb b/spec/models/members/scopes/of_project_spec.rb index 778bc54b4913..420da705cb96 100644 --- a/spec/models/members/scopes/of_project_spec.rb +++ b/spec/models/members/scopes/of_project_spec.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/models/members/scopes/of_work_package_spec.rb b/spec/models/members/scopes/of_work_package_spec.rb index 643064d4b280..913b090e1300 100644 --- a/spec/models/members/scopes/of_work_package_spec.rb +++ b/spec/models/members/scopes/of_work_package_spec.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/models/members/scopes/visible_spec.rb b/spec/models/members/scopes/visible_spec.rb index 4304fde45178..0be1638f8310 100644 --- a/spec/models/members/scopes/visible_spec.rb +++ b/spec/models/members/scopes/visible_spec.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/models/members/scopes/with_shared_work_packages_info_spec.rb b/spec/models/members/scopes/with_shared_work_packages_info_spec.rb index 8e7364dc3957..fc3f691f1550 100644 --- a/spec/models/members/scopes/with_shared_work_packages_info_spec.rb +++ b/spec/models/members/scopes/with_shared_work_packages_info_spec.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/models/members/scopes/without_inherited_roles_spec.rb b/spec/models/members/scopes/without_inherited_roles_spec.rb index e1a8f058257a..3ac73888fe08 100644 --- a/spec/models/members/scopes/without_inherited_roles_spec.rb +++ b/spec/models/members/scopes/without_inherited_roles_spec.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/models/principals/scopes/having_entity_membership_spec.rb b/spec/models/principals/scopes/having_entity_membership_spec.rb index 544930fcd093..724ca7d9926a 100644 --- a/spec/models/principals/scopes/having_entity_membership_spec.rb +++ b/spec/models/principals/scopes/having_entity_membership_spec.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/models/project_role_spec.rb b/spec/models/project_role_spec.rb index 6a95a15eaeaf..df30ca2176f2 100644 --- a/spec/models/project_role_spec.rb +++ b/spec/models/project_role_spec.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/models/projects/scopes/available_custom_fields_spec.rb b/spec/models/projects/scopes/available_custom_fields_spec.rb index 8d13425d5f1f..9c500a95713a 100644 --- a/spec/models/projects/scopes/available_custom_fields_spec.rb +++ b/spec/models/projects/scopes/available_custom_fields_spec.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/models/projects/scopes/visible_spec.rb b/spec/models/projects/scopes/visible_spec.rb index 2f2a28ff46f1..deb06e7cdb3a 100644 --- a/spec/models/projects/scopes/visible_spec.rb +++ b/spec/models/projects/scopes/visible_spec.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/services/shares/delete_role_service_spec.rb b/spec/services/shares/delete_role_service_spec.rb index b5ebce34b51c..c8559b4f4d5f 100644 --- a/spec/services/shares/delete_role_service_spec.rb +++ b/spec/services/shares/delete_role_service_spec.rb @@ -2,7 +2,7 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/services/shares/delete_service_spec.rb b/spec/services/shares/delete_service_spec.rb index 0df47449fb8d..cd4a16a55f9a 100644 --- a/spec/services/shares/delete_service_spec.rb +++ b/spec/services/shares/delete_service_spec.rb @@ -2,7 +2,7 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/services/shares/set_attributes_service_spec.rb b/spec/services/shares/set_attributes_service_spec.rb index 874f97e5d2dd..26fcc4d5b9ad 100644 --- a/spec/services/shares/set_attributes_service_spec.rb +++ b/spec/services/shares/set_attributes_service_spec.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/support/components/projects/project_custom_fields/edit_dialog.rb b/spec/support/components/projects/project_custom_fields/edit_dialog.rb index 80abf886e0a7..a438f9392494 100644 --- a/spec/support/components/projects/project_custom_fields/edit_dialog.rb +++ b/spec/support/components/projects/project_custom_fields/edit_dialog.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. diff --git a/spec/support/components/work_packages/share_modal.rb b/spec/support/components/work_packages/share_modal.rb index ab5be93450f2..6f7956df8e51 100644 --- a/spec/support/components/work_packages/share_modal.rb +++ b/spec/support/components/work_packages/share_modal.rb @@ -1,6 +1,6 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2023 the OpenProject GmbH +# Copyright (C) 2010-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. From 825a62d0a926ca70b5f73da842dc29ce1763ab0b Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Wed, 3 Jul 2024 09:26:11 +0200 Subject: [PATCH 46/62] Allow multiple custom components --- app/components/shares/modal_body_component.html.erb | 8 +++++--- app/models/sharing_strategies/base_strategy.rb | 10 +++++----- .../sharing_strategies/project_query_strategy.rb | 7 +++++-- config/locales/en.yml | 4 ++++ 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/app/components/shares/modal_body_component.html.erb b/app/components/shares/modal_body_component.html.erb index 2995901dc0d7..f66a46615a45 100644 --- a/app/components/shares/modal_body_component.html.erb +++ b/app/components/shares/modal_body_component.html.erb @@ -1,11 +1,13 @@ <%= component_wrapper(tag: 'turbo-frame') do flex_layout(data: { turbo: true }) do |modal_content| - if strategy.custom_body_component? - render(strategy.additional_body_component.new(strategy: strategy, modal_body_container: modal_content)) + if strategy.custom_body_components? + strategy.additional_body_components.each do |component| + render(component.new(strategy: strategy, modal_body_container: modal_content)) + end end - modal_content.with_row(mt: (strategy.custom_body_component? ? 3 : nil)) do + modal_content.with_row(mt: (strategy.custom_body_components? ? 3 : nil)) do render(Shares::InviteUserFormComponent.new(strategy:, errors: errors)) end diff --git a/app/models/sharing_strategies/base_strategy.rb b/app/models/sharing_strategies/base_strategy.rb index 6b1a82fb59f8..9976d93c325d 100644 --- a/app/models/sharing_strategies/base_strategy.rb +++ b/app/models/sharing_strategies/base_strategy.rb @@ -61,13 +61,13 @@ def delete_contract_class raise NotImplementedError, "Override in a subclass and return the contract class for deleting a share" end - def custom_body_component? - additional_body_component.present? + def custom_body_components? + !additional_body_components.empty? end - # Override by returning a component class that should be rendered in the sharing dialog above the table of shares - def additional_body_component - nil + # Override by returning a list of component classes that should be rendered in the sharing dialog above the table of shares + def additional_body_components + [] end def custom_empty_state_component? diff --git a/app/models/sharing_strategies/project_query_strategy.rb b/app/models/sharing_strategies/project_query_strategy.rb index 981b3fa73593..dc600fb2c1b4 100644 --- a/app/models/sharing_strategies/project_query_strategy.rb +++ b/app/models/sharing_strategies/project_query_strategy.rb @@ -62,8 +62,11 @@ def delete_contract_class Shares::ProjectQueries::DeleteContract end - def additional_body_component - Shares::ProjectQueries::PublicFlagComponent + def additional_body_components + [ + Shares::ProjectQueries::PublicFlagComponent, + Shares::ProjectQueries::ProjectAccessWarningComponent + ] end def empty_state_component diff --git a/config/locales/en.yml b/config/locales/en.yml index a868c0e24b3a..ff76954e8a20 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -3675,6 +3675,10 @@ en: additional_privileges_group: "Might have additional privileges (as group member)" additional_privileges_project_or_group: "Might have additional privileges (as project or group member)" project_queries: + access_warning: "Users will only see the projects they have access to. Sharing project lists does not impact individual project permissions." + public_flag: + label: "Share with everyone at %{instance_name}" + caption: "Everyone can view this project list. Those with global edit permissions can modify it." text_empty_state_header: "Shared with everyone" text_empty_state_description: "Everyone can view this project list. You can also add individual users with extra permissions." permissions: From d88d2ed75cd0519e89e6c4b3df0dca6d68082242 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Wed, 3 Jul 2024 09:26:27 +0200 Subject: [PATCH 47/62] Add translations to the public flag component --- .../shares/project_queries/public_flag_component.html.erb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/components/shares/project_queries/public_flag_component.html.erb b/app/components/shares/project_queries/public_flag_component.html.erb index d218a2774b29..cdfe036edca0 100644 --- a/app/components/shares/project_queries/public_flag_component.html.erb +++ b/app/components/shares/project_queries/public_flag_component.html.erb @@ -2,9 +2,8 @@ container.with_row(mt: 3) do render(Primer::Forms::ToggleSwitchForm.new( name: "publish_project_query", - # TODO: Proper translations - label: "Make the query public", - caption: "All users will have access", + label: t('sharing.project_queries.public_flag.label', instance_name: Setting.app_title), + caption: t('sharing.project_queries.public_flag.caption'), src: toggle_public_flag_link, csrf_token: form_authenticity_token, status_label_position: :start, From 18c1f1a50e93da639c8ea80e92d76628eebd1d95 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Wed, 3 Jul 2024 09:26:41 +0200 Subject: [PATCH 48/62] Add an access warning --- .../project_access_warning_component.html.erb | 3 +++ .../project_access_warning_component.rb | 26 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 app/components/shares/project_queries/project_access_warning_component.html.erb create mode 100644 app/components/shares/project_queries/project_access_warning_component.rb diff --git a/app/components/shares/project_queries/project_access_warning_component.html.erb b/app/components/shares/project_queries/project_access_warning_component.html.erb new file mode 100644 index 000000000000..3e9558fe38d6 --- /dev/null +++ b/app/components/shares/project_queries/project_access_warning_component.html.erb @@ -0,0 +1,3 @@ +<%- if query_is_public? || query_is_shared? %> + <%= container.with_row(mt: 3) { render(Primer::Alpha::Banner.new(icon: :info)) { I18n.t('sharing.project_queries.access_warning') } }%> +<%- end %> diff --git a/app/components/shares/project_queries/project_access_warning_component.rb b/app/components/shares/project_queries/project_access_warning_component.rb new file mode 100644 index 000000000000..97b76a75d61f --- /dev/null +++ b/app/components/shares/project_queries/project_access_warning_component.rb @@ -0,0 +1,26 @@ +module Shares + module ProjectQueries + class ProjectAccessWarningComponent < ViewComponent::Base # rubocop:disable OpenProject/AddPreviewForViewComponent + include OpPrimer::ComponentHelpers + + def initialize(strategy:, modal_body_container:) + super + + @strategy = strategy + @container = modal_body_container + end + + private + + attr_reader :strategy, :container + + def query_is_public? + @strategy.entity.public? + end + + def query_is_shared? + @strategy.entity.members.any? + end + end + end +end From a7403feea550f0d1a1583a67f5ff9ca1aca7d481 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Wed, 3 Jul 2024 09:34:57 +0200 Subject: [PATCH 49/62] Add tri-state to empty state --- .../shares/project_queries/empty_state_component.rb | 12 ++++++++---- .../sharing_strategies/project_query_strategy.rb | 2 +- config/locales/en.yml | 9 +++++++-- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/app/components/shares/project_queries/empty_state_component.rb b/app/components/shares/project_queries/empty_state_component.rb index 36e986bcb260..0a6a5d7819ab 100644 --- a/app/components/shares/project_queries/empty_state_component.rb +++ b/app/components/shares/project_queries/empty_state_component.rb @@ -42,12 +42,16 @@ def initialize(strategy:) attr_reader :strategy, :entity - def blankslate_config + def blankslate_config # rubocop:disable Metrics/AbcSize @blankslate_config ||= {}.tap do |config| - if params[:filters].blank? + if entity.public? config[:icon] = :people - config[:heading_text] = I18n.t("sharing.project_queries.text_empty_state_header") - config[:description_text] = I18n.t("sharing.project_queries.text_empty_state_description") + config[:heading_text] = I18n.t("sharing.project_queries.blank_state.public.header") + config[:description_text] = I18n.t("sharing.project_queries.blank_state.public.description") + elsif params[:filters].blank? + config[:icon] = "share-android" + config[:heading_text] = I18n.t("sharing.project_queries.blank_state.private.header") + config[:description_text] = I18n.t("sharing.project_queries.blank_state.private.description") else config[:icon] = :search config[:heading_text] = I18n.t("sharing.text_empty_search_header") diff --git a/app/models/sharing_strategies/project_query_strategy.rb b/app/models/sharing_strategies/project_query_strategy.rb index dc600fb2c1b4..09cb2b6973fe 100644 --- a/app/models/sharing_strategies/project_query_strategy.rb +++ b/app/models/sharing_strategies/project_query_strategy.rb @@ -70,7 +70,7 @@ def additional_body_components end def empty_state_component - Shares::ProjectQueries::EmptyStateComponent if @entity.public? + Shares::ProjectQueries::EmptyStateComponent end end end diff --git a/config/locales/en.yml b/config/locales/en.yml index ff76954e8a20..9a297cefc287 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -3679,8 +3679,13 @@ en: public_flag: label: "Share with everyone at %{instance_name}" caption: "Everyone can view this project list. Those with global edit permissions can modify it." - text_empty_state_header: "Shared with everyone" - text_empty_state_description: "Everyone can view this project list. You can also add individual users with extra permissions." + blank_state: + public: + header: "Shared with everyone" + description: "Everyone can view this project list. You can also add individual users with extra permissions." + private: + header: "Not shared: Private" + description: "This project list has not been shared with anyone yet. Only you can access this list." permissions: view: "View" view_description: "TODO: Add me" From f6b9daebd97a56a8a1ae6f219e50230e9cb1eef3 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Wed, 3 Jul 2024 09:50:41 +0200 Subject: [PATCH 50/62] Add documentation for keep-open-on-submit --- lookbook/docs/patterns/05-dialogs.md.erb | 32 +++++++++++++----------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/lookbook/docs/patterns/05-dialogs.md.erb b/lookbook/docs/patterns/05-dialogs.md.erb index 75046c837aad..6ecba47b38ec 100644 --- a/lookbook/docs/patterns/05-dialogs.md.erb +++ b/lookbook/docs/patterns/05-dialogs.md.erb @@ -1,13 +1,9 @@ As of now, this page is still a stub. In particular, the considerations on when and where to apply Dialogs are still missing. - For now, it can be stated that OpenProject employs Dialogs for more purposes than GitHub [Primer specifies](https://primer.style/components/dialog). - ## Async dialogs as turbo streams - Primer dialogs need to be rendered and tied to a button, resulting in their content always being present. If you have any non-trivial content that should be displayed in a dialog, you should use the async dialog pattern. - To render dialogs asynchronously, you use a Button/Link/IconButton component as a trigger and add the following attributes to it: @@ -152,6 +148,23 @@ class TestController < ApplicationControler end ``` +### Dialogs that should stay open on form submission + +Because we normally want to close the dialog when a form is submitted successfully, we have implmented this as the default behaviour. As described in the last section, +when the submission returns a 4xx or 5xx status code, the dialog will stay open and display the validation errors and will be closed when the form is submitted successfully. + +If you want to keep a dialog open even after a successful form submission, you can use the `data-keep-open-on-submit="true"` attribute on the dialog. This will prevent the dialog +from closing even if the form submission is successful. + +```erb +<%%= turbo_stream.dialog do + render(Primer::Alpha::Dialog.new(data: { 'keep-open-on-submit': true }) do |d| + # ... + end +end +%> +``` + ## Special kinds of dialogs We have multiple kind of dialogs that re-appear throughout the whole application. In order to make sure, they all look and feel alike, we further specify them here. @@ -198,7 +211,6 @@ the form via the `form` attribute. In pseudo html, the logical format is then like this: ```html - Dialog title @@ -210,33 +222,25 @@ In pseudo html, the logical format is then like this: Submit - + - ``` <%= embed OpenProject::Common::DialogPreview, :form %> - #### Where it's used - - WorkPackage → Meeting Tab → Add meeting to WorkPackage ### User nudging modal - The idea of nudging modals is to promote users to perform actions that are not 100% mandatory but might be interesting for them. For example, after creating a type, an admin would like to add the new type to a project and configure a workflow for it. Another example is to grant personal access to a recently created storage: - - #### Accessibility considerations - To be written down.. #### Where it's used - - Admin → Storages → OAuth Grant Access From c8fa05b13a3b5235a98c8798905992d86f19eb6e Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Wed, 3 Jul 2024 10:17:09 +0200 Subject: [PATCH 51/62] Add ShareDialog preview --- .../shares/share_dialog_component.html.erb | 8 +++ .../shares/share_dialog_component.rb | 49 +++++++++++++++++++ app/views/shares/dialog.turbo_stream.erb | 10 +--- .../shares/share_dialog_component_preview.rb | 23 +++++++++ 4 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 app/components/shares/share_dialog_component.html.erb create mode 100644 app/components/shares/share_dialog_component.rb create mode 100644 lookbook/previews/shares/share_dialog_component_preview.rb diff --git a/app/components/shares/share_dialog_component.html.erb b/app/components/shares/share_dialog_component.html.erb new file mode 100644 index 000000000000..e9e061841c7f --- /dev/null +++ b/app/components/shares/share_dialog_component.html.erb @@ -0,0 +1,8 @@ +<%= + render(Primer::Alpha::Dialog.new(title: t(:label_share_project_list), id: 'sharing-modal', data: { 'keep-open-on-submit': true }, size: :xlarge)) do |d| + d.with_header(variant: :large) + d.with_body do + render(Shares::ModalBodyComponent.new(strategy:, shares:, errors:)) + end + end +%> diff --git a/app/components/shares/share_dialog_component.rb b/app/components/shares/share_dialog_component.rb new file mode 100644 index 000000000000..e9e56d6cc768 --- /dev/null +++ b/app/components/shares/share_dialog_component.rb @@ -0,0 +1,49 @@ +# 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. +# ++ + +module Shares + class ShareDialogComponent < ApplicationComponent + include ApplicationHelper + include OpTurbo::Streamable + include OpPrimer::ComponentHelpers + + def initialize(shares:, strategy:, errors:) + super + + @strategy = strategy + @shares = shares + @errors = errors + end + + private + + attr_reader :shares, :strategy, :errors + end +end diff --git a/app/views/shares/dialog.turbo_stream.erb b/app/views/shares/dialog.turbo_stream.erb index 7c16bd93ef15..9e5a07ab1977 100644 --- a/app/views/shares/dialog.turbo_stream.erb +++ b/app/views/shares/dialog.turbo_stream.erb @@ -1,12 +1,4 @@ <%= turbo_stream.dialog do - render(Primer::Alpha::Dialog.new(title: t(:label_share_project_list), - id: 'sharing-modal', - data: { 'keep-open-on-submit': true }, - size: :xlarge)) do |d| - d.with_header(variant: :large) - d.with_body do - render(Shares::ModalBodyComponent.new(strategy: @sharing_strategy, shares: @shares, errors: @errors)) - end - end + render(Shares::ShareDialogComponent.new(strategy: @sharing_strategy, shares: @shares, errors: @errors)) end %> diff --git a/lookbook/previews/shares/share_dialog_component_preview.rb b/lookbook/previews/shares/share_dialog_component_preview.rb new file mode 100644 index 000000000000..8818bcb4e508 --- /dev/null +++ b/lookbook/previews/shares/share_dialog_component_preview.rb @@ -0,0 +1,23 @@ +module Shares + class ShareDialogComponentPreview < Lookbook::Preview + def project_query + user = FactoryBot.build_stubbed(:admin) + query = FactoryBot.build_stubbed(:project_query, user:) + strategy = SharingStrategies::ProjectQueryStrategy.new(query, user:) + errors = [] + shares = [] + + render(Shares::ShareDialogComponent.new(strategy:, shares:, errors:)) + end + + def work_package + user = FactoryBot.build_stubbed(:admin) + work_package = FactoryBot.build_stubbed(:work_package) + strategy = SharingStrategies::WorkPackageStrategy.new(work_package, user:) + errors = [] + shares = [] + + render(Shares::ShareDialogComponent.new(strategy:, shares:, errors:)) + end + end +end From 0dcdd58426b9625d08b8330bb7c241934fdf6672 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Wed, 3 Jul 2024 11:20:01 +0200 Subject: [PATCH 52/62] Add descriptions for roles --- .../sharing_strategies/project_query_strategy.rb | 12 ++++++++---- config/locales/en.yml | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/models/sharing_strategies/project_query_strategy.rb b/app/models/sharing_strategies/project_query_strategy.rb index 09cb2b6973fe..574a41ec3926 100644 --- a/app/models/sharing_strategies/project_query_strategy.rb +++ b/app/models/sharing_strategies/project_query_strategy.rb @@ -32,13 +32,17 @@ def available_roles role_mapping = ProjectQueryRole.pluck(:builtin, :id).to_h [ - { label: I18n.t("sharing.project_queries.permissions.edit"), + { + label: I18n.t("sharing.project_queries.permissions.edit"), value: role_mapping[Role::BUILTIN_PROJECT_QUERY_EDIT], - description: I18n.t("sharing.project_queries.permissions.edit_description") }, - { label: I18n.t("sharing.project_queries.permissions.view"), + description: I18n.t("sharing.project_queries.permissions.edit_description") + }, + { + label: I18n.t("sharing.project_queries.permissions.view"), value: role_mapping[Role::BUILTIN_PROJECT_QUERY_VIEW], description: I18n.t("sharing.project_queries.permissions.view_description"), - default: true } + default: true + } ] end diff --git a/config/locales/en.yml b/config/locales/en.yml index 9a297cefc287..5ce8012c322f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -3688,9 +3688,9 @@ en: description: "This project list has not been shared with anyone yet. Only you can access this list." permissions: view: "View" - view_description: "TODO: Add me" + view_description: "Can view this project list." edit: "Edit" - edit_description: "TODO: Add me as well" + edit_description: "Can view, share and edit this project list." working_days: info: > Days that are not selected are skipped when scheduling work packages (and not included in the day count). From 90dcd9dea04c8d9c14c8e69b3996b68fe70ae28c Mon Sep 17 00:00:00 2001 From: Aaron Contreras <61627014+aaron-contreras@users.noreply.github.com> Date: Wed, 3 Jul 2024 12:08:29 -0500 Subject: [PATCH 53/62] Fix typo in dialog documentation Thanks @dombesz --- lookbook/docs/patterns/05-dialogs.md.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lookbook/docs/patterns/05-dialogs.md.erb b/lookbook/docs/patterns/05-dialogs.md.erb index 6ecba47b38ec..e39a97bd4324 100644 --- a/lookbook/docs/patterns/05-dialogs.md.erb +++ b/lookbook/docs/patterns/05-dialogs.md.erb @@ -150,7 +150,7 @@ end ### Dialogs that should stay open on form submission -Because we normally want to close the dialog when a form is submitted successfully, we have implmented this as the default behaviour. As described in the last section, +Because we normally want to close the dialog when a form is submitted successfully, we have implemented this as the default behaviour. As described in the last section, when the submission returns a 4xx or 5xx status code, the dialog will stay open and display the validation errors and will be closed when the form is submitted successfully. If you want to keep a dialog open even after a successful form submission, you can use the `data-keep-open-on-submit="true"` attribute on the dialog. This will prevent the dialog From 05d05adb94eaea9a1acce0f17b4647c126755fd5 Mon Sep 17 00:00:00 2001 From: Aaron Contreras Date: Wed, 3 Jul 2024 13:52:34 -0500 Subject: [PATCH 54/62] Extract 'show toggle share dialog' button condition --- app/components/projects/index_page_header_component.html.erb | 2 +- app/components/projects/index_page_header_component.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/components/projects/index_page_header_component.html.erb b/app/components/projects/index_page_header_component.html.erb index 8762ae35bd2a..fe6997b729de 100644 --- a/app/components/projects/index_page_header_component.html.erb +++ b/app/components/projects/index_page_header_component.html.erb @@ -21,7 +21,7 @@ ) end - if query.persisted? && OpenProject::FeatureDecisions.project_list_sharing_active? + if show_toggle_share_dialog_button? header.with_action_icon_button( tag: :a, href: dialog_project_query_members_path(query), diff --git a/app/components/projects/index_page_header_component.rb b/app/components/projects/index_page_header_component.rb index 119efbefd99f..d947ce8c9991 100644 --- a/app/components/projects/index_page_header_component.rb +++ b/app/components/projects/index_page_header_component.rb @@ -92,6 +92,10 @@ def show_state? state == :show end + def show_toggle_share_dialog_button? + query.persisted? && OpenProject::FeatureDecisions.project_list_sharing_active? + end + def breadcrumb_items [ { href: projects_path, text: t(:label_project_plural) }, From 467fe5695bda3b863f16b91e94b3d37e935d59d4 Mon Sep 17 00:00:00 2001 From: Aaron Contreras Date: Wed, 3 Jul 2024 14:02:55 -0500 Subject: [PATCH 55/62] Extract blankslate config setup into separate methods --- .../shares/empty_state_component.rb | 34 ++++++++----- .../project_queries/empty_state_component.rb | 48 ++++++++++++------- 2 files changed, 54 insertions(+), 28 deletions(-) diff --git a/app/components/shares/empty_state_component.rb b/app/components/shares/empty_state_component.rb index df896b878297..1dda0facba91 100644 --- a/app/components/shares/empty_state_component.rb +++ b/app/components/shares/empty_state_component.rb @@ -41,18 +41,28 @@ def initialize(strategy:) attr_reader :strategy, :entity - def blankslate_config # rubocop:disable Metrics/AbcSize - @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 + def blankslate_config + @blankslate_config ||= if params[:filters].blank? + unfiltered_blankslate_config + else + filtered_blankslate_config + end + end + + def unfiltered_blankslate_config + { + icon: :people, + heading_text: I18n.t("sharing.text_empty_state_header"), + description_text: I18n.t("sharing.text_empty_state_description", entity: @entity.class.model_name.human) + } + end + + def filtered_blankslate_config + { + icon: :search, + heading_text: I18n.t("sharing.text_empty_search_header"), + description_text: I18n.t("sharing.text_empty_search_description") + } end end end diff --git a/app/components/shares/project_queries/empty_state_component.rb b/app/components/shares/project_queries/empty_state_component.rb index 0a6a5d7819ab..b02fcb9d8dd6 100644 --- a/app/components/shares/project_queries/empty_state_component.rb +++ b/app/components/shares/project_queries/empty_state_component.rb @@ -42,22 +42,38 @@ def initialize(strategy:) attr_reader :strategy, :entity - def blankslate_config # rubocop:disable Metrics/AbcSize - @blankslate_config ||= {}.tap do |config| - if entity.public? - config[:icon] = :people - config[:heading_text] = I18n.t("sharing.project_queries.blank_state.public.header") - config[:description_text] = I18n.t("sharing.project_queries.blank_state.public.description") - elsif params[:filters].blank? - config[:icon] = "share-android" - config[:heading_text] = I18n.t("sharing.project_queries.blank_state.private.header") - config[:description_text] = I18n.t("sharing.project_queries.blank_state.private.description") - 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 + def blankslate_config + @blankslate_config ||= if entity.public? + public_blankslate_config + elsif params[:filters].blank? + unfiltered_blankslate_config + else + filtered_blankslate_config + end + end + + def public_blankslate_config + { + icon: :people, + heading_text: I18n.t("sharing.project_queries.blank_state.public.header"), + description_text: I18n.t("sharing.project_queries.blank_state.public.description") + } + end + + def unfiltered_blankslate_config + { + icon: :people, + heading_text: I18n.t("sharing.project_queries.blank_state.private.header"), + description_text: I18n.t("sharing.project_queries.blank_state.private.description") + } + end + + def filtered_blankslate_config + { + icon: :search, + heading_text: I18n.t("sharing.text_empty_search_header"), + description_text: I18n.t("sharing.text_empty_search_description") + } end end end From ff8a5f8a26a5983ad5a52cf7d076aa15f3879a4e Mon Sep 17 00:00:00 2001 From: Aaron Contreras <61627014+aaron-contreras@users.noreply.github.com> Date: Wed, 3 Jul 2024 14:07:25 -0500 Subject: [PATCH 56/62] Set consistent order in state initialization Thanks Ivan! Co-authored-by: Ivan Kuchin --- app/components/shares/share_dialog_component.rb | 2 +- app/components/shares/share_row_component.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/shares/share_dialog_component.rb b/app/components/shares/share_dialog_component.rb index e9e56d6cc768..702f5adc5567 100644 --- a/app/components/shares/share_dialog_component.rb +++ b/app/components/shares/share_dialog_component.rb @@ -37,8 +37,8 @@ class ShareDialogComponent < ApplicationComponent def initialize(shares:, strategy:, errors:) super - @strategy = strategy @shares = shares + @strategy = strategy @errors = errors end diff --git a/app/components/shares/share_row_component.rb b/app/components/shares/share_row_component.rb index b047f1435290..1a4376af63de 100644 --- a/app/components/shares/share_row_component.rb +++ b/app/components/shares/share_row_component.rb @@ -37,8 +37,8 @@ class ShareRowComponent < ApplicationComponent # rubocop:disable OpenProject/Add def initialize(share:, strategy:, container: nil) super - @strategy = strategy @share = share + @strategy = strategy @entity = strategy.entity @principal = share.principal @available_roles = strategy.available_roles From e964e00c847a5f11858d7cfc683b86764347c68d Mon Sep 17 00:00:00 2001 From: Aaron Contreras Date: Wed, 3 Jul 2024 14:27:20 -0500 Subject: [PATCH 57/62] Use gap as spacing between rows in the share modal * We use `mb: 3` to preserve the spacing off the modal header as we do in the share modal. --- app/components/shares/modal_body_component.html.erb | 8 ++++---- app/components/shares/modal_body_component.sass | 3 +++ .../project_access_warning_component.html.erb | 2 +- .../shares/project_queries/public_flag_component.html.erb | 2 +- app/components/shares/share_dialog_component.html.erb | 2 +- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/components/shares/modal_body_component.html.erb b/app/components/shares/modal_body_component.html.erb index f66a46615a45..a4cf6c39cb98 100644 --- a/app/components/shares/modal_body_component.html.erb +++ b/app/components/shares/modal_body_component.html.erb @@ -1,18 +1,18 @@ <%= component_wrapper(tag: 'turbo-frame') do - flex_layout(data: { turbo: true }) do |modal_content| + flex_layout(data: { turbo: true }, + classes: "op-share-dialog-modal-body--row-container") do |modal_content| if strategy.custom_body_components? strategy.additional_body_components.each do |component| render(component.new(strategy: strategy, modal_body_container: modal_content)) end end - modal_content.with_row(mt: (strategy.custom_body_components? ? 3 : nil)) do + modal_content.with_row do render(Shares::InviteUserFormComponent.new(strategy:, errors: errors)) end - modal_content.with_row(mt: 3, - data: { 'test-selector': 'op-share-dialog-active-list', + modal_content.with_row(data: { 'test-selector': 'op-share-dialog-active-list', controller: 'shares--bulk-selection', application_target: 'dynamic' }) do render(border_box_container(list_id: insert_target_modifier_id)) do |border_box| diff --git a/app/components/shares/modal_body_component.sass b/app/components/shares/modal_body_component.sass index 17f99bc4aab1..44f6e8260e20 100644 --- a/app/components/shares/modal_body_component.sass +++ b/app/components/shares/modal_body_component.sass @@ -1,4 +1,7 @@ .op-share-dialog-modal-body + &--row-container + gap: 16px + &--user-row display: grid grid-template-columns: minmax(31px, auto) 1fr // 31px is the width needed to display a group avatar diff --git a/app/components/shares/project_queries/project_access_warning_component.html.erb b/app/components/shares/project_queries/project_access_warning_component.html.erb index 3e9558fe38d6..629a8b7b9dca 100644 --- a/app/components/shares/project_queries/project_access_warning_component.html.erb +++ b/app/components/shares/project_queries/project_access_warning_component.html.erb @@ -1,3 +1,3 @@ <%- if query_is_public? || query_is_shared? %> - <%= container.with_row(mt: 3) { render(Primer::Alpha::Banner.new(icon: :info)) { I18n.t('sharing.project_queries.access_warning') } }%> + <%= container.with_row { render(Primer::Alpha::Banner.new(icon: :info)) { I18n.t('sharing.project_queries.access_warning') } }%> <%- end %> diff --git a/app/components/shares/project_queries/public_flag_component.html.erb b/app/components/shares/project_queries/public_flag_component.html.erb index cdfe036edca0..285646253cba 100644 --- a/app/components/shares/project_queries/public_flag_component.html.erb +++ b/app/components/shares/project_queries/public_flag_component.html.erb @@ -1,5 +1,5 @@ <%= -container.with_row(mt: 3) do +container.with_row do render(Primer::Forms::ToggleSwitchForm.new( name: "publish_project_query", label: t('sharing.project_queries.public_flag.label', instance_name: Setting.app_title), diff --git a/app/components/shares/share_dialog_component.html.erb b/app/components/shares/share_dialog_component.html.erb index e9e061841c7f..df84ba60efd5 100644 --- a/app/components/shares/share_dialog_component.html.erb +++ b/app/components/shares/share_dialog_component.html.erb @@ -1,6 +1,6 @@ <%= render(Primer::Alpha::Dialog.new(title: t(:label_share_project_list), id: 'sharing-modal', data: { 'keep-open-on-submit': true }, size: :xlarge)) do |d| - d.with_header(variant: :large) + d.with_header(variant: :large, mb: 3) d.with_body do render(Shares::ModalBodyComponent.new(strategy:, shares:, errors:)) end From d0d820d3c8f3d08d7490d39b4816c91fd285230b Mon Sep 17 00:00:00 2001 From: Aaron Contreras <61627014+aaron-contreras@users.noreply.github.com> Date: Wed, 3 Jul 2024 14:29:28 -0500 Subject: [PATCH 58/62] Split complex guard clause into two Thanks Ivan! Co-authored-by: Ivan Kuchin --- app/components/shares/modal_body_component.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/components/shares/modal_body_component.rb b/app/components/shares/modal_body_component.rb index d1b96c8f72a9..231cb4bd070a 100644 --- a/app/components/shares/modal_body_component.rb +++ b/app/components/shares/modal_body_component.rb @@ -90,7 +90,8 @@ def type_filter_option_active?(option) # rubocop:disable Metrics/AbcSize principal_type_filter_value = current_filter_value(params[:filters], "principal_type") project_member_filter_value = current_filter_value(params[:filters], "also_project_member") - return false if principal_type_filter_value.nil? || (project_scoped_entity? && project_member_filter_value.nil?) + return false if principal_type_filter_value.nil? + return false if project_scoped_entity? && project_member_filter_value.nil? principal_type_checked = option[:value][:principal_type] == principal_type_filter_value From 1f8d492a0d6534b75801e3a956879effec11e4ad Mon Sep 17 00:00:00 2001 From: Aaron Contreras Date: Wed, 3 Jul 2024 14:34:20 -0500 Subject: [PATCH 59/62] Rename `#checked` to `#published?` --- .../shares/project_queries/public_flag_component.html.erb | 2 +- app/components/shares/project_queries/public_flag_component.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/shares/project_queries/public_flag_component.html.erb b/app/components/shares/project_queries/public_flag_component.html.erb index 285646253cba..aec171db24e8 100644 --- a/app/components/shares/project_queries/public_flag_component.html.erb +++ b/app/components/shares/project_queries/public_flag_component.html.erb @@ -7,7 +7,7 @@ container.with_row do src: toggle_public_flag_link, csrf_token: form_authenticity_token, status_label_position: :start, - checked:, + checked: published?, enabled: can_publish? )) end diff --git a/app/components/shares/project_queries/public_flag_component.rb b/app/components/shares/project_queries/public_flag_component.rb index 0c00669ce60e..813c5caa2a11 100644 --- a/app/components/shares/project_queries/public_flag_component.rb +++ b/app/components/shares/project_queries/public_flag_component.rb @@ -49,7 +49,7 @@ def toggle_public_flag_link toggle_public_project_query_path(strategy.entity) end - def checked + def published? strategy.entity.public? end From 5fd3b5a04c7b3633942b0ead362fe16643c678e6 Mon Sep 17 00:00:00 2001 From: Aaron Contreras Date: Wed, 3 Jul 2024 14:43:59 -0500 Subject: [PATCH 60/62] Split multi-check conditionals in project/menu.rb --- app/menus/projects/menu.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/menus/projects/menu.rb b/app/menus/projects/menu.rb index b19ba2af9623..857c0dbf2db3 100644 --- a/app/menus/projects/menu.rb +++ b/app/menus/projects/menu.rb @@ -104,14 +104,15 @@ def public_filters def my_filters persisted_filters - .select { |query| query.user == current_user && !query.public? } + .reject(&:public?) + .select { |query| query.user == current_user } .map { |query| menu_item(query.name, query_id: query.id) } end def shared_filters persisted_filters - .select { |query| !query.public? && query.user != current_user } - # query is not public and not owned by the user, so it must be shared with them + .reject(&:public?) + .reject { |query| query.user == current_user } .map { |query| menu_item(query.name, query_id: query.id) } end From 1831b7679b11569d2d2c6c465d8026de11aeb172 Mon Sep 17 00:00:00 2001 From: Aaron Contreras <61627014+aaron-contreras@users.noreply.github.com> Date: Wed, 3 Jul 2024 14:45:36 -0500 Subject: [PATCH 61/62] Fix typo in SharingStrategies::BaseStrategy Thanks Ivan! Co-authored-by: Ivan Kuchin --- app/models/sharing_strategies/base_strategy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/sharing_strategies/base_strategy.rb b/app/models/sharing_strategies/base_strategy.rb index 9976d93c325d..e28dbf2e55a8 100644 --- a/app/models/sharing_strategies/base_strategy.rb +++ b/app/models/sharing_strategies/base_strategy.rb @@ -40,7 +40,7 @@ def available_roles raise NotImplementedError, "Override in a subclass and return an array of roles that should be displayed" end - def vieable? + def viewable? raise NotImplementedError, "Override in a subclass and return true if the current user can view who the entity is shared with" end From c600054027e5eb471e4c009bf99b0b8e1b1d03b8 Mon Sep 17 00:00:00 2001 From: Aaron Contreras <61627014+aaron-contreras@users.noreply.github.com> Date: Wed, 3 Jul 2024 14:46:33 -0500 Subject: [PATCH 62/62] Fix .erb tag Co-authored-by: Ivan Kuchin --- app/views/shares/dialog.turbo_stream.erb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/views/shares/dialog.turbo_stream.erb b/app/views/shares/dialog.turbo_stream.erb index 9e5a07ab1977..56f9e3b1a3ab 100644 --- a/app/views/shares/dialog.turbo_stream.erb +++ b/app/views/shares/dialog.turbo_stream.erb @@ -1,4 +1,3 @@ <%= turbo_stream.dialog do render(Shares::ShareDialogComponent.new(strategy: @sharing_strategy, shares: @shares, errors: @errors)) -end -%> +end %>