From 5e0bf3d0a1ee2e42af6463f1c10f43ea914ed133 Mon Sep 17 00:00:00 2001 From: Aaron Dewes Date: Fri, 26 Apr 2024 18:23:09 +0200 Subject: [PATCH 01/91] Add support for Cloudflare Turnstile Cloudflare Turnstile is an alternative to Recaptcha that avoids user interaction. More information: https://developers.cloudflare.com/turnstile/ --- .../recaptcha/request_controller.rb | 32 +++++++++++++++++-- .../recaptcha/app/helpers/recaptcha_helper.rb | 4 ++- .../app/views/recaptcha/admin/show.html.erb | 3 +- .../views/recaptcha/request/perform.html.erb | 21 ++++++++++++ modules/recaptcha/config/locales/en.yml | 6 +++- .../recaptcha/lib/open_project/recaptcha.rb | 1 + .../open_project/recaptcha/configuration.rb | 8 +++++ .../lib/open_project/recaptcha/engine.rb | 7 ++++ 8 files changed, 77 insertions(+), 5 deletions(-) diff --git a/modules/recaptcha/app/controllers/recaptcha/request_controller.rb b/modules/recaptcha/app/controllers/recaptcha/request_controller.rb index 2d48aa38032e..0cac9aced789 100644 --- a/modules/recaptcha/app/controllers/recaptcha/request_controller.rb +++ b/modules/recaptcha/app/controllers/recaptcha/request_controller.rb @@ -1,4 +1,5 @@ require "recaptcha" +require "net/http" module ::Recaptcha class RequestController < ApplicationController @@ -28,13 +29,15 @@ class RequestController < ApplicationController def perform if OpenProject::Recaptcha::Configuration.use_hcaptcha? use_content_security_policy_named_append(:hcaptcha) - else + elsif OpenProject::Recaptcha::Configuration.use_turnstile? + use_content_security_policy_named_append(:turnstile) + elsif OpenProject::Recaptcha::Configuration.use_recaptcha? use_content_security_policy_named_append(:recaptcha) end end def verify - if valid_recaptcha? + if valid_turnstile? || valid_recaptcha? save_recaptcha_verification_success! complete_stage_redirect else @@ -70,6 +73,8 @@ def recaptcha_version 2 when ::OpenProject::Recaptcha::TYPE_V3 3 + when ::OpenProject::Recaptcha::TYPE_TURNSTILE + 99 # Turnstile is not comparable/compatible with recaptcha end end @@ -84,6 +89,29 @@ def valid_recaptcha? verify_recaptcha call_args end + ## + # + def valid_turnstile? + return false unless OpenProject::Recaptcha::Configuration.use_turnstile? + token = params["turnstile-response"] + return false if token.blank? + + data = { + "response" => token, + "remoteip" => request.remote_ip, + "secret" => recaptcha_settings["secret_key"], + } + + data_encoded = URI.encode_www_form(data) + + response = Net::HTTP.post_form( + URI("https://challenges.cloudflare.com/turnstile/v0/siteverify"), + data + ) + response = JSON.parse(response.body) + response["success"] + end + ## # fail the recaptcha def fail_recaptcha(msg) diff --git a/modules/recaptcha/app/helpers/recaptcha_helper.rb b/modules/recaptcha/app/helpers/recaptcha_helper.rb index 662db0a65079..c2b5d96075d1 100644 --- a/modules/recaptcha/app/helpers/recaptcha_helper.rb +++ b/modules/recaptcha/app/helpers/recaptcha_helper.rb @@ -4,7 +4,9 @@ def recaptcha_available_options [I18n.t("recaptcha.settings.type_disabled"), ::OpenProject::Recaptcha::TYPE_DISABLED], [I18n.t("recaptcha.settings.type_v2"), ::OpenProject::Recaptcha::TYPE_V2], [I18n.t("recaptcha.settings.type_v3"), ::OpenProject::Recaptcha::TYPE_V3], - [I18n.t("recaptcha.settings.type_hcaptcha"), ::OpenProject::Recaptcha::TYPE_HCAPTCHA] + [I18n.t("recaptcha.settings.type_hcaptcha"), ::OpenProject::Recaptcha::TYPE_HCAPTCHA], + [I18n.t("recaptcha.settings.type_turnstile"), ::OpenProject::Recaptcha::TYPE_TURNSTILE] + ] end diff --git a/modules/recaptcha/app/views/recaptcha/admin/show.html.erb b/modules/recaptcha/app/views/recaptcha/admin/show.html.erb index c67374539fb0..2fc2d3f31208 100644 --- a/modules/recaptcha/app/views/recaptcha/admin/show.html.erb +++ b/modules/recaptcha/app/views/recaptcha/admin/show.html.erb @@ -18,7 +18,8 @@
<%= I18n.t('recaptcha.settings.recaptcha_description_html', hcaptcha_link: link_to('https://docs.hcaptcha.com/switch/', 'https://docs.hcaptcha.com/switch/', target: '_blan'), - recaptcha_link: link_to('https://www.google.com/recaptcha', 'https://www.google.com/recaptcha', target: '_blank')).html_safe %> + recaptcha_link: link_to('https://www.google.com/recaptcha', 'https://www.google.com/recaptcha', target: '_blank'), + turnstile_link: link_to('https://developers.cloudflare.com/turnstile/', 'https://developers.cloudflare.com/turnstile/', target: '_blank')).html_safe %>
diff --git a/modules/recaptcha/app/views/recaptcha/request/perform.html.erb b/modules/recaptcha/app/views/recaptcha/request/perform.html.erb index 4274d6bf8ec3..b600f8970f50 100644 --- a/modules/recaptcha/app/views/recaptcha/request/perform.html.erb +++ b/modules/recaptcha/app/views/recaptcha/request/perform.html.erb @@ -41,6 +41,27 @@ document.getElementById('submit_captcha').submit(); } <% end %> + <% elsif recaptcha_settings['captcha_type'] == ::OpenProject::Recaptcha::TYPE_TURNSTILE %> + <% input_name = "turnstile-response" %> + + + +
+ <%= nonced_javascript_tag do %> + function submitTurnstileForm(token) { + var input = document.getElementsByName('<%= input_name %>')[0]; + + input.value = token; + document.getElementById('submit_captcha').submit(); + } + + window.onloadTurnstileCallback = function () { + turnstile.render('#turnstile-container', { + sitekey: '<%= recaptcha_settings['website_key'] %>', + callback: submitTurnstileForm, + }); + }; + <% end %> <% end %> <% end %>
diff --git a/modules/recaptcha/config/locales/en.yml b/modules/recaptcha/config/locales/en.yml index e3ae8e19db20..aaf062087e44 100644 --- a/modules/recaptcha/config/locales/en.yml +++ b/modules/recaptcha/config/locales/en.yml @@ -9,7 +9,7 @@ en: verify_account: "Verify your account" error_captcha: "Your account could not be verified. Please contact an administrator." settings: - website_key: 'Website key' + website_key: 'Website key (May also be called "Site key")' response_limit: 'Response limit for HCaptcha' response_limit_text: 'The maximum number of characters to treat the HCaptcha response as valid.' website_key_text: 'Enter the website key you created on the reCAPTCHA admin console for this domain.' @@ -20,6 +20,7 @@ en: type_v2: 'reCAPTCHA v2' type_v3: 'reCAPTCHA v3' type_hcaptcha: 'HCaptcha' + type_turnstile: 'Cloudflare Turnstile™' recaptcha_description_html: > reCAPTCHA is a free service by Google that can be enabled for your OpenProject instance. If enabled, a captcha form will be rendered upon login for all users that have not verified a captcha yet. @@ -29,3 +30,6 @@ en:
HCaptcha is a Google-free alternative that you can use if you do not want to use reCAPTCHA. See this link for more information: %{hcaptcha_link} +
+ Cloudflare Turnstile™ is another alternative that is more convenient for users while still providing the same level of security. + See this link for more information: %{turnstile_link} diff --git a/modules/recaptcha/lib/open_project/recaptcha.rb b/modules/recaptcha/lib/open_project/recaptcha.rb index d7933a433069..ab9bd6f1297e 100644 --- a/modules/recaptcha/lib/open_project/recaptcha.rb +++ b/modules/recaptcha/lib/open_project/recaptcha.rb @@ -4,6 +4,7 @@ module Recaptcha TYPE_V2 ||= "v2" TYPE_V3 ||= "v3" TYPE_HCAPTCHA ||= "hcaptcha" + TYPE_TURNSTILE ||= "turnstile" require "open_project/recaptcha/engine" require "open_project/recaptcha/configuration" diff --git a/modules/recaptcha/lib/open_project/recaptcha/configuration.rb b/modules/recaptcha/lib/open_project/recaptcha/configuration.rb index 205c9ee39362..4e3cd05e9c17 100644 --- a/modules/recaptcha/lib/open_project/recaptcha/configuration.rb +++ b/modules/recaptcha/lib/open_project/recaptcha/configuration.rb @@ -13,6 +13,14 @@ def use_hcaptcha? type == ::OpenProject::Recaptcha::TYPE_HCAPTCHA end + def use_turnstile? + type == ::OpenProject::Recaptcha::TYPE_TURNSTILE + end + + def use_recaptcha? + type == ::OpenProject::Recaptcha::TYPE_V2 || type == ::OpenProject::Recaptcha::TYPE_V3 + end + def type ::Setting.plugin_openproject_recaptcha["recaptcha_type"] end diff --git a/modules/recaptcha/lib/open_project/recaptcha/engine.rb b/modules/recaptcha/lib/open_project/recaptcha/engine.rb index a81b8d846615..163b8a417381 100644 --- a/modules/recaptcha/lib/open_project/recaptcha/engine.rb +++ b/modules/recaptcha/lib/open_project/recaptcha/engine.rb @@ -41,6 +41,13 @@ class Engine < ::Rails::Engine keys.index_with value end + SecureHeaders::Configuration.named_append(:turnstile) do + value = %w(https://challenges.cloudflare.com) + keys = %i(frame_src script_src style_src connect_src) + + keys.index_with value + end + OpenProject::Authentication::Stage.register( :recaptcha, nil, From 1b4eba69eb147cb507d091f36c694da84d758a37 Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Fri, 9 Aug 2024 12:28:04 +0300 Subject: [PATCH 02/91] [#56922] Trigger a Login Dialog when the user has no oauth access grant https://community.openproject.org/work_packages/56922 --- ...auth_access_grant_nudge_modal_component.rb | 2 +- ...ccess_grant_nudge_modal_component.html.erb | 94 ++++++++++++++++ ...auth_access_grant_nudge_modal_component.rb | 101 ++++++++++++++++++ .../storages/project_storages_controller.rb | 35 +++++- modules/storages/config/routes.rb | 1 + 5 files changed, 230 insertions(+), 3 deletions(-) create mode 100644 modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.html.erb create mode 100644 modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.rb diff --git a/modules/storages/app/components/storages/admin/oauth_access_grant_nudge_modal_component.rb b/modules/storages/app/components/storages/admin/oauth_access_grant_nudge_modal_component.rb index 38b3627b1755..3a8670755779 100644 --- a/modules/storages/app/components/storages/admin/oauth_access_grant_nudge_modal_component.rb +++ b/modules/storages/app/components/storages/admin/oauth_access_grant_nudge_modal_component.rb @@ -29,7 +29,7 @@ #++ # module Storages::Admin - class OAuthAccessGrantNudgeModalComponent < ApplicationComponent # rubocop:disable OpenProject/AddPreviewForViewComponent + class OAuthAccessGrantNudgeModalComponent < ApplicationComponent options dialog_id: "storages--oauth-grant-nudge-modal-component", dialog_body_id: "storages--oauth-grant-nudge-modal-body-component", authorized: false diff --git a/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.html.erb b/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.html.erb new file mode 100644 index 000000000000..266dbbcb06c1 --- /dev/null +++ b/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.html.erb @@ -0,0 +1,94 @@ +<%= + component_wrapper do + render( + Primer::Alpha::Dialog.new( + id: dialog_id, + title:, + data: { + 'application-target': 'dynamic', + controller: 'storages--oauth-access-grant-nudge-modal', + 'storages--oauth-access-grant-nudge-modal-close-button-label-value': I18n.t('button_close'), + 'storages--oauth-access-grant-nudge-modal-loading-screen-reader-message-value': waiting_title, + }, + test_selector: 'oauth-access-grant-nudge-modal' + ) + ) do |dialog| + dialog.with_header( + show_divider: false, + role: :alert, + aria: { live: :assertive }, + data: { + 'storages--oauth-access-grant-nudge-modal-target': 'header' + }, + visually_hide_title: authorized + ) + + dialog.with_body( + id: dialog_body_id, + test_selector: 'oauth-access-grant-nudge-modal-body', + aria: { hidden: authorized } + ) do + concat( + render( + Primer::Beta::Text.new( + display: :none, + data: { + 'storages--oauth-access-grant-nudge-modal-target': 'loadingIndicator' + } + ) + ) { render(Storages::OpenProjectStorageModalComponent::Body.new(:waiting, waiting_title:)) } + ) + concat( + render( + Primer::Beta::Text.new( + data: { + 'storages--oauth-access-grant-nudge-modal-target': 'requestAccessBody' + } + ) + ) { body_text } + ) + end + + dialog.with_footer(show_divider: false) do + concat( + render( + Primer::Beta::Button.new( + scheme: :default, + size: :medium, + data: { + 'close-dialog-id': dialog_id, + 'storages--oauth-access-grant-nudge-modal-target': 'closeButton' + } + ) + ) { cancel_button_text } + ) + + unless authorized + concat( + primer_form_with( + model: @project_storage, + url: confirm_button_url, + method: :get, + data: { + 'storages--oauth-access-grant-nudge-modal-target': 'requestAccessForm' + } + ) do |_form| + render( + Primer::Beta::Button.new( + scheme: :primary, + size: :medium, + type: :submit, + aria: { label: confirm_button_aria_label }, + data: { + 'storages--oauth-access-grant-nudge-modal-target': 'requestAccessButton', + action: 'click->storages--oauth-access-grant-nudge-modal#requestAccess' + } + ) + ) { confirm_button_text } + end + ) + end + end + end + end +%> diff --git a/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.rb b/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.rb new file mode 100644 index 000000000000..fb15a096e3d0 --- /dev/null +++ b/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ +# +module Storages::Admin::Storages + class OAuthAccessGrantNudgeModalComponent < ApplicationComponent + include OpTurbo::Streamable + + options dialog_id: "storages--oauth-grant-nudge-modal-component", + dialog_body_id: "storages--oauth-grant-nudge-modal-body-component", + authorized: false + + attr_reader :storage + + def initialize(storage:, **) + @storage = find_storage(storage) + super(@storage, **) + end + + def render? + @storage.present? + end + + private + + def confirm_button_text + I18n.t("storages.oauth_grant_nudge_modal.confirm_button_label") + end + + def title + if authorized + I18n.t("storages.oauth_grant_nudge_modal.access_granted_screen_reader", storage: storage.name) + else + I18n.t("storages.oauth_grant_nudge_modal.title") + end + end + + def waiting_title + I18n.t("storages.oauth_grant_nudge_modal.requesting_access_to", storage: storage.name) + end + + def cancel_button_text + if authorized + I18n.t("button_close") + else + I18n.t("storages.oauth_grant_nudge_modal.cancel_button_label") + end + end + + def body_text + if authorized + success_title = I18n.t("storages.oauth_grant_nudge_modal.access_granted") + success_subtitle = I18n.t("storages.oauth_grant_nudge_modal.storage_ready", storage: storage.name) + concat(render(::Storages::OpenProjectStorageModalComponent::Body.new(:success, success_subtitle:, success_title:))) + else + I18n.t("storages.oauth_grant_nudge_modal.body", storage: storage.name) + end + end + + def confirm_button_aria_label + I18n.t("storages.oauth_grant_nudge_modal.confirm_button_aria_label", storage: storage.name) + end + + def confirm_button_url + url_helpers.oauth_access_grant_admin_settings_storage_project_storages_path(storage) + end + + def find_storage(storage_record_or_id) + return if storage_record_or_id.blank? + return storage_record_or_id if storage_record_or_id.is_a?(::Storages::Storage) + + ::Storages::Storage.find_by(id: storage_record_or_id) + end + end +end diff --git a/modules/storages/app/controllers/storages/admin/storages/project_storages_controller.rb b/modules/storages/app/controllers/storages/admin/storages/project_storages_controller.rb index 2bc95d577124..fac0e2de8cab 100644 --- a/modules/storages/app/controllers/storages/admin/storages/project_storages_controller.rb +++ b/modules/storages/app/controllers/storages/admin/storages/project_storages_controller.rb @@ -52,11 +52,28 @@ class Storages::Admin::Storages::ProjectStoragesController < ApplicationControll def index; end def new - respond_with_dialog Storages::Admin::Storages::ProjectsStorageModalComponent.new( - project_storage: @project_storage, last_project_folders: {} + respond_with_dialog( + if storage_oauth_access_granted? + ::Storages::Admin::Storages::ProjectsStorageModalComponent.new( + project_storage: @project_storage, last_project_folders: {} + ) + else + ::Storages::Admin::Storages::OAuthAccessGrantNudgeModalComponent.new(storage: @storage) + end ) end + def oauth_access_grant + nonce = SecureRandom.uuid + cookies["oauth_state_#{nonce}"] = { + value: { href: admin_settings_storage_project_storages_url(@storage), + storageId: @storage.id }.to_json, + expires: 1.hour + } + session[:oauth_callback_flash_modal] = oauth_access_grant_nudge_modal(authorized: true) + redirect_to(storage.oauth_configuration.authorization_uri(state: nonce), allow_other_host: true) + end + def create # rubocop:disable Metrics/AbcSize create_service = ::Storages::ProjectStorages::BulkCreateService .new(user: current_user, projects: @projects, storage: @storage, @@ -188,6 +205,10 @@ def initialize_project_storage .result end + def storage_oauth_access_granted? + OAuthClientToken.exists?(user: current_user, oauth_client: @storage.oauth_client) + end + def include_sub_projects? ActiveRecord::Type::Boolean.new.cast(params.to_unsafe_h[:storages_project_storage][:include_sub_projects]) end @@ -204,4 +225,14 @@ def ensure_storage_configured! respond_with_turbo_streams false end + + def oauth_access_grant_nudge_modal(authorized: false) + { + type: "Storages::Admin::Storages::OAuthAccessGrantNudgeModalComponent", + parameters: { + storage: @storage.id, + authorized: + } + } + end end diff --git a/modules/storages/config/routes.rb b/modules/storages/config/routes.rb index b26381b72720..bbc6a37a5ef6 100644 --- a/modules/storages/config/routes.rb +++ b/modules/storages/config/routes.rb @@ -49,6 +49,7 @@ controller: "/storages/admin/storages/project_storages", only: %i[index new create edit update destroy] do get :destroy_confirmation_dialog, on: :member + get :oauth_access_grant, on: :collection end end From b400bd12416062ece17c2b3311af992d56b0653b Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Tue, 13 Aug 2024 18:31:05 +0300 Subject: [PATCH 03/91] chore[Op#56922]: Split authorized state from nudge modal --- .../controllers/dynamic/auto-show-dialog.ts | 37 +++++++ ...ccess_grant_nudge_modal_component.html.erb | 54 +++++----- ...auth_access_grant_nudge_modal_component.rb | 98 +++++++++---------- ...th_access_granted_modal_component.html.erb | 36 +++++++ .../oauth_access_granted_modal_component.rb | 80 +++++++++++++++ .../storages/project_storages_controller.rb | 13 +-- 6 files changed, 227 insertions(+), 91 deletions(-) create mode 100644 frontend/src/stimulus/controllers/dynamic/auto-show-dialog.ts create mode 100644 modules/storages/app/components/storages/admin/storages/oauth_access_granted_modal_component.html.erb create mode 100644 modules/storages/app/components/storages/admin/storages/oauth_access_granted_modal_component.rb diff --git a/frontend/src/stimulus/controllers/dynamic/auto-show-dialog.ts b/frontend/src/stimulus/controllers/dynamic/auto-show-dialog.ts new file mode 100644 index 000000000000..db2a0aabe551 --- /dev/null +++ b/frontend/src/stimulus/controllers/dynamic/auto-show-dialog.ts @@ -0,0 +1,37 @@ +/* + * -- copyright + * OpenProject is an open source project management software. + * Copyright (C) the OpenProject GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 3. + * + * OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: + * Copyright (C) 2006-2013 Jean-Philippe Lang + * Copyright (C) 2010-2013 the ChiliProject Team + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * See COPYRIGHT and LICENSE files for more details. + * ++ + */ + +import { Controller } from '@hotwired/stimulus'; + +export default class AutoShowDialogController extends Controller { + connect() { + this.element.showModal(); + } +} diff --git a/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.html.erb b/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.html.erb index 266dbbcb06c1..3c71ea2eb3f3 100644 --- a/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.html.erb +++ b/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.html.erb @@ -19,14 +19,12 @@ aria: { live: :assertive }, data: { 'storages--oauth-access-grant-nudge-modal-target': 'header' - }, - visually_hide_title: authorized + } ) dialog.with_body( id: dialog_body_id, - test_selector: 'oauth-access-grant-nudge-modal-body', - aria: { hidden: authorized } + test_selector: 'oauth-access-grant-nudge-modal-body' ) do concat( render( @@ -63,31 +61,29 @@ ) { cancel_button_text } ) - unless authorized - concat( - primer_form_with( - model: @project_storage, - url: confirm_button_url, - method: :get, - data: { - 'storages--oauth-access-grant-nudge-modal-target': 'requestAccessForm' - } - ) do |_form| - render( - Primer::Beta::Button.new( - scheme: :primary, - size: :medium, - type: :submit, - aria: { label: confirm_button_aria_label }, - data: { - 'storages--oauth-access-grant-nudge-modal-target': 'requestAccessButton', - action: 'click->storages--oauth-access-grant-nudge-modal#requestAccess' - } - ) - ) { confirm_button_text } - end - ) - end + concat( + primer_form_with( + model:, + url: confirm_button_url, + method: :get, + data: { + 'storages--oauth-access-grant-nudge-modal-target': 'requestAccessForm' + } + ) do |_form| + render( + Primer::Beta::Button.new( + scheme: :primary, + size: :medium, + type: :submit, + aria: { label: confirm_button_aria_label }, + data: { + 'storages--oauth-access-grant-nudge-modal-target': 'requestAccessButton', + action: 'storages--oauth-access-grant-nudge-modal#requestAccess' + } + ) + ) { confirm_button_text } + end + ) end end end diff --git a/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.rb b/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.rb index fb15a096e3d0..8dd42e5eb213 100644 --- a/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.rb +++ b/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.rb @@ -28,74 +28,64 @@ # See COPYRIGHT and LICENSE files for more details. #++ # -module Storages::Admin::Storages - class OAuthAccessGrantNudgeModalComponent < ApplicationComponent - include OpTurbo::Streamable - options dialog_id: "storages--oauth-grant-nudge-modal-component", - dialog_body_id: "storages--oauth-grant-nudge-modal-body-component", - authorized: false +module Storages + module Admin + module Storages + class OAuthAccessGrantNudgeModalComponent < ApplicationComponent + include OpTurbo::Streamable - attr_reader :storage + options dialog_id: "storages--oauth-grant-nudge-modal-component", + dialog_body_id: "storages--oauth-grant-nudge-modal-body-component" - def initialize(storage:, **) - @storage = find_storage(storage) - super(@storage, **) - end + def initialize(storage:, **) + @storage = find_storage(storage) + super(@storage, **) + end - def render? - @storage.present? - end + def render? + @storage.present? + end - private + private - def confirm_button_text - I18n.t("storages.oauth_grant_nudge_modal.confirm_button_label") - end + attr_reader :storage - def title - if authorized - I18n.t("storages.oauth_grant_nudge_modal.access_granted_screen_reader", storage: storage.name) - else - I18n.t("storages.oauth_grant_nudge_modal.title") - end - end + def confirm_button_text + I18n.t("storages.oauth_grant_nudge_modal.confirm_button_label") + end - def waiting_title - I18n.t("storages.oauth_grant_nudge_modal.requesting_access_to", storage: storage.name) - end + def title + I18n.t("storages.oauth_grant_nudge_modal.title") + end - def cancel_button_text - if authorized - I18n.t("button_close") - else - I18n.t("storages.oauth_grant_nudge_modal.cancel_button_label") - end - end + def waiting_title + I18n.t("storages.oauth_grant_nudge_modal.requesting_access_to", storage: storage.name) + end - def body_text - if authorized - success_title = I18n.t("storages.oauth_grant_nudge_modal.access_granted") - success_subtitle = I18n.t("storages.oauth_grant_nudge_modal.storage_ready", storage: storage.name) - concat(render(::Storages::OpenProjectStorageModalComponent::Body.new(:success, success_subtitle:, success_title:))) - else - I18n.t("storages.oauth_grant_nudge_modal.body", storage: storage.name) - end - end + def cancel_button_text + I18n.t("storages.oauth_grant_nudge_modal.cancel_button_label") + end - def confirm_button_aria_label - I18n.t("storages.oauth_grant_nudge_modal.confirm_button_aria_label", storage: storage.name) - end + def body_text + I18n.t("storages.oauth_grant_nudge_modal.body", storage: storage.name) + end - def confirm_button_url - url_helpers.oauth_access_grant_admin_settings_storage_project_storages_path(storage) - end + def confirm_button_aria_label + I18n.t("storages.oauth_grant_nudge_modal.confirm_button_aria_label", storage: storage.name) + end + + def confirm_button_url + url_helpers.oauth_access_grant_admin_settings_storage_project_storages_path(storage) + end - def find_storage(storage_record_or_id) - return if storage_record_or_id.blank? - return storage_record_or_id if storage_record_or_id.is_a?(::Storages::Storage) + def find_storage(storage_record_or_id) + return if storage_record_or_id.blank? + return storage_record_or_id if storage_record_or_id.is_a?(::Storages::Storage) - ::Storages::Storage.find_by(id: storage_record_or_id) + ::Storages::Storage.find_by(id: storage_record_or_id) + end + end end end end diff --git a/modules/storages/app/components/storages/admin/storages/oauth_access_granted_modal_component.html.erb b/modules/storages/app/components/storages/admin/storages/oauth_access_granted_modal_component.html.erb new file mode 100644 index 000000000000..cbeaa3e073ce --- /dev/null +++ b/modules/storages/app/components/storages/admin/storages/oauth_access_granted_modal_component.html.erb @@ -0,0 +1,36 @@ +<%= + component_wrapper do + render( + Primer::Alpha::Dialog.new( + id: dialog_id, + title:, + test_selector: 'oauth-access-granted-modal' + ) + ) do |dialog| + dialog.with_header( + show_divider: false, + role: :alert, + aria: { live: :assertive }, + visually_hide_title: true + ) + + dialog.with_body( + id: dialog_body_id, + test_selector: 'oauth-access-granted-modal-body', + aria: { hidden: true } + ) do + render(Primer::Beta::Text.new) { body_text } + end + + dialog.with_footer(show_divider: false) do + render( + Primer::Beta::Button.new( + scheme: :default, + size: :medium, + data: { 'close-dialog-id': dialog_id } + ) + ) { cancel_button_text } + end + end + end +%> diff --git a/modules/storages/app/components/storages/admin/storages/oauth_access_granted_modal_component.rb b/modules/storages/app/components/storages/admin/storages/oauth_access_granted_modal_component.rb new file mode 100644 index 000000000000..c5ff9dc78cf2 --- /dev/null +++ b/modules/storages/app/components/storages/admin/storages/oauth_access_granted_modal_component.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ +# +module Storages + module Admin + module Storages + class OAuthAccessGrantedModalComponent < ApplicationComponent + include OpTurbo::Streamable + + def initialize(storage:, **) + @storage = find_storage(storage) + super(@storage, **) + end + + def render? + storage.present? && OAuthClientToken.exists?(user: User.current, oauth_client: storage.oauth_client) + end + + private + + attr_reader :storage + + def dialog_id = "#{wrapper_key}-dialog-id" + def dialog_body_id = "#{wrapper_key}-dialog-body-id" + + def title + I18n.t("storages.oauth_grant_nudge_modal.access_granted_screen_reader", storage: storage.name) + end + + def waiting_title + I18n.t("storages.oauth_grant_nudge_modal.requesting_access_to", storage: storage.name) + end + + def cancel_button_text + I18n.t("button_close") + end + + def body_text + success_title = I18n.t("storages.oauth_grant_nudge_modal.access_granted") + success_subtitle = I18n.t("storages.oauth_grant_nudge_modal.storage_ready", storage: storage.name) + concat(render(::Storages::OpenProjectStorageModalComponent::Body.new(:success, success_subtitle:, success_title:))) + end + + def find_storage(storage_record_or_id) + return if storage_record_or_id.blank? + return storage_record_or_id if storage_record_or_id.is_a?(::Storages::Storage) + + ::Storages::Storage.find_by(id: storage_record_or_id) + end + end + end + end +end diff --git a/modules/storages/app/controllers/storages/admin/storages/project_storages_controller.rb b/modules/storages/app/controllers/storages/admin/storages/project_storages_controller.rb index fac0e2de8cab..4129b2164ae1 100644 --- a/modules/storages/app/controllers/storages/admin/storages/project_storages_controller.rb +++ b/modules/storages/app/controllers/storages/admin/storages/project_storages_controller.rb @@ -70,8 +70,8 @@ def oauth_access_grant storageId: @storage.id }.to_json, expires: 1.hour } - session[:oauth_callback_flash_modal] = oauth_access_grant_nudge_modal(authorized: true) - redirect_to(storage.oauth_configuration.authorization_uri(state: nonce), allow_other_host: true) + session[:oauth_callback_flash_modal] = oauth_access_granted_modal_params + redirect_to(@storage.oauth_configuration.authorization_uri(state: nonce), allow_other_host: true) end def create # rubocop:disable Metrics/AbcSize @@ -226,13 +226,10 @@ def ensure_storage_configured! false end - def oauth_access_grant_nudge_modal(authorized: false) + def oauth_access_granted_modal_params { - type: "Storages::Admin::Storages::OAuthAccessGrantNudgeModalComponent", - parameters: { - storage: @storage.id, - authorized: - } + type: "Storages::Admin::Storages::OAuthAccessGrantedModalComponent", + parameters: { storage: @storage.id } } end end From 9d2c7caf532071f0cec82df9a745c1db674c1c2e Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Wed, 14 Aug 2024 16:05:22 +0300 Subject: [PATCH 04/91] chore[Op#56922]: split out project vs storage oauth components --- ...alog.ts => auto-show-dialog.controller.ts} | 0 ...ccess_grant_nudge_modal_component.html.erb | 92 ---------------- ...auth_access_grant_nudge_modal_component.rb | 102 ------------------ ...th_access_granted_modal_component.html.erb | 10 +- ...auth_access_grant_nudge_modal_component.rb | 59 ++++++++++ .../admin/project_storages_controller.rb | 20 ++-- .../storages/project_storages_controller.rb | 2 +- ...access_grant_nudge_modal_component_spec.rb | 55 ++++++++++ ...th_access_granted_modal_component_spec.rb} | 68 ++++++------ ...access_grant_nudge_modal_component_spec.rb | 56 ++++++++++ 10 files changed, 227 insertions(+), 237 deletions(-) rename frontend/src/stimulus/controllers/dynamic/{auto-show-dialog.ts => auto-show-dialog.controller.ts} (100%) delete mode 100644 modules/storages/app/components/storages/admin/oauth_access_grant_nudge_modal_component.html.erb delete mode 100644 modules/storages/app/components/storages/admin/oauth_access_grant_nudge_modal_component.rb create mode 100644 modules/storages/app/components/storages/project_storages/oauth_access_grant_nudge_modal_component.rb create mode 100644 modules/storages/spec/components/storages/admin/storages/oauth_access_grant_nudge_modal_component_spec.rb rename modules/storages/spec/components/storages/admin/{oauth_access_grant_nudge_modal_component_spec.rb => storages/oauth_access_granted_modal_component_spec.rb} (53%) create mode 100644 modules/storages/spec/components/storages/project_storages/oauth_access_grant_nudge_modal_component_spec.rb diff --git a/frontend/src/stimulus/controllers/dynamic/auto-show-dialog.ts b/frontend/src/stimulus/controllers/dynamic/auto-show-dialog.controller.ts similarity index 100% rename from frontend/src/stimulus/controllers/dynamic/auto-show-dialog.ts rename to frontend/src/stimulus/controllers/dynamic/auto-show-dialog.controller.ts diff --git a/modules/storages/app/components/storages/admin/oauth_access_grant_nudge_modal_component.html.erb b/modules/storages/app/components/storages/admin/oauth_access_grant_nudge_modal_component.html.erb deleted file mode 100644 index af60d43a12de..000000000000 --- a/modules/storages/app/components/storages/admin/oauth_access_grant_nudge_modal_component.html.erb +++ /dev/null @@ -1,92 +0,0 @@ -<%= - render( - Primer::Alpha::Dialog.new( - id: dialog_id, - title:, - data: { - 'application-target': 'dynamic', - controller: 'storages--oauth-access-grant-nudge-modal', - 'storages--oauth-access-grant-nudge-modal-close-button-label-value': I18n.t('button_close'), - 'storages--oauth-access-grant-nudge-modal-loading-screen-reader-message-value': waiting_title, - }, - test_selector: 'oauth-access-grant-nudge-modal' - ) - ) do |dialog| - dialog.with_header( - show_divider: false, - role: :alert, - aria: { live: :assertive }, - data: { - 'storages--oauth-access-grant-nudge-modal-target': 'header' - }, - visually_hide_title: authorized - ) - - dialog.with_body( - id: dialog_body_id, - test_selector: 'oauth-access-grant-nudge-modal-body', - aria: { hidden: authorized } - ) do - concat( - render( - Primer::Beta::Text.new( - display: :none, - data: { - 'storages--oauth-access-grant-nudge-modal-target': 'loadingIndicator' - } - ) - ) { render(Storages::OpenProjectStorageModalComponent::Body.new(:waiting, waiting_title:)) } - ) - concat( - render( - Primer::Beta::Text.new( - data: { - 'storages--oauth-access-grant-nudge-modal-target': 'requestAccessBody' - } - ) - ) { body_text } - ) - end - - dialog.with_footer(show_divider: false) do - concat( - render( - Primer::Beta::Button.new( - scheme: :default, - size: :medium, - data: { - 'close-dialog-id': dialog_id, - 'storages--oauth-access-grant-nudge-modal-target': 'closeButton' - } - ) - ) { cancel_button_text } - ) - - unless authorized - concat( - primer_form_with( - model: @project_storage, - url: confirm_button_url, - method: :get, - data: { - 'storages--oauth-access-grant-nudge-modal-target': 'requestAccessForm' - } - ) do |_form| - render( - Primer::Beta::Button.new( - scheme: :primary, - size: :medium, - type: :submit, - aria: { label: confirm_button_aria_label }, - data: { - 'storages--oauth-access-grant-nudge-modal-target': 'requestAccessButton', - action: 'click->storages--oauth-access-grant-nudge-modal#requestAccess' - } - ) - ) { confirm_button_text } - end - ) - end - end - end -%> diff --git a/modules/storages/app/components/storages/admin/oauth_access_grant_nudge_modal_component.rb b/modules/storages/app/components/storages/admin/oauth_access_grant_nudge_modal_component.rb deleted file mode 100644 index 3a8670755779..000000000000 --- a/modules/storages/app/components/storages/admin/oauth_access_grant_nudge_modal_component.rb +++ /dev/null @@ -1,102 +0,0 @@ -# frozen_string_literal: true - -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) the OpenProject GmbH -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License version 3. -# -# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -# Copyright (C) 2006-2013 Jean-Philippe Lang -# Copyright (C) 2010-2013 the ChiliProject Team -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# See COPYRIGHT and LICENSE files for more details. -#++ -# -module Storages::Admin - class OAuthAccessGrantNudgeModalComponent < ApplicationComponent - options dialog_id: "storages--oauth-grant-nudge-modal-component", - dialog_body_id: "storages--oauth-grant-nudge-modal-body-component", - authorized: false - - attr_reader :project_storage - - def initialize(project_storage:, **) - @project_storage = find_project_storage(project_storage) - super(@project_storage, **) - end - - def render? - @project_storage.present? - end - - private - - def confirm_button_text - I18n.t("storages.oauth_grant_nudge_modal.confirm_button_label") - end - - def title - if authorized - I18n.t("storages.oauth_grant_nudge_modal.access_granted_screen_reader", storage: project_storage.storage.name) - else - I18n.t("storages.oauth_grant_nudge_modal.title") - end - end - - def waiting_title - I18n.t("storages.oauth_grant_nudge_modal.requesting_access_to", storage: project_storage.storage.name) - end - - def cancel_button_text - if authorized - I18n.t("button_close") - else - I18n.t("storages.oauth_grant_nudge_modal.cancel_button_label") - end - end - - def body_text - if authorized - success_title = I18n.t("storages.oauth_grant_nudge_modal.access_granted") - success_subtitle = I18n.t("storages.oauth_grant_nudge_modal.storage_ready", storage: project_storage.storage.name) - concat(render(::Storages::OpenProjectStorageModalComponent::Body.new(:success, success_subtitle:, success_title:))) - else - I18n.t("storages.oauth_grant_nudge_modal.body", storage: project_storage.storage.name) - end - end - - def confirm_button_aria_label - I18n.t("storages.oauth_grant_nudge_modal.confirm_button_aria_label", storage: project_storage.storage.name) - end - - def confirm_button_url - options[:confirm_button_url] || url_helpers.oauth_access_grant_project_settings_project_storage_path( - project_id: project_storage.project.id, - id: project_storage - ) - end - - def find_project_storage(project_storage_record_or_id) - return if project_storage_record_or_id.blank? - return project_storage_record_or_id if project_storage_record_or_id.is_a?(::Storages::ProjectStorage) - - ::Storages::ProjectStorage.find_by(id: project_storage_record_or_id) - end - end -end diff --git a/modules/storages/app/components/storages/admin/storages/oauth_access_granted_modal_component.html.erb b/modules/storages/app/components/storages/admin/storages/oauth_access_granted_modal_component.html.erb index cbeaa3e073ce..8530faeb50bc 100644 --- a/modules/storages/app/components/storages/admin/storages/oauth_access_granted_modal_component.html.erb +++ b/modules/storages/app/components/storages/admin/storages/oauth_access_granted_modal_component.html.erb @@ -4,7 +4,11 @@ Primer::Alpha::Dialog.new( id: dialog_id, title:, - test_selector: 'oauth-access-granted-modal' + data: { + "application-target": "dynamic", + controller: "auto-show-dialog", + }, + test_selector: "oauth-access-granted-modal" ) ) do |dialog| dialog.with_header( @@ -16,7 +20,7 @@ dialog.with_body( id: dialog_body_id, - test_selector: 'oauth-access-granted-modal-body', + test_selector: "oauth-access-granted-modal-body", aria: { hidden: true } ) do render(Primer::Beta::Text.new) { body_text } @@ -27,7 +31,7 @@ Primer::Beta::Button.new( scheme: :default, size: :medium, - data: { 'close-dialog-id': dialog_id } + data: { "close-dialog-id": dialog_id } ) ) { cancel_button_text } end diff --git a/modules/storages/app/components/storages/project_storages/oauth_access_grant_nudge_modal_component.rb b/modules/storages/app/components/storages/project_storages/oauth_access_grant_nudge_modal_component.rb new file mode 100644 index 000000000000..f6c3d9c4b5d7 --- /dev/null +++ b/modules/storages/app/components/storages/project_storages/oauth_access_grant_nudge_modal_component.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ +# +module Storages + module ProjectStorages + class OAuthAccessGrantNudgeModalComponent < ::Storages::Admin::Storages::OAuthAccessGrantNudgeModalComponent + def initialize(project_storage:, **) + @project_storage = find_project_storage(project_storage) + super(storage: @project_storage&.storage, **) + @model = @project_storage + end + + private + + attr_reader :project_storage + + def confirm_button_url + url_helpers.oauth_access_grant_project_settings_project_storage_path( + project_id: project_storage.project.id, + id: project_storage + ) + end + + def find_project_storage(project_storage_record_or_id) + return if project_storage_record_or_id.blank? + return project_storage_record_or_id if project_storage_record_or_id.is_a?(::Storages::ProjectStorage) + + ::Storages::ProjectStorage.find_by(id: project_storage_record_or_id) + end + end + end +end diff --git a/modules/storages/app/controllers/storages/admin/project_storages_controller.rb b/modules/storages/app/controllers/storages/admin/project_storages_controller.rb index a5275d9f0d20..931d3fdbd114 100644 --- a/modules/storages/app/controllers/storages/admin/project_storages_controller.rb +++ b/modules/storages/app/controllers/storages/admin/project_storages_controller.rb @@ -85,7 +85,7 @@ def oauth_access_grant # rubocop:disable Metrics/AbcSize storageId: @project_storage.storage_id }.to_json, expires: 1.hour } - session[:oauth_callback_flash_modal] = oauth_access_grant_nudge_modal(authorized: true) + session[:oauth_callback_flash_modal] = storage_oauth_access_granted_modal(storage:) redirect_to(storage.oauth_configuration.authorization_uri(state: nonce), allow_other_host: true) end end @@ -171,17 +171,21 @@ def storage_oauth_access_granted? def redirect_to_project_storages_path_with_nudge_modal redirect_to( external_file_storages_project_settings_project_storages_path, - flash: { modal: oauth_access_grant_nudge_modal } + flash: { modal: oauth_access_grant_nudge_modal(project_storage: @project_storage) } ) end - def oauth_access_grant_nudge_modal(authorized: false) + def oauth_access_grant_nudge_modal(project_storage:) { - type: "Storages::Admin::OAuthAccessGrantNudgeModalComponent", - parameters: { - project_storage: @project_storage.id, - authorized: - } + type: Storages::ProjectStorages::OAuthAccessGrantNudgeModalComponent.name, + parameters: { project_storage: project_storage.id } + } + end + + def storage_oauth_access_granted_modal(storage:) + { + type: Storages::Admin::Storages::OAuthAccessGrantedModalComponent.name, + parameters: { storage: storage.id } } end end diff --git a/modules/storages/app/controllers/storages/admin/storages/project_storages_controller.rb b/modules/storages/app/controllers/storages/admin/storages/project_storages_controller.rb index 4129b2164ae1..afc0e37ebf1f 100644 --- a/modules/storages/app/controllers/storages/admin/storages/project_storages_controller.rb +++ b/modules/storages/app/controllers/storages/admin/storages/project_storages_controller.rb @@ -228,7 +228,7 @@ def ensure_storage_configured! def oauth_access_granted_modal_params { - type: "Storages::Admin::Storages::OAuthAccessGrantedModalComponent", + type: Storages::Admin::Storages::OAuthAccessGrantedModalComponent.name, parameters: { storage: @storage.id } } end diff --git a/modules/storages/spec/components/storages/admin/storages/oauth_access_grant_nudge_modal_component_spec.rb b/modules/storages/spec/components/storages/admin/storages/oauth_access_grant_nudge_modal_component_spec.rb new file mode 100644 index 000000000000..73bd0dd34fa7 --- /dev/null +++ b/modules/storages/spec/components/storages/admin/storages/oauth_access_grant_nudge_modal_component_spec.rb @@ -0,0 +1,55 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ +# +require "spec_helper" +require_module_spec_helper + +RSpec.describe Storages::Admin::Storages::OAuthAccessGrantNudgeModalComponent, type: :component do # rubocop:disable RSpec/SpecFilePathFormat + let(:storage) { build_stubbed(:nextcloud_storage) } + + it "renders the nudge modal" do + render_inline(described_class.new(storage:)) + + expect(page).to have_css('[role="alert"]', text: "One more step...", aria: { live: :assertive }) + expect(page).to have_test_selector( + "oauth-access-grant-nudge-modal-body", + text: "To get access to the project folder you need to login to #{storage.name}." + ) + + expect(page).to have_button("I will do it later") + expect(page).to have_button("Login", aria: { label: "Login to #{storage.name}" }) + end + + context "with no storage" do + it "does not render" do + render_inline(described_class.new(storage: nil)) + + expect(page.text).to be_empty + end + end +end diff --git a/modules/storages/spec/components/storages/admin/oauth_access_grant_nudge_modal_component_spec.rb b/modules/storages/spec/components/storages/admin/storages/oauth_access_granted_modal_component_spec.rb similarity index 53% rename from modules/storages/spec/components/storages/admin/oauth_access_grant_nudge_modal_component_spec.rb rename to modules/storages/spec/components/storages/admin/storages/oauth_access_granted_modal_component_spec.rb index 606ae91b72d7..27653183edf1 100644 --- a/modules/storages/spec/components/storages/admin/oauth_access_grant_nudge_modal_component_spec.rb +++ b/modules/storages/spec/components/storages/admin/storages/oauth_access_granted_modal_component_spec.rb @@ -29,60 +29,66 @@ require "spec_helper" require_module_spec_helper -RSpec.describe Storages::Admin::OAuthAccessGrantNudgeModalComponent, type: :component do # rubocop:disable RSpec/SpecFilePathFormat - let(:project_storage) { build_stubbed(:project_storage) } +RSpec.describe Storages::Admin::Storages::OAuthAccessGrantedModalComponent, type: :component do # rubocop:disable RSpec/SpecFilePathFormat + context "with storage and oauth client token" do + let(:oauth_client) { build_stubbed(:oauth_client) } + let(:storage) { build_stubbed(:nextcloud_storage, oauth_client:) } - before do - render_inline(oauth_access_grant_nudge_modal_component) - end - - context "with access pending authorization" do - let(:oauth_access_grant_nudge_modal_component) { described_class.new(project_storage:) } - - it "renders the nudge modal" do - expect(page).to have_css('[role="alert"]', text: "One more step...", aria: { live: :assertive }) - expect(page).to have_test_selector( - "oauth-access-grant-nudge-modal-body", - text: "To get access to the project folder you need to login to #{project_storage.storage.name}.", - aria: { hidden: false } - ) - - expect(page).to have_button("I will do it later") - expect(page).to have_button("Login", aria: { label: "Login to #{project_storage.storage.name}" }) - end - end - - context "with access authorized" do - let(:oauth_access_grant_nudge_modal_component) do - described_class.new(project_storage:, authorized: true) + before do + allow(OAuthClientToken).to receive(:exists?).and_return(true) end it "renders a success modal" do + render_inline(described_class.new(storage:)) + expect(page).to have_css( "h1.sr-only", - text: "Access granted. You are now ready to use #{project_storage.storage.name}" + text: "Access granted. You are now ready to use #{storage.name}" ) expect(page).to have_test_selector( - "oauth-access-grant-nudge-modal-body", + "oauth-access-granted-modal-body", text: "Access granted", aria: { hidden: true } ) expect(page).to have_test_selector( - "oauth-access-grant-nudge-modal-body", - text: "You are now ready to use #{project_storage.storage.name}", + "oauth-access-granted-modal-body", + text: "You are now ready to use #{storage.name}", aria: { hidden: true } ) expect(page).to have_button("Close") + + aggregate_failures "checks that the current user has an oauth token" do + expect(OAuthClientToken).to have_received(:exists?) + .with(user: User.current, oauth_client: storage.oauth_client) + end + end + end + + context "with no storage" do + it "does not render" do + render_inline(described_class.new(storage: nil)) + + expect(page.text).to be_empty end end - context "with no project storage" do - let(:oauth_access_grant_nudge_modal_component) { described_class.new(project_storage: nil) } + context "with storage but no oauth client token" do + before do + allow(OAuthClientToken).to receive(:exists?).and_call_original + end it "does not render" do + storage = build_stubbed(:nextcloud_storage) + render_inline(described_class.new(storage:)) + expect(page.text).to be_empty + + aggregate_failures "checks that the current user has an oauth token" do + expect(OAuthClientToken).to have_received(:exists?) + .with(user: User.current, oauth_client: storage.oauth_client) + end end end end diff --git a/modules/storages/spec/components/storages/project_storages/oauth_access_grant_nudge_modal_component_spec.rb b/modules/storages/spec/components/storages/project_storages/oauth_access_grant_nudge_modal_component_spec.rb new file mode 100644 index 000000000000..7ca41d20deac --- /dev/null +++ b/modules/storages/spec/components/storages/project_storages/oauth_access_grant_nudge_modal_component_spec.rb @@ -0,0 +1,56 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ +# +require "spec_helper" +require_module_spec_helper + +RSpec.describe Storages::ProjectStorages::OAuthAccessGrantNudgeModalComponent, type: :component do # rubocop:disable RSpec/SpecFilePathFormat + let(:storage) { build_stubbed(:nextcloud_storage) } + let(:project_storage) { build_stubbed(:project_storage, storage:) } + + it "renders the nudge modal" do + render_inline(described_class.new(project_storage:)) + + expect(page).to have_css('[role="alert"]', text: "One more step...", aria: { live: :assertive }) + expect(page).to have_test_selector( + "oauth-access-grant-nudge-modal-body", + text: "To get access to the project folder you need to login to #{storage.name}." + ) + + expect(page).to have_button("I will do it later") + expect(page).to have_button("Login", aria: { label: "Login to #{storage.name}" }) + end + + context "with no project storage" do + it "does not render" do + render_inline(described_class.new(project_storage: nil)) + + expect(page.text).to be_empty + end + end +end From 2cf34b83d46a956ecc39942100bff252c80145f0 Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Thu, 15 Aug 2024 11:48:01 +0300 Subject: [PATCH 05/91] fix[Op#56922]: primer form does not auth check via API --- .../dynamic/project-storage-form.controller.ts | 2 +- .../project-folder-mode-form.controller.ts | 16 +++++----------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/frontend/src/stimulus/controllers/dynamic/project-storage-form.controller.ts b/frontend/src/stimulus/controllers/dynamic/project-storage-form.controller.ts index 2e1385657e51..c827051dfbac 100644 --- a/frontend/src/stimulus/controllers/dynamic/project-storage-form.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/project-storage-form.controller.ts @@ -213,7 +213,7 @@ export default class ProjectStorageFormController extends Controller { window.history.replaceState(window.history.state, '', url); } - private toggleFolderDisplay(value:string):void { + protected toggleFolderDisplay(value:string):void { // If the manual radio button is selected, show the manual folder selection section if (this.hasProjectFolderSectionTarget && value === 'manual') { this.projectFolderSectionTarget.classList.remove('d-none'); diff --git a/frontend/src/stimulus/controllers/dynamic/storages/project-folder-mode-form.controller.ts b/frontend/src/stimulus/controllers/dynamic/storages/project-folder-mode-form.controller.ts index ce5cdd449038..5b27137824da 100644 --- a/frontend/src/stimulus/controllers/dynamic/storages/project-folder-mode-form.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/storages/project-folder-mode-form.controller.ts @@ -28,22 +28,16 @@ * ++ */ -import { IStorageFile } from 'core-app/core/state/storage-files/storage-file.model'; import { PortalOutletTarget } from 'core-app/shared/components/modal/portal-outlet-target.enum'; import ProjectStorageFormController from '../project-storage-form.controller'; export default class ProjectFolderModeFormController extends ProjectStorageFormController { - protected get OutletTarget():PortalOutletTarget { - return PortalOutletTarget.Custom; + connect():void { + this.toggleFolderDisplay(this.folderModeValue); + this.setProjectFolderModeQueryParam(this.folderModeValue); } - protected displayFolderSelectionOrLoginButton(isConnected:boolean, projectFolder:IStorageFile|null):void { - if (isConnected) { - this.selectedFolderTextTarget.innerText = projectFolder === null - ? this.placeholderFolderNameValue - : projectFolder.name; - } else { - this.selectedFolderTextTarget.innerText = this.notLoggedInValidationValue; - } + protected get OutletTarget():PortalOutletTarget { + return PortalOutletTarget.Custom; } } From 948e99ae28ef93a8938d340ef235e3b36459d436 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Fri, 9 Aug 2024 15:19:47 +0200 Subject: [PATCH 06/91] Show correct numbers in Relations and Files tab of new SplitScreenComponent (WiP) --- app/models/relation.rb | 8 ++++++++ config/initializers/menus.rb | 8 ++++++++ .../queries/relations/filters/involved_filter_spec.rb | 6 ++++++ 3 files changed, 22 insertions(+) diff --git a/app/models/relation.rb b/app/models/relation.rb index f57f195fb1f7..5906c3c01a12 100644 --- a/app/models/relation.rb +++ b/app/models/relation.rb @@ -103,6 +103,14 @@ class Relation < ApplicationRecord scope :follows_with_lag, -> { follows.where("lag > 0") } + scope :visible_involved, + ->(work_package) do + visible_sql = WorkPackage.visible(User.current).select(:id).to_sql + where("(from_id IN (?) AND to_id IN (#{visible_sql})) OR (to_id IN (?) AND from_id IN (#{visible_sql}))", + work_package, + work_package) + end + validates :lag, numericality: { allow_nil: true } validates :to, uniqueness: { scope: :from } diff --git a/config/initializers/menus.rb b/config/initializers/menus.rb index c1445d11b380..58783d58090d 100644 --- a/config/initializers/menus.rb +++ b/config/initializers/menus.rb @@ -654,10 +654,18 @@ menu.push :files, { tab: :files }, skip_permissions_check: true, + badge: ->(work_package:, **) { + Attachment.where(container_type: "WorkPackage", + container_id: work_package) + .count + }, caption: :"js.work_packages.tabs.files" menu.push :relations, { tab: :relations }, skip_permissions_check: true, + badge: ->(work_package:, **) { + Relation.visible_involved(work_package).count + WorkPackage.where(parent_id: work_package).count + }, caption: :"js.work_packages.tabs.relations" menu.push :watchers, { tab: :watchers }, diff --git a/spec/models/queries/relations/filters/involved_filter_spec.rb b/spec/models/queries/relations/filters/involved_filter_spec.rb index e76b51d7d71d..e39735f00538 100644 --- a/spec/models/queries/relations/filters/involved_filter_spec.rb +++ b/spec/models/queries/relations/filters/involved_filter_spec.rb @@ -67,6 +67,12 @@ expect(instance.apply_to(model).to_sql).to eql expected.to_sql end + + it "is the same as the visible_involved scope" do + expected = Relation.visible_involved("1") + + expect(instance.apply_to(model).to_sql).to eql expected.to_sql + end end context 'for "!"' do From a395654731fcc19e5d6d0e1f24320fd8ca2c79ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Tue, 13 Aug 2024 12:27:52 +0200 Subject: [PATCH 07/91] Update counter as turbo stream --- .../details/tab_component.html.erb | 2 +- .../concerns/work_packages/with_split_view.rb | 40 +++++++++++++++++++ app/controllers/notifications_controller.rb | 2 +- app/views/notifications/menus/_menu.html.erb | 2 +- config/initializers/menus.rb | 3 ++ config/routes.rb | 9 +++-- .../watchers-tab/watchers-tab.component.ts | 13 ++++++ 7 files changed, 64 insertions(+), 7 deletions(-) diff --git a/app/components/work_packages/details/tab_component.html.erb b/app/components/work_packages/details/tab_component.html.erb index 5c88f03c2f16..9e47fba192cc 100644 --- a/app/components/work_packages/details/tab_component.html.erb +++ b/app/components/work_packages/details/tab_component.html.erb @@ -10,7 +10,7 @@ ) do |c| c.with_text { t("js.work_packages.tabs.#{node.name}") } count = node.badge(work_package:).to_i - c.with_counter(count:, test_selector: "wp-details-tab-component--tab-counter") if count > 0 + c.with_counter(count:, hide_if_zero: true, id: "wp-details-tab-#{node.name}-counter", test_selector: "wp-details-tab-component--tab-counter") end end end diff --git a/app/controllers/concerns/work_packages/with_split_view.rb b/app/controllers/concerns/work_packages/with_split_view.rb index 0f9cfe748960..158718129f54 100644 --- a/app/controllers/concerns/work_packages/with_split_view.rb +++ b/app/controllers/concerns/work_packages/with_split_view.rb @@ -32,12 +32,33 @@ module WithSplitView included do helper_method :split_view_base_route + + before_action :find_work_package, only: %i[split_view update_counter] + before_action :find_counter_menu, only: %i[update_counter] end def split_view_work_package_id params[:work_package_id].to_i end + def update_counter + respond_to do |format| + format.turbo_stream do + render turbo_stream: [ + turbo_stream.replace("wp-details-tab-#{@counter_menu.name}-counter") do + count = @counter_menu.badge(work_package: @work_package).to_i + Primer::Beta::Counter + .new(count:, + hide_if_zero: true, + id: "wp-details-tab-#{@counter_menu.name}-counter", + test_selector: "wp-details-tab-component--tab-counter") + .render_in(view_context) + end + ] + end + end + end + def close_split_view respond_to do |format| format.turbo_stream do @@ -65,5 +86,24 @@ def respond_to_with_split_view(&format_block) yield(format) if format_block end end + + private + + def find_work_package + @work_package = WorkPackage.visible.find(params[:work_package_id]) + rescue ActiveRecord::RecordNotFound + flash[:error] = I18n.t(:error_work_package_id_not_found) + redirect_to split_view_base_route + end + + def find_counter_menu + @counter_menu = Redmine::MenuManager + .items(:work_package_split_view, nil) + .root + .children + .detect { |node| node.name.to_s == params[:counter] } + + render_400 if @counter_menu.nil? + end end end diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb index 2db952aeb488..250ac4168860 100644 --- a/app/controllers/notifications_controller.rb +++ b/app/controllers/notifications_controller.rb @@ -31,7 +31,7 @@ class NotificationsController < ApplicationController before_action :require_login before_action :filtered_query, only: :mark_all_read - no_authorization_required! :index, :split_view, :close_split_view, :mark_all_read, :date_alerts, :share_upsale + no_authorization_required! :index, :split_view, :update_counter, :close_split_view, :mark_all_read, :date_alerts, :share_upsale def index render_notifications_layout diff --git a/app/views/notifications/menus/_menu.html.erb b/app/views/notifications/menus/_menu.html.erb index a9df39477686..9033251ad412 100644 --- a/app/views/notifications/menus/_menu.html.erb +++ b/app/views/notifications/menus/_menu.html.erb @@ -1,5 +1,5 @@ <%= turbo_frame_tag "notifications_sidemenu", - src: notifications_menu_path(**params.permit(:filter, :name)), + src: notifications_menu_path(**params.slice(:filter, :name).permit!), target: '_top', data: { turbo: false }, loading: :lazy %> diff --git a/config/initializers/menus.rb b/config/initializers/menus.rb index 58783d58090d..28fa3edaa69a 100644 --- a/config/initializers/menus.rb +++ b/config/initializers/menus.rb @@ -670,5 +670,8 @@ menu.push :watchers, { tab: :watchers }, skip_permissions_check: true, + badge: ->(work_package:, **) { + work_package.watchers.count + }, caption: :"js.work_packages.tabs.watchers" end diff --git a/config/routes.rb b/config/routes.rb index c54e101879c3..222c08b6abf6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -701,21 +701,22 @@ concern :with_split_view do |options| get "details/:work_package_id(/:tab)", - on: :collection, action: options.fetch(:action, :split_view), defaults: { tab: :overview }, as: :details, work_package_split_view: true get "/:work_package_id/close", - on: :collection, action: :close_split_view + + get "/:work_package_id/update_counter", + action: :update_counter end resources :notifications, only: :index do - concerns :with_split_view, base_route: :notifications_path - collection do + concerns :with_split_view, base_route: :notifications_path + post :mark_all_read resource :menu, module: :notifications, only: %i[show], as: :notifications_menu end diff --git a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/watchers-tab/watchers-tab.component.ts b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/watchers-tab/watchers-tab.component.ts index 32f72a86be66..cebccd4eec5d 100644 --- a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/watchers-tab/watchers-tab.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/watchers-tab/watchers-tab.component.ts @@ -42,6 +42,7 @@ import { } from 'core-app/features/work-packages/services/notifications/work-package-notification.service'; import { UntilDestroyedMixin } from 'core-app/shared/helpers/angular/until-destroyed.mixin'; import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service'; +import { TurboRequestsService } from "core-app/core/turbo/turbo-requests.service"; @Component({ templateUrl: './watchers-tab.html', @@ -89,6 +90,7 @@ export class WorkPackageWatchersTabComponent extends UntilDestroyedMixin impleme readonly cdRef:ChangeDetectorRef, readonly pathHelper:PathHelperService, readonly apiV3Service:ApiV3Service, + readonly turboRequests:TurboRequestsService, ) { super(); } @@ -97,6 +99,7 @@ export class WorkPackageWatchersTabComponent extends UntilDestroyedMixin impleme this.$element = jQuery(this.elementRef.nativeElement); const { workPackageId } = this.uiRouterGlobals.params as unknown as { workPackageId:string }; this.workPackageId = (this.workPackage.id as string) || workPackageId; + this .apiV3Service .work_packages @@ -150,6 +153,8 @@ export class WorkPackageWatchersTabComponent extends UntilDestroyedMixin impleme .id(this.workPackage) .refresh(); + this.updateCounter(); + this.cdRef.detectChanges(); }) .catch((error:any) => this.notificationService.showError(error, this.workPackage)); @@ -168,8 +173,16 @@ export class WorkPackageWatchersTabComponent extends UntilDestroyedMixin impleme .work_packages .id(this.workPackage) .refresh(); + + this.updateCounter(); + this.cdRef.detectChanges(); }) .catch((error:any) => this.notificationService.showError(error, this.workPackage)); } + + public updateCounter() { + const url = this.pathHelper.notificationsPath() + `/${this.workPackageId}/update_counter?counter=watchers`; + void this.turboRequests.request(url); + } } From 91bd0ac18c66e0bd7d311fdcd8ebc17cdc6f4456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Tue, 13 Aug 2024 12:48:17 +0200 Subject: [PATCH 08/91] Make update_counter generic for all split view --- .../concerns/work_packages/with_split_view.rb | 40 ----------- .../work_packages/split_view_controller.rb | 72 +++++++++++++++++++ config/routes.rb | 8 +-- .../watchers-tab/watchers-tab.component.ts | 2 +- 4 files changed, 77 insertions(+), 45 deletions(-) create mode 100644 app/controllers/work_packages/split_view_controller.rb diff --git a/app/controllers/concerns/work_packages/with_split_view.rb b/app/controllers/concerns/work_packages/with_split_view.rb index 158718129f54..0f9cfe748960 100644 --- a/app/controllers/concerns/work_packages/with_split_view.rb +++ b/app/controllers/concerns/work_packages/with_split_view.rb @@ -32,33 +32,12 @@ module WithSplitView included do helper_method :split_view_base_route - - before_action :find_work_package, only: %i[split_view update_counter] - before_action :find_counter_menu, only: %i[update_counter] end def split_view_work_package_id params[:work_package_id].to_i end - def update_counter - respond_to do |format| - format.turbo_stream do - render turbo_stream: [ - turbo_stream.replace("wp-details-tab-#{@counter_menu.name}-counter") do - count = @counter_menu.badge(work_package: @work_package).to_i - Primer::Beta::Counter - .new(count:, - hide_if_zero: true, - id: "wp-details-tab-#{@counter_menu.name}-counter", - test_selector: "wp-details-tab-component--tab-counter") - .render_in(view_context) - end - ] - end - end - end - def close_split_view respond_to do |format| format.turbo_stream do @@ -86,24 +65,5 @@ def respond_to_with_split_view(&format_block) yield(format) if format_block end end - - private - - def find_work_package - @work_package = WorkPackage.visible.find(params[:work_package_id]) - rescue ActiveRecord::RecordNotFound - flash[:error] = I18n.t(:error_work_package_id_not_found) - redirect_to split_view_base_route - end - - def find_counter_menu - @counter_menu = Redmine::MenuManager - .items(:work_package_split_view, nil) - .root - .children - .detect { |node| node.name.to_s == params[:counter] } - - render_400 if @counter_menu.nil? - end end end diff --git a/app/controllers/work_packages/split_view_controller.rb b/app/controllers/work_packages/split_view_controller.rb new file mode 100644 index 000000000000..876478fcd575 --- /dev/null +++ b/app/controllers/work_packages/split_view_controller.rb @@ -0,0 +1,72 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +require "rack/utils" + +class WorkPackages::SplitViewController < ApplicationController + # Authorization is checked in the find_work_package action + no_authorization_required! :update_counter + before_action :find_work_package, only: %i[update_counter] + before_action :find_counter_menu, only: %i[update_counter] + + def update_counter + respond_to do |format| + format.turbo_stream do + render turbo_stream: [ + turbo_stream.replace("wp-details-tab-#{@counter_menu.name}-counter") do + count = @counter_menu.badge(work_package: @work_package).to_i + Primer::Beta::Counter + .new(count:, + hide_if_zero: true, + id: "wp-details-tab-#{@counter_menu.name}-counter", + test_selector: "wp-details-tab-component--tab-counter") + .render_in(view_context) + end + ] + end + end + end + + private + + def find_work_package + @work_package = WorkPackage.visible.find(params[:id]) + rescue ActiveRecord::RecordNotFound + render_404 message: I18n.t(:error_work_package_id_not_found) + end + + def find_counter_menu + @counter_menu = Redmine::MenuManager + .items(:work_package_split_view, nil) + .root + .children + .detect { |node| node.name.to_s == params[:counter] } + + render_400 if @counter_menu.nil? + end +end diff --git a/config/routes.rb b/config/routes.rb index 222c08b6abf6..814193024c7e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -561,12 +561,15 @@ as: :work_package_progress end + get "/split_view/update_counter" => "work_packages/split_view#update_counter", + on: :member + # states managed by client-side (angular) routing on work_package#show get "/" => "work_packages#index", on: :collection, as: "index" get "/create_new" => "work_packages#index", on: :collection, as: "new_split" get "/new" => "work_packages#index", on: :collection, as: "new", state: "new" # We do not want to match the work package export routes - get "(/*state)" => "work_packages#show", on: :member, as: "", constraints: { id: /\d+/, state: /(?!shares).+/ } + get "(/*state)" => "work_packages#show", on: :member, as: "", constraints: { id: /\d+/, state: /(?!(shares|split_view)).+/ } get "/share_upsale" => "work_packages#index", on: :collection, as: "share_upsale" get "/edit" => "work_packages#show", on: :member, as: "edit" end @@ -708,9 +711,6 @@ get "/:work_package_id/close", action: :close_split_view - - get "/:work_package_id/update_counter", - action: :update_counter end resources :notifications, only: :index do diff --git a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/watchers-tab/watchers-tab.component.ts b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/watchers-tab/watchers-tab.component.ts index cebccd4eec5d..cf6e83069993 100644 --- a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/watchers-tab/watchers-tab.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/watchers-tab/watchers-tab.component.ts @@ -182,7 +182,7 @@ export class WorkPackageWatchersTabComponent extends UntilDestroyedMixin impleme } public updateCounter() { - const url = this.pathHelper.notificationsPath() + `/${this.workPackageId}/update_counter?counter=watchers`; + const url = `${this.pathHelper.workPackagePath(this.workPackageId)}/split_view/update_counter?counter=watchers`; void this.turboRequests.request(url); } } From 3205338db0f016f79ac290183e507f35e26ca28c Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Tue, 13 Aug 2024 15:02:16 +0200 Subject: [PATCH 09/91] Show correct number in attachments tab --- app/models/relation.rb | 8 -------- config/initializers/menus.rb | 10 ++++++---- .../queries/relations/filters/involved_filter_spec.rb | 6 ------ 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/app/models/relation.rb b/app/models/relation.rb index 5906c3c01a12..f57f195fb1f7 100644 --- a/app/models/relation.rb +++ b/app/models/relation.rb @@ -103,14 +103,6 @@ class Relation < ApplicationRecord scope :follows_with_lag, -> { follows.where("lag > 0") } - scope :visible_involved, - ->(work_package) do - visible_sql = WorkPackage.visible(User.current).select(:id).to_sql - where("(from_id IN (?) AND to_id IN (#{visible_sql})) OR (to_id IN (?) AND from_id IN (#{visible_sql}))", - work_package, - work_package) - end - validates :lag, numericality: { allow_nil: true } validates :to, uniqueness: { scope: :from } diff --git a/config/initializers/menus.rb b/config/initializers/menus.rb index 28fa3edaa69a..8800c3d6a770 100644 --- a/config/initializers/menus.rb +++ b/config/initializers/menus.rb @@ -655,16 +655,18 @@ { tab: :files }, skip_permissions_check: true, badge: ->(work_package:, **) { - Attachment.where(container_type: "WorkPackage", - container_id: work_package) - .count + count = Storages::FileLink.where(container_type: "WorkPackage", container_id: work_package).count + unless work_package.hide_attachments? + count += work_package.attachments.count + end + count }, caption: :"js.work_packages.tabs.files" menu.push :relations, { tab: :relations }, skip_permissions_check: true, badge: ->(work_package:, **) { - Relation.visible_involved(work_package).count + WorkPackage.where(parent_id: work_package).count + work_package.relations.count + work_package.children.count }, caption: :"js.work_packages.tabs.relations" menu.push :watchers, diff --git a/spec/models/queries/relations/filters/involved_filter_spec.rb b/spec/models/queries/relations/filters/involved_filter_spec.rb index e39735f00538..e76b51d7d71d 100644 --- a/spec/models/queries/relations/filters/involved_filter_spec.rb +++ b/spec/models/queries/relations/filters/involved_filter_spec.rb @@ -67,12 +67,6 @@ expect(instance.apply_to(model).to_sql).to eql expected.to_sql end - - it "is the same as the visible_involved scope" do - expected = Relation.visible_involved("1") - - expect(instance.apply_to(model).to_sql).to eql expected.to_sql - end end context 'for "!"' do From 0f15c3d54b60b31df1c353d75f88039d1d2af833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Tue, 13 Aug 2024 16:02:30 +0200 Subject: [PATCH 10/91] Correctly add meetings tab for notifications --- modules/meeting/lib/open_project/meeting/engine.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/modules/meeting/lib/open_project/meeting/engine.rb b/modules/meeting/lib/open_project/meeting/engine.rb index 29939b531ee2..245c8745d84e 100644 --- a/modules/meeting/lib/open_project/meeting/engine.rb +++ b/modules/meeting/lib/open_project/meeting/engine.rb @@ -124,6 +124,18 @@ class Engine < ::Rails::Engine parent: :meetings, partial: "meetings/menus/menu" + menu :work_package_split_view, + :meetings, + { tab: :meetings }, + skip_permissions_check: true, + if: ->(_project) { + User.current.allowed_in_any_project?(:view_meetings) + }, + badge: ->(work_package:, **) { + work_package.meetings.count + }, + caption: :label_meeting_plural + should_render_global_menu_item = Proc.new do (User.current.logged? || !Setting.login_required?) && User.current.allowed_in_any_project?(:view_meetings) From 7d9d636487f2d1f458a1a524b18fe5cba085f104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Tue, 13 Aug 2024 16:10:33 +0200 Subject: [PATCH 11/91] Fix github split view tab --- .../lib/open_project/github_integration/engine.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/github_integration/lib/open_project/github_integration/engine.rb b/modules/github_integration/lib/open_project/github_integration/engine.rb index e900ac811df4..e325e8e8699f 100644 --- a/modules/github_integration/lib/open_project/github_integration/engine.rb +++ b/modules/github_integration/lib/open_project/github_integration/engine.rb @@ -81,9 +81,11 @@ def self.settings if: ->(project) { User.current.allowed_in_project?(:show_github_content, project) }, - parent: :admin_github_integration, - caption: :label_deploy_target_plural, - icon: "cloud" + skip_permissions_check: true, + badge: ->(work_package:, **) { + work_package.github_pull_requests.count + }, + caption: :project_module_github end initializer "github.register_hook" do From c3ed85c3c7c41cf44f486dafe4ffcc5d121efb47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Tue, 13 Aug 2024 16:13:36 +0200 Subject: [PATCH 12/91] Fix gitlab split view tab --- .../lib/open_project/gitlab_integration/engine.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/modules/gitlab_integration/lib/open_project/gitlab_integration/engine.rb b/modules/gitlab_integration/lib/open_project/gitlab_integration/engine.rb index ac286e9b81a6..93959013cdc5 100644 --- a/modules/gitlab_integration/lib/open_project/gitlab_integration/engine.rb +++ b/modules/gitlab_integration/lib/open_project/gitlab_integration/engine.rb @@ -47,6 +47,19 @@ class Engine < ::Rails::Engine permission(:show_gitlab_content, {}, permissible_on: %i[work_package project]) + + menu :work_package_split_view, + :gitlab, + { tab: :gitlab }, + if: ->(project) { + User.current.allowed_in_project?(:show_gitlab_content, project) + }, + skip_permissions_check: true, + badge: ->(work_package:, **) { + work_package.gitlab_merge_requests.count + + work_package.gitlab_issues.count + }, + caption: :project_module_github end end From 101ddbea1bbdd4262e02d21679aa7d19687ed8d3 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Wed, 14 Aug 2024 08:43:27 +0200 Subject: [PATCH 13/91] Update relations counter correctly in new split screen --- .../core/path-helper/path-helper.service.ts | 4 ++++ .../children/wp-children-query.component.ts | 13 ++++++++--- .../wp-relation-row.component.ts | 2 ++ .../wp-relations-create.component.ts | 23 ++++++++----------- .../wp-relations/wp-relations.service.ts | 15 ++++++++++-- .../watchers-tab/watchers-tab.component.ts | 2 +- 6 files changed, 39 insertions(+), 20 deletions(-) diff --git a/frontend/src/app/core/path-helper/path-helper.service.ts b/frontend/src/app/core/path-helper/path-helper.service.ts index 9fbd5a869bcb..a24e9fb6ab0a 100644 --- a/frontend/src/app/core/path-helper/path-helper.service.ts +++ b/frontend/src/app/core/path-helper/path-helper.service.ts @@ -288,6 +288,10 @@ export class PathHelperService { return `${this.workPackagePath(workPackageId)}/progress/edit`; } + public workPackageUpdateCounterPath(workPackageId:string|number, counter:string) { + return `${this.workPackagePath(workPackageId)}/split_view/update_counter?counter=${counter}`; + } + // Work Package Bulk paths public workPackagesBulkEditPath() { diff --git a/frontend/src/app/features/work-packages/components/wp-relations/embedded/children/wp-children-query.component.ts b/frontend/src/app/features/work-packages/components/wp-relations/embedded/children/wp-children-query.component.ts index 9693e42f00cd..6f11aa8ff1e7 100644 --- a/frontend/src/app/features/work-packages/components/wp-relations/embedded/children/wp-children-query.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-relations/embedded/children/wp-children-query.component.ts @@ -43,6 +43,7 @@ import { HalEventsService } from 'core-app/features/hal/services/hal-events.serv import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service'; import { GroupDescriptor } from 'core-app/features/work-packages/components/wp-single-view/wp-single-view.component'; import idFromLink from 'core-app/features/hal/helpers/id-from-link'; +import { WorkPackageRelationsService } from 'core-app/features/work-packages/components/wp-relations/wp-relations.service'; @Component({ selector: 'wp-children-query', @@ -76,13 +77,16 @@ export class WorkPackageChildrenQueryComponent extends WorkPackageRelationQueryB ), ]; - constructor(protected wpRelationsHierarchyService:WorkPackageRelationsHierarchyService, + constructor( + protected wpRelationsHierarchyService:WorkPackageRelationsHierarchyService, protected PathHelper:PathHelperService, protected wpInlineCreate:WorkPackageInlineCreateService, protected halEvents:HalEventsService, protected apiV3Service:ApiV3Service, protected queryUrlParamsHelper:UrlParamsHelperService, - readonly I18n:I18nService) { + readonly I18n:I18nService, + readonly wpRelations:WorkPackageRelationsService, + ) { super(queryUrlParamsHelper); } @@ -116,6 +120,9 @@ export class WorkPackageChildrenQueryComponent extends WorkPackageRelationQueryB filter(() => !!this.embeddedTable?.isInitialized), this.untilDestroyed(), ) - .subscribe(() => this.refreshTable()); + .subscribe(() => { + this.wpRelations.updateCounter(this.workPackage); + this.refreshTable(); + }); } } diff --git a/frontend/src/app/features/work-packages/components/wp-relations/wp-relation-row/wp-relation-row.component.ts b/frontend/src/app/features/work-packages/components/wp-relations/wp-relation-row/wp-relation-row.component.ts index 03c6347d03bd..458088971a07 100644 --- a/frontend/src/app/features/work-packages/components/wp-relations/wp-relation-row/wp-relation-row.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-relations/wp-relation-row/wp-relation-row.component.ts @@ -197,6 +197,8 @@ export class WorkPackageRelationRowComponent extends UntilDestroyedMixin impleme .cache .updateWorkPackage(this.relatedWorkPackage); + this.wpRelations.updateCounter(this.workPackage); + this.notificationService.showSave(this.relatedWorkPackage); }) .catch((err:any) => this.notificationService.handleRawError(err, diff --git a/frontend/src/app/features/work-packages/components/wp-relations/wp-relations-create/wp-relations-create.component.ts b/frontend/src/app/features/work-packages/components/wp-relations/wp-relations-create/wp-relations-create.component.ts index 38d34ad55bac..3ac9ac22230f 100644 --- a/frontend/src/app/features/work-packages/components/wp-relations/wp-relations-create/wp-relations-create.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-relations/wp-relations-create/wp-relations-create.component.ts @@ -1,5 +1,8 @@ import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; -import { Component, Input } from '@angular/core'; +import { + Component, + Input, +} from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { HalEventsService } from 'core-app/features/hal/services/hal-events.service'; import { WorkPackageNotificationService } from 'core-app/features/work-packages/services/notifications/work-package-notification.service'; @@ -29,21 +32,12 @@ export class WorkPackageRelationsCreateComponent { addNewRelation: this.I18n.t('js.relation_buttons.add_new_relation'), }; - constructor(readonly I18n:I18nService, + constructor( + readonly I18n:I18nService, protected wpRelations:WorkPackageRelationsService, protected notificationService:WorkPackageNotificationService, - protected halEvents:HalEventsService) { - } - - public createRelation() { - if (!this.selectedRelationType || !this.selectedWpId) { - return; - } - - this.isDisabled = true; - this.createCommonRelation() - .catch(() => this.isDisabled = false) - .then(() => this.isDisabled = false); + protected halEvents:HalEventsService, + ) { } public onSelected(workPackage?:WorkPackageResource) { @@ -64,6 +58,7 @@ export class WorkPackageRelationsCreateComponent { relationType: this.selectedRelationType, }); this.notificationService.showSave(this.workPackage); + this.wpRelations.updateCounter(this.workPackage); this.toggleRelationsCreateForm(); }) .catch((err) => { diff --git a/frontend/src/app/features/work-packages/components/wp-relations/wp-relations.service.ts b/frontend/src/app/features/work-packages/components/wp-relations/wp-relations.service.ts index 8be4945eff09..84ae3d9bfb9e 100644 --- a/frontend/src/app/features/work-packages/components/wp-relations/wp-relations.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-relations/wp-relations.service.ts @@ -11,6 +11,7 @@ import { } from 'rxjs'; import { map, take } from 'rxjs/operators'; import { RelationResource } from 'core-app/features/hal/resources/relation-resource'; +import { TurboRequestsService } from 'core-app/core/turbo/turbo-requests.service'; export type RelationsStateValue = { [relationId:string]:RelationResource }; @@ -27,9 +28,12 @@ export class RelationStateGroup extends StatesGroup { @Injectable() export class WorkPackageRelationsService extends StateCacheService { - constructor(private PathHelper:PathHelperService, + constructor( + private PathHelper:PathHelperService, private apiV3Service:ApiV3Service, - private halResource:HalResourceService) { + private halResource:HalResourceService, + readonly turboRequests:TurboRequestsService, + ) { super(new RelationStateGroup().relations); } @@ -172,6 +176,13 @@ export class WorkPackageRelationsService extends StateCacheService Date: Wed, 14 Aug 2024 10:08:18 +0200 Subject: [PATCH 14/91] Extract component for updating counters --- .../details/update_counter_component.rb | 50 +++++++++++++++++++ .../work_packages/split_view_controller.rb | 23 ++------- .../open_project/gitlab_integration/engine.rb | 26 +++++----- 3 files changed, 66 insertions(+), 33 deletions(-) create mode 100644 app/components/work_packages/details/update_counter_component.rb diff --git a/app/components/work_packages/details/update_counter_component.rb b/app/components/work_packages/details/update_counter_component.rb new file mode 100644 index 000000000000..38fef212bafc --- /dev/null +++ b/app/components/work_packages/details/update_counter_component.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +class WorkPackages::Details::UpdateCounterComponent < ApplicationComponent + include OpPrimer::ComponentHelpers + include OpTurbo::Streamable + + attr_reader :work_package, :menu_name + + def initialize(work_package:, menu_name:) + super + + @work_package = work_package + @menu = find_menu_item(menu_name) + end + + def call + render Primer::Beta::Counter + .new(count:, + hide_if_zero: true, + id: wrapper_key, + test_selector: "wp-details-tab-component--tab-counter") + end + + # We don't need a wrapper component, but wrap on the counter id + def wrapped? + true + end + + def wrapper_key + "wp-details-tab-#{@menu.name}-counter" + end + + def render? + @menu.present? + end + + def count + @menu + .badge(work_package:) + .to_i + end + + def find_menu_item(menu_name) + Redmine::MenuManager + .items(:work_package_split_view, nil) + .root + .children + .detect { |node| node.name.to_s == menu_name } + end +end diff --git a/app/controllers/work_packages/split_view_controller.rb b/app/controllers/work_packages/split_view_controller.rb index 876478fcd575..4a5a9057d95a 100644 --- a/app/controllers/work_packages/split_view_controller.rb +++ b/app/controllers/work_packages/split_view_controller.rb @@ -32,21 +32,14 @@ class WorkPackages::SplitViewController < ApplicationController # Authorization is checked in the find_work_package action no_authorization_required! :update_counter before_action :find_work_package, only: %i[update_counter] - before_action :find_counter_menu, only: %i[update_counter] def update_counter respond_to do |format| format.turbo_stream do render turbo_stream: [ - turbo_stream.replace("wp-details-tab-#{@counter_menu.name}-counter") do - count = @counter_menu.badge(work_package: @work_package).to_i - Primer::Beta::Counter - .new(count:, - hide_if_zero: true, - id: "wp-details-tab-#{@counter_menu.name}-counter", - test_selector: "wp-details-tab-component--tab-counter") - .render_in(view_context) - end + WorkPackages::Details::UpdateCounterComponent + .new(work_package: @work_package, menu_name: params[:counter]) + .render_as_turbo_stream(action: :replace, view_context:) ] end end @@ -59,14 +52,4 @@ def find_work_package rescue ActiveRecord::RecordNotFound render_404 message: I18n.t(:error_work_package_id_not_found) end - - def find_counter_menu - @counter_menu = Redmine::MenuManager - .items(:work_package_split_view, nil) - .root - .children - .detect { |node| node.name.to_s == params[:counter] } - - render_400 if @counter_menu.nil? - end end diff --git a/modules/gitlab_integration/lib/open_project/gitlab_integration/engine.rb b/modules/gitlab_integration/lib/open_project/gitlab_integration/engine.rb index 93959013cdc5..9a42cff16d90 100644 --- a/modules/gitlab_integration/lib/open_project/gitlab_integration/engine.rb +++ b/modules/gitlab_integration/lib/open_project/gitlab_integration/engine.rb @@ -47,20 +47,20 @@ class Engine < ::Rails::Engine permission(:show_gitlab_content, {}, permissible_on: %i[work_package project]) - - menu :work_package_split_view, - :gitlab, - { tab: :gitlab }, - if: ->(project) { - User.current.allowed_in_project?(:show_gitlab_content, project) - }, - skip_permissions_check: true, - badge: ->(work_package:, **) { - work_package.gitlab_merge_requests.count + - work_package.gitlab_issues.count - }, - caption: :project_module_github end + + menu :work_package_split_view, + :gitlab, + { tab: :gitlab }, + if: ->(project) { + User.current.allowed_in_project?(:show_gitlab_content, project) + }, + skip_permissions_check: true, + badge: ->(work_package:, **) { + work_package.gitlab_merge_requests.count + + work_package.gitlab_issues.count + }, + caption: :project_module_github end patches %w[WorkPackage] From 66148da5fc9612624e4af4399da13cdbb47b07eb Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Wed, 14 Aug 2024 14:37:52 +0200 Subject: [PATCH 15/91] Update meetings counter when adding a new meeting --- .../app/controllers/work_package_meetings_tab_controller.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/meeting/app/controllers/work_package_meetings_tab_controller.rb b/modules/meeting/app/controllers/work_package_meetings_tab_controller.rb index 51101a29e21f..5267dc509c81 100644 --- a/modules/meeting/app/controllers/work_package_meetings_tab_controller.rb +++ b/modules/meeting/app/controllers/work_package_meetings_tab_controller.rb @@ -83,6 +83,11 @@ def add_work_package_to_meeting upcoming_meetings_count: @upcoming_meetings_count, past_meetings_count: @past_meetings_count ) + + replace_via_turbo_stream( + component: WorkPackages::Details::UpdateCounterComponent.new(work_package: @work_package, menu_name: "meetings") + ) + # TODO: show success message? else # show errors in form From 8383b2f3ae17bb493ccd8eba47025c1cb1ea4b9b Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Wed, 14 Aug 2024 14:42:14 +0200 Subject: [PATCH 16/91] Only count upcoming meetings for tab counter --- modules/meeting/lib/open_project/meeting/engine.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/meeting/lib/open_project/meeting/engine.rb b/modules/meeting/lib/open_project/meeting/engine.rb index 245c8745d84e..06cad77f9d83 100644 --- a/modules/meeting/lib/open_project/meeting/engine.rb +++ b/modules/meeting/lib/open_project/meeting/engine.rb @@ -132,7 +132,7 @@ class Engine < ::Rails::Engine User.current.allowed_in_any_project?(:view_meetings) }, badge: ->(work_package:, **) { - work_package.meetings.count + work_package.meetings.where(meetings: { start_time: Time.zone.today.beginning_of_day.. }).count }, caption: :label_meeting_plural From ad9a31d6216a416921e5ce9bcc6a17f42b9b9bce Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Thu, 15 Aug 2024 09:58:08 +0200 Subject: [PATCH 17/91] Update counter of files tab --- .../files-tab/op-files-tab.component.html | 8 ++++++- .../files-tab/op-files-tab.component.ts | 21 ++++++++++++++++++- .../attachment-list.component.ts | 14 +++++++++++-- .../attachments/attachments.component.html | 1 + .../attachments/attachments.component.ts | 10 +++++++-- .../storages/storage/storage.component.ts | 11 ++++++++-- 6 files changed, 57 insertions(+), 8 deletions(-) diff --git a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/files-tab/op-files-tab.component.html b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/files-tab/op-files-tab.component.html index c063526da053..ae007c7afac3 100644 --- a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/files-tab/op-files-tab.component.html +++ b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/files-tab/op-files-tab.component.html @@ -15,7 +15,11 @@ - +
diff --git a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/files-tab/op-files-tab.component.ts b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/files-tab/op-files-tab.component.ts index a36203364fe1..b37ea1ba78ad 100644 --- a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/files-tab/op-files-tab.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/files-tab/op-files-tab.component.ts @@ -1,4 +1,4 @@ -//-- copyright +// -- copyright // OpenProject is an open source project management software. // Copyright (C) the OpenProject GmbH // @@ -41,6 +41,8 @@ import { CurrentUserService } from 'core-app/core/current-user/current-user.serv import { HalResource } from 'core-app/features/hal/resources/hal-resource'; import { ProjectStoragesResourceService } from 'core-app/core/state/project-storages/project-storages.service'; import { IProjectStorage } from 'core-app/core/state/project-storages/project-storage.model'; +import { TurboRequestsService } from 'core-app/core/turbo/turbo-requests.service'; +import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; @Component({ selector: 'op-files-tab', @@ -68,6 +70,8 @@ export class WorkPackageFilesTabComponent implements OnInit { private readonly i18n:I18nService, private readonly currentUserService:CurrentUserService, private readonly projectStoragesResourceService:ProjectStoragesResourceService, + private readonly pathHelper:PathHelperService, + private readonly turboRequests:TurboRequestsService, ) { } ngOnInit():void { @@ -96,4 +100,19 @@ export class WorkPackageFilesTabComponent implements OnInit { map(([storages, viewPermission]) => storages.length > 0 && viewPermission), ); } + + attachmentRemoved() { + this.updateCounter(); + } + + attachmentAdded() { + this.updateCounter(); + } + + private updateCounter() { + if (this.workPackage.id) { + const url = this.pathHelper.workPackageUpdateCounterPath(this.workPackage.id, 'files'); + void this.turboRequests.request(url); + } + } } diff --git a/frontend/src/app/shared/components/attachments/attachment-list/attachment-list.component.ts b/frontend/src/app/shared/components/attachments/attachment-list/attachment-list.component.ts index f8ce45051967..d5dd582204fd 100644 --- a/frontend/src/app/shared/components/attachments/attachment-list/attachment-list.component.ts +++ b/frontend/src/app/shared/components/attachments/attachment-list/attachment-list.component.ts @@ -1,4 +1,4 @@ -//-- copyright +// -- copyright // OpenProject is an open source project management software. // Copyright (C) the OpenProject GmbH // @@ -26,7 +26,14 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + OnInit, + Output, +} from '@angular/core'; import { IAttachment } from 'core-app/core/state/attachments/attachment.model'; import { UntilDestroyedMixin } from 'core-app/shared/helpers/angular/until-destroyed.mixin'; import { AttachmentsResourceService } from 'core-app/core/state/attachments/attachments.service'; @@ -43,6 +50,8 @@ export class OpAttachmentListComponent extends UntilDestroyedMixin implements On @Input() public showTimestamp = true; + @Output() public attachmentRemoved = new EventEmitter(); + constructor( private readonly attachmentsResourceService:AttachmentsResourceService, ) { @@ -54,5 +63,6 @@ export class OpAttachmentListComponent extends UntilDestroyedMixin implements On public removeAttachment(attachment:IAttachment):void { this.attachmentsResourceService.removeAttachment(this.collectionKey, attachment).subscribe(); + this.attachmentRemoved.emit(); } } diff --git a/frontend/src/app/shared/components/attachments/attachments.component.html b/frontend/src/app/shared/components/attachments/attachments.component.html index 3c7dccbb6be4..6df36c5761b1 100644 --- a/frontend/src/app/shared/components/attachments/attachments.component.html +++ b/frontend/src/app/shared/components/attachments/attachments.component.html @@ -7,6 +7,7 @@ [attachments]="attachments$ | async" [collectionKey]="collectionKey" [showTimestamp]="showTimestamp" + (attachmentRemoved)="attachmentRemoved.emit()" > (); + + @Output() public attachmentAdded = new EventEmitter(); + public attachments$:Observable; public draggingOverDropZone = false; @@ -258,7 +264,7 @@ export class OpAttachmentsComponent extends UntilDestroyedMixin implements OnIni .attachmentsResourceService .attachFiles(this.resource, filesWithoutFolders) .subscribe({ - next: () => {}, + next: () => { this.attachmentAdded.emit(); }, error: (error:HttpErrorResponse) => this.toastService.addError(error), }); } diff --git a/frontend/src/app/shared/components/storages/storage/storage.component.ts b/frontend/src/app/shared/components/storages/storage/storage.component.ts index 2a1231873553..cc7ca74d77c2 100644 --- a/frontend/src/app/shared/components/storages/storage/storage.component.ts +++ b/frontend/src/app/shared/components/storages/storage/storage.component.ts @@ -1,4 +1,4 @@ -//-- copyright +// -- copyright // OpenProject is an open source project management software. // Copyright (C) the OpenProject GmbH // @@ -31,9 +31,11 @@ import { ChangeDetectorRef, Component, ElementRef, + EventEmitter, Input, OnDestroy, OnInit, + Output, ViewChild, } from '@angular/core'; import { HttpErrorResponse } from '@angular/common/http'; @@ -121,6 +123,10 @@ export class StorageComponent extends UntilDestroyedMixin implements OnInit, OnD @ViewChild('hiddenFileInput') public filePicker:ElementRef; + @Output() public fileRemoved = new EventEmitter(); + + @Output() public fileAdded = new EventEmitter(); + fileLinks:Observable; storage:Observable; @@ -291,7 +297,7 @@ export class StorageComponent extends UntilDestroyedMixin implements OnInit, OnD switchMap((key) => this.fileLinkResourceService.remove(key, fileLink)), ) .subscribe({ - next: () => { /* Do nothing */ }, + next: () => { this.fileRemoved.emit(); }, error: (error:HttpErrorResponse) => this.toastService.addError(error), }); } @@ -412,6 +418,7 @@ export class StorageComponent extends UntilDestroyedMixin implements OnInit, OnD .subscribe({ next: (collection) => { this.toastService.addSuccess(this.text.toast.successFileLinksCreated(collection.count)); + this.fileAdded.emit(); }, error: (error) => { if (isUploadError) { From a6172642192344bd0a9c4c72fe1baf39058d2091 Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Thu, 15 Aug 2024 11:58:55 +0300 Subject: [PATCH 18/91] tests[Op#56922]: update feature context with login modal --- .../storages/admin/project_storages_spec.rb | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/modules/storages/spec/features/storages/admin/project_storages_spec.rb b/modules/storages/spec/features/storages/admin/project_storages_spec.rb index d41c7941ad3b..c51aab3263ff 100644 --- a/modules/storages/spec/features/storages/admin/project_storages_spec.rb +++ b/modules/storages/spec/features/storages/admin/project_storages_spec.rb @@ -46,6 +46,11 @@ shared_let(:archived_project) { create(:project, active: false, name: "My archived Project") } shared_let(:storage) { create(:nextcloud_storage_with_complete_configuration, name: "My Nextcloud Storage") } shared_let(:project_storage) { create(:project_storage, project:, storage:, project_folder_mode: "automatic") } + shared_let(:oauth_client_token) { create(:oauth_client_token, oauth_client: storage.oauth_client, user: admin) } + + shared_let(:remote_identity) do + create(:remote_identity, oauth_client: storage.oauth_client, user: admin, origin_user_id: "admin") + end shared_let(:archived_project_project_storage) do create(:project_storage, project: archived_project, storage:, project_folder_mode: "inactive") @@ -55,7 +60,6 @@ current_user { admin } - context "with insufficient permissions" do it "is not accessible" do login_as(non_admin) @@ -86,7 +90,7 @@ end end - context "with sufficient permissions and an completely configured storage" do + context "with sufficient permissions and a completely configured storage" do before do login_as(admin) storage.update!(host: "https://example.com") @@ -180,10 +184,6 @@ describe "Linking a project to a storage with a manually managed folder" do context "when the user has granted OAuth access" do - let(:oauth_client_token) { create(:oauth_client_token, oauth_client: storage.oauth_client, user: admin) } - let(:remote_identity) do - create(:remote_identity, oauth_client: storage.oauth_client, user: admin, origin_user_id: "admin") - end let(:location_picker) { Components::FilePickerDialog.new } let(:root_xml_response) { build(:webdav_data) } @@ -205,9 +205,6 @@ end before do - oauth_client_token - remote_identity - stub_request(:propfind, "#{storage.host}/remote.php/dav/files/#{remote_identity.origin_user_id}") .to_return(status: 207, body: root_xml_response, headers: {}) stub_request(:propfind, "#{storage.host}/remote.php/dav/files/#{remote_identity.origin_user_id}/Folder1") @@ -296,17 +293,15 @@ end context "when the user has not granted oauth access" do - before do + it "show a storage login button" do OAuthClientToken.where(user: admin, oauth_client: storage.oauth_client).destroy_all - end - it "show a storage login button" do click_on "Add projects" within("dialog") do - choose "Existing folder with manually managed permissions" - wait_for(page).to have_button("Nextcloud login") - click_on("Nextcloud login") + wait_for(page).to have_button("Login") + click_on("Login") + wait_for(page).to have_current_path( %r{/index.php/apps/oauth2/authorize\?client_id=.*&redirect_uri=.*&response_type=code&state=.*} ) From fe7ead6c2b2cd40026eeda176169f27136fec924 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Thu, 15 Aug 2024 13:51:11 +0200 Subject: [PATCH 19/91] Fix some linting issues --- .../watchers-tab/watchers-tab.component.ts | 2 +- .../meetings/work_package_meetings_tab_component_streams.rb | 6 ++++++ .../app/controllers/work_package_meetings_tab_controller.rb | 4 +--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/watchers-tab/watchers-tab.component.ts b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/watchers-tab/watchers-tab.component.ts index 449e3b733b7a..14738a4b1cfa 100644 --- a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/watchers-tab/watchers-tab.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/watchers-tab/watchers-tab.component.ts @@ -42,7 +42,7 @@ import { } from 'core-app/features/work-packages/services/notifications/work-package-notification.service'; import { UntilDestroyedMixin } from 'core-app/shared/helpers/angular/until-destroyed.mixin'; import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service'; -import { TurboRequestsService } from "core-app/core/turbo/turbo-requests.service"; +import { TurboRequestsService } from 'core-app/core/turbo/turbo-requests.service'; @Component({ templateUrl: './watchers-tab.html', diff --git a/modules/meeting/app/controllers/concerns/meetings/work_package_meetings_tab_component_streams.rb b/modules/meeting/app/controllers/concerns/meetings/work_package_meetings_tab_component_streams.rb index f0f5316814dd..69a0b75c61e6 100644 --- a/modules/meeting/app/controllers/concerns/meetings/work_package_meetings_tab_component_streams.rb +++ b/modules/meeting/app/controllers/concerns/meetings/work_package_meetings_tab_component_streams.rb @@ -63,6 +63,12 @@ def update_index_component_via_turbo_stream(direction:, agenda_items_grouped_by_ ) ) end + + def replace_tab_counter_via_turbo_stream(work_package: @work_package) + replace_via_turbo_stream( + component: WorkPackages::Details::UpdateCounterComponent.new(work_package:, menu_name: "meetings") + ) + end end end end diff --git a/modules/meeting/app/controllers/work_package_meetings_tab_controller.rb b/modules/meeting/app/controllers/work_package_meetings_tab_controller.rb index 5267dc509c81..9d4a7f3c59e9 100644 --- a/modules/meeting/app/controllers/work_package_meetings_tab_controller.rb +++ b/modules/meeting/app/controllers/work_package_meetings_tab_controller.rb @@ -84,9 +84,7 @@ def add_work_package_to_meeting past_meetings_count: @past_meetings_count ) - replace_via_turbo_stream( - component: WorkPackages::Details::UpdateCounterComponent.new(work_package: @work_package, menu_name: "meetings") - ) + replace_tab_counter_via_turbo_stream(work_package: @work_package) # TODO: show success message? else From 883b8bc9a7144886ff0eae20b1b0ebb56cca0963 Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Thu, 15 Aug 2024 15:13:52 +0300 Subject: [PATCH 20/91] feat[Op#56922]: update oauth grant modal to new design --- ...ccess_grant_nudge_modal_component.html.erb | 31 ++++++++++++------- ...auth_access_grant_nudge_modal_component.rb | 26 +++++----------- .../oauth_access_granted_modal_component.rb | 18 +++-------- ...auth_access_grant_nudge_modal_component.rb | 4 +++ modules/storages/config/locales/en.yml | 15 ++++----- ...access_grant_nudge_modal_component_spec.rb | 9 +++--- ...uth_access_granted_modal_component_spec.rb | 13 ++++---- ...access_grant_nudge_modal_component_spec.rb | 8 +++-- .../storages/admin/project_storages_spec.rb | 6 ++-- .../oauth_access_grant_spec.rb | 8 ++--- 10 files changed, 68 insertions(+), 70 deletions(-) diff --git a/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.html.erb b/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.html.erb index 3c71ea2eb3f3..46bb927797aa 100644 --- a/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.html.erb +++ b/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.html.erb @@ -3,14 +3,15 @@ render( Primer::Alpha::Dialog.new( id: dialog_id, - title:, + title: heading_text, data: { 'application-target': 'dynamic', controller: 'storages--oauth-access-grant-nudge-modal', 'storages--oauth-access-grant-nudge-modal-close-button-label-value': I18n.t('button_close'), 'storages--oauth-access-grant-nudge-modal-loading-screen-reader-message-value': waiting_title, }, - test_selector: 'oauth-access-grant-nudge-modal' + test_selector: 'oauth-access-grant-nudge-modal', + size: :large ) ) do |dialog| dialog.with_header( @@ -19,7 +20,8 @@ aria: { live: :assertive }, data: { 'storages--oauth-access-grant-nudge-modal-target': 'header' - } + }, + visually_hide_title: true ) dialog.with_body( @@ -37,13 +39,17 @@ ) { render(Storages::OpenProjectStorageModalComponent::Body.new(:waiting, waiting_title:)) } ) concat( - render( - Primer::Beta::Text.new( - data: { - 'storages--oauth-access-grant-nudge-modal-target': 'requestAccessBody' - } - ) - ) { body_text } + render(Primer::Beta::Blankslate.new( + border: false, + data: { "storages--oauth-access-grant-nudge-modal-target": "requestAccessBody" } + )) do |component| + component.with_visual_icon(icon: :"sign-in", size: :medium) + + component.with_heading(tag: :h2, aria: { hidden: true }) + .with_content(heading_text) + + component.with_description(color: :subtle) { body_text } + end ) end @@ -81,7 +87,10 @@ action: 'storages--oauth-access-grant-nudge-modal#requestAccess' } ) - ) { confirm_button_text } + ) do |button| + button.with_trailing_action_icon(icon: :"link-external") + confirm_button_text + end end ) end diff --git a/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.rb b/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.rb index 8dd42e5eb213..351c386fd512 100644 --- a/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.rb +++ b/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.rb @@ -34,6 +34,7 @@ module Admin module Storages class OAuthAccessGrantNudgeModalComponent < ApplicationComponent include OpTurbo::Streamable + include OpPrimer::ComponentHelpers options dialog_id: "storages--oauth-grant-nudge-modal-component", dialog_body_id: "storages--oauth-grant-nudge-modal-body-component" @@ -51,25 +52,12 @@ def render? attr_reader :storage - def confirm_button_text - I18n.t("storages.oauth_grant_nudge_modal.confirm_button_label") - end - - def title - I18n.t("storages.oauth_grant_nudge_modal.title") - end - - def waiting_title - I18n.t("storages.oauth_grant_nudge_modal.requesting_access_to", storage: storage.name) - end - - def cancel_button_text - I18n.t("storages.oauth_grant_nudge_modal.cancel_button_label") - end - - def body_text - I18n.t("storages.oauth_grant_nudge_modal.body", storage: storage.name) - end + def confirm_button_text = I18n.t("storages.oauth_grant_nudge_modal.confirm_button_label", provider_type:) + def heading_text = I18n.t("storages.oauth_grant_nudge_modal.heading", provider_type:) + def waiting_title = I18n.t("storages.oauth_grant_nudge_modal.requesting_access_to", storage: storage.name) + def cancel_button_text = I18n.t(:button_close) + def body_text = I18n.t("storages.oauth_grant_nudge_modal.body", provider_type:) + def provider_type = I18n.t("storages.provider_types.#{storage.short_provider_type}.name") def confirm_button_aria_label I18n.t("storages.oauth_grant_nudge_modal.confirm_button_aria_label", storage: storage.name) diff --git a/modules/storages/app/components/storages/admin/storages/oauth_access_granted_modal_component.rb b/modules/storages/app/components/storages/admin/storages/oauth_access_granted_modal_component.rb index c5ff9dc78cf2..a35716e70bab 100644 --- a/modules/storages/app/components/storages/admin/storages/oauth_access_granted_modal_component.rb +++ b/modules/storages/app/components/storages/admin/storages/oauth_access_granted_modal_component.rb @@ -49,22 +49,12 @@ def render? def dialog_id = "#{wrapper_key}-dialog-id" def dialog_body_id = "#{wrapper_key}-dialog-body-id" - - def title - I18n.t("storages.oauth_grant_nudge_modal.access_granted_screen_reader", storage: storage.name) - end - - def waiting_title - I18n.t("storages.oauth_grant_nudge_modal.requesting_access_to", storage: storage.name) - end - - def cancel_button_text - I18n.t("button_close") - end + def title = I18n.t("storages.oauth_access_granted_modal.access_granted_screen_reader", storage: storage.name) + def cancel_button_text = I18n.t("button_close") def body_text - success_title = I18n.t("storages.oauth_grant_nudge_modal.access_granted") - success_subtitle = I18n.t("storages.oauth_grant_nudge_modal.storage_ready", storage: storage.name) + success_title = I18n.t("storages.oauth_access_granted_modal.access_granted") + success_subtitle = I18n.t("storages.oauth_access_granted_modal.storage_ready", storage: storage.name) concat(render(::Storages::OpenProjectStorageModalComponent::Body.new(:success, success_subtitle:, success_title:))) end diff --git a/modules/storages/app/components/storages/project_storages/oauth_access_grant_nudge_modal_component.rb b/modules/storages/app/components/storages/project_storages/oauth_access_grant_nudge_modal_component.rb index f6c3d9c4b5d7..3eeb3f3bfe65 100644 --- a/modules/storages/app/components/storages/project_storages/oauth_access_grant_nudge_modal_component.rb +++ b/modules/storages/app/components/storages/project_storages/oauth_access_grant_nudge_modal_component.rb @@ -48,6 +48,10 @@ def confirm_button_url ) end + def cancel_button_text + I18n.t("storages.oauth_grant_nudge_modal.cancel_button_label") + end + def find_project_storage(project_storage_record_or_id) return if project_storage_record_or_id.blank? return project_storage_record_or_id if project_storage_record_or_id.is_a?(::Storages::ProjectStorage) diff --git a/modules/storages/config/locales/en.yml b/modules/storages/config/locales/en.yml index 6436c13f9433..d69e59725201 100644 --- a/modules/storages/config/locales/en.yml +++ b/modules/storages/config/locales/en.yml @@ -318,16 +318,17 @@ en: notice_successful_storage_connection: |- Storage connected successfully! Remember to activate the module and the specific storage in the project settings of each desired project to use it. - oauth_grant_nudge_modal: + oauth_access_granted_modal: access_granted: Access granted - access_granted_screen_reader: Access granted. You are now ready to use %{storage}. - body: To get access to the project folder you need to login to %{storage}. + access_granted_screen_reader: "Access granted. You are now ready to add projects to %{storage}" + storage_ready: "You are now ready add projects to %{storage}" + oauth_grant_nudge_modal: + body: In order to add projects to this storage you need to be logged into %{provider_type}. Please, log in and try again. cancel_button_label: I will do it later - confirm_button_aria_label: Login to %{storage} - confirm_button_label: Login + confirm_button_aria_label: "Login to %{storage}" + confirm_button_label: "%{provider_type} log in" + heading: "Login to %{provider_type} required" requesting_access_to: Requesting access to %{storage} - storage_ready: You are now ready to use %{storage} - title: One more step... open_project_storage_modal: success: subtitle: You are being redirected diff --git a/modules/storages/spec/components/storages/admin/storages/oauth_access_grant_nudge_modal_component_spec.rb b/modules/storages/spec/components/storages/admin/storages/oauth_access_grant_nudge_modal_component_spec.rb index 73bd0dd34fa7..8ae056c9b628 100644 --- a/modules/storages/spec/components/storages/admin/storages/oauth_access_grant_nudge_modal_component_spec.rb +++ b/modules/storages/spec/components/storages/admin/storages/oauth_access_grant_nudge_modal_component_spec.rb @@ -35,14 +35,15 @@ it "renders the nudge modal" do render_inline(described_class.new(storage:)) - expect(page).to have_css('[role="alert"]', text: "One more step...", aria: { live: :assertive }) + expect(page).to have_css('[role="alert"]', text: "Login to Nextcloud required", aria: { live: :assertive }) expect(page).to have_test_selector( "oauth-access-grant-nudge-modal-body", - text: "To get access to the project folder you need to login to #{storage.name}." + text: "In order to add projects to this storage you need to be logged into Nextcloud. " \ + "Please, log in and try again." ) - expect(page).to have_button("I will do it later") - expect(page).to have_button("Login", aria: { label: "Login to #{storage.name}" }) + expect(page).to have_button("Close") + expect(page).to have_button("Nextcloud log in", aria: { label: "Login to #{storage.name}" }) end context "with no storage" do diff --git a/modules/storages/spec/components/storages/admin/storages/oauth_access_granted_modal_component_spec.rb b/modules/storages/spec/components/storages/admin/storages/oauth_access_granted_modal_component_spec.rb index 27653183edf1..bd05640798e6 100644 --- a/modules/storages/spec/components/storages/admin/storages/oauth_access_granted_modal_component_spec.rb +++ b/modules/storages/spec/components/storages/admin/storages/oauth_access_granted_modal_component_spec.rb @@ -43,7 +43,7 @@ expect(page).to have_css( "h1.sr-only", - text: "Access granted. You are now ready to use #{storage.name}" + text: "Access granted. You are now ready to add projects to #{storage.name}" ) expect(page).to have_test_selector( @@ -51,11 +51,12 @@ text: "Access granted", aria: { hidden: true } ) - expect(page).to have_test_selector( - "oauth-access-granted-modal-body", - text: "You are now ready to use #{storage.name}", - aria: { hidden: true } - ) + + # expect(page).to have_test_selector( + # "oauth-access-granted-modal-body", + # text: "You are now ready to to add projects to #{storage.name}", + # aria: { hidden: true } + # ) expect(page).to have_button("Close") diff --git a/modules/storages/spec/components/storages/project_storages/oauth_access_grant_nudge_modal_component_spec.rb b/modules/storages/spec/components/storages/project_storages/oauth_access_grant_nudge_modal_component_spec.rb index 7ca41d20deac..3c84c55a3d83 100644 --- a/modules/storages/spec/components/storages/project_storages/oauth_access_grant_nudge_modal_component_spec.rb +++ b/modules/storages/spec/components/storages/project_storages/oauth_access_grant_nudge_modal_component_spec.rb @@ -36,14 +36,16 @@ it "renders the nudge modal" do render_inline(described_class.new(project_storage:)) - expect(page).to have_css('[role="alert"]', text: "One more step...", aria: { live: :assertive }) + expect(page).to have_css('[role="alert"]', text: "Login to Nextcloud required", aria: { live: :assertive }) + expect(page).to have_test_selector( "oauth-access-grant-nudge-modal-body", - text: "To get access to the project folder you need to login to #{storage.name}." + text: "In order to add projects to this storage you need to be logged into Nextcloud. " \ + "Please, log in and try again." ) expect(page).to have_button("I will do it later") - expect(page).to have_button("Login", aria: { label: "Login to #{storage.name}" }) + expect(page).to have_button("Nextcloud log in", aria: { label: "Login to #{storage.name}" }) end context "with no project storage" do diff --git a/modules/storages/spec/features/storages/admin/project_storages_spec.rb b/modules/storages/spec/features/storages/admin/project_storages_spec.rb index c51aab3263ff..5f59524efaf5 100644 --- a/modules/storages/spec/features/storages/admin/project_storages_spec.rb +++ b/modules/storages/spec/features/storages/admin/project_storages_spec.rb @@ -299,8 +299,10 @@ click_on "Add projects" within("dialog") do - wait_for(page).to have_button("Login") - click_on("Login") + wait_for(page).to have_button("Nextcloud log in") + + expect(page).to have_text("Login to Nextcloud required") + click_on("Nextcloud log in") wait_for(page).to have_current_path( %r{/index.php/apps/oauth2/authorize\?client_id=.*&redirect_uri=.*&response_type=code&state=.*} diff --git a/modules/storages/spec/features/storages/project_settings/oauth_access_grant_spec.rb b/modules/storages/spec/features/storages/project_settings/oauth_access_grant_spec.rb index 5ae39aa72beb..aa1ab0c26162 100644 --- a/modules/storages/spec/features/storages/project_settings/oauth_access_grant_spec.rb +++ b/modules/storages/spec/features/storages/project_settings/oauth_access_grant_spec.rb @@ -80,8 +80,8 @@ within_test_selector("oauth-access-grant-nudge-modal") do expect(page).to be_axe_clean - expect(page).to have_text("One more step...") - click_on("Login") + expect(page).to have_text("Login to Nextcloud required") + click_on("Nextcloud log in") wait_for(page).to have_current_path("/index.php/apps/oauth2/authorize?client_id=#{storage.oauth_client.client_id}&" \ "redirect_uri=#{redirect_uri}&response_type=code&state=#{nonce}") end @@ -98,8 +98,8 @@ within_test_selector("oauth-access-grant-nudge-modal") do expect(page).to be_axe_clean - expect(page).to have_text("One more step...") - click_on("Login") + expect(page).to have_text("Login to Nextcloud required") + click_on("Nextcloud log in") wait_for(page).to have_current_path("/index.php/apps/oauth2/authorize?client_id=#{storage.oauth_client.client_id}&" \ "redirect_uri=#{redirect_uri}&response_type=code&state=#{nonce}") end From 16163020f45bbf6d2cd8f380c482a0e3bfc57efb Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Thu, 15 Aug 2024 15:27:07 +0300 Subject: [PATCH 21/91] chore[Op#56922]: update hidden preview --- .../oauth_access_grant_nudge_modal_component_preview.rb | 9 ++++----- .../default.html.erb | 2 +- .../storages/oauth_access_grant_nudge_modal_component.rb | 3 ++- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lookbook/previews/open_project/storages/admin/oauth_access_grant_nudge_modal_component_preview.rb b/lookbook/previews/open_project/storages/admin/oauth_access_grant_nudge_modal_component_preview.rb index ffeb552e93b5..4985cae3ee61 100644 --- a/lookbook/previews/open_project/storages/admin/oauth_access_grant_nudge_modal_component_preview.rb +++ b/lookbook/previews/open_project/storages/admin/oauth_access_grant_nudge_modal_component_preview.rb @@ -2,11 +2,10 @@ module OpenProject::Storages module Admin # @hidden class OAuthAccessGrantNudgeModalComponentPreview < Lookbook::Preview - # Renders a oauth access grant nudge modal component - # @param authorized toggle Denotes whether access has been granted and renders a success state - def default(authorized: false) - project_storage = FactoryBot.build_stubbed(:project_storage) - render_with_template(locals: { project_storage:, authorized:, confirm_button_url: "#" }) + # Renders an oauth access grant nudge modal component + def default + storage = FactoryBot.build_stubbed(:nextcloud_storage) + render_with_template(locals: { storage:, confirm_button_url: "#" }) end end end diff --git a/lookbook/previews/open_project/storages/admin/oauth_access_grant_nudge_modal_component_preview/default.html.erb b/lookbook/previews/open_project/storages/admin/oauth_access_grant_nudge_modal_component_preview/default.html.erb index 9950e6f41500..e1c5a3c38891 100644 --- a/lookbook/previews/open_project/storages/admin/oauth_access_grant_nudge_modal_component_preview/default.html.erb +++ b/lookbook/previews/open_project/storages/admin/oauth_access_grant_nudge_modal_component_preview/default.html.erb @@ -1,4 +1,4 @@

Default
- <%= render(Storages::Admin::OAuthAccessGrantNudgeModalComponent.new(project_storage:, authorized:, confirm_button_url:)) %> + <%= render(Storages::Admin::Storages::OAuthAccessGrantNudgeModalComponent.new(storage:, confirm_button_url:)) %>

diff --git a/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.rb b/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.rb index 351c386fd512..4cc227fd8c20 100644 --- a/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.rb +++ b/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.rb @@ -64,7 +64,8 @@ def confirm_button_aria_label end def confirm_button_url - url_helpers.oauth_access_grant_admin_settings_storage_project_storages_path(storage) + options[:confirm_button_url] || + url_helpers.oauth_access_grant_admin_settings_storage_project_storages_path(storage) end def find_storage(storage_record_or_id) From 485f40bc232d6b21d4edac943a044a9568707c8e Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Thu, 15 Aug 2024 17:02:37 +0300 Subject: [PATCH 22/91] tests[Op#56922]: reinstate test, remove flaky aria assertion --- .../oauth_access_granted_modal_component_spec.rb | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/modules/storages/spec/components/storages/admin/storages/oauth_access_granted_modal_component_spec.rb b/modules/storages/spec/components/storages/admin/storages/oauth_access_granted_modal_component_spec.rb index bd05640798e6..a7f164282c8e 100644 --- a/modules/storages/spec/components/storages/admin/storages/oauth_access_granted_modal_component_spec.rb +++ b/modules/storages/spec/components/storages/admin/storages/oauth_access_granted_modal_component_spec.rb @@ -46,17 +46,8 @@ text: "Access granted. You are now ready to add projects to #{storage.name}" ) - expect(page).to have_test_selector( - "oauth-access-granted-modal-body", - text: "Access granted", - aria: { hidden: true } - ) - - # expect(page).to have_test_selector( - # "oauth-access-granted-modal-body", - # text: "You are now ready to to add projects to #{storage.name}", - # aria: { hidden: true } - # ) + expect(page).to have_content("Access granted") + expect(page).to have_content("You are now ready to add projects to #{storage.name}") expect(page).to have_button("Close") From 13b919cde36f4fb0be640ed4b124488fe7160498 Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Thu, 15 Aug 2024 17:34:15 +0300 Subject: [PATCH 23/91] tests[Op#56922]: normalize i18n --- ...oauth_access_grant_nudge_modal_component.html.erb | 4 ++-- .../oauth_access_grant_nudge_modal_component.rb | 8 ++++---- modules/storages/config/locales/en.yml | 12 ++++++------ 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.html.erb b/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.html.erb index 46bb927797aa..98dae582f9b6 100644 --- a/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.html.erb +++ b/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.html.erb @@ -81,7 +81,7 @@ scheme: :primary, size: :medium, type: :submit, - aria: { label: confirm_button_aria_label }, + aria: { label: login_button_aria_label }, data: { 'storages--oauth-access-grant-nudge-modal-target': 'requestAccessButton', action: 'storages--oauth-access-grant-nudge-modal#requestAccess' @@ -89,7 +89,7 @@ ) ) do |button| button.with_trailing_action_icon(icon: :"link-external") - confirm_button_text + login_button_label end end ) diff --git a/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.rb b/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.rb index 4cc227fd8c20..25c7bc1a034b 100644 --- a/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.rb +++ b/modules/storages/app/components/storages/admin/storages/oauth_access_grant_nudge_modal_component.rb @@ -52,15 +52,15 @@ def render? attr_reader :storage - def confirm_button_text = I18n.t("storages.oauth_grant_nudge_modal.confirm_button_label", provider_type:) + def login_button_label = I18n.t("storages.oauth_grant_nudge_modal.login_button_label", provider_type:) def heading_text = I18n.t("storages.oauth_grant_nudge_modal.heading", provider_type:) def waiting_title = I18n.t("storages.oauth_grant_nudge_modal.requesting_access_to", storage: storage.name) def cancel_button_text = I18n.t(:button_close) - def body_text = I18n.t("storages.oauth_grant_nudge_modal.body", provider_type:) + def body_text = I18n.t("storages.oauth_grant_nudge_modal.description", provider_type:) def provider_type = I18n.t("storages.provider_types.#{storage.short_provider_type}.name") - def confirm_button_aria_label - I18n.t("storages.oauth_grant_nudge_modal.confirm_button_aria_label", storage: storage.name) + def login_button_aria_label + I18n.t("storages.oauth_grant_nudge_modal.login_button_aria_label", storage: storage.name) end def confirm_button_url diff --git a/modules/storages/config/locales/en.yml b/modules/storages/config/locales/en.yml index d69e59725201..187cc8148bd6 100644 --- a/modules/storages/config/locales/en.yml +++ b/modules/storages/config/locales/en.yml @@ -320,14 +320,14 @@ en: of each desired project to use it. oauth_access_granted_modal: access_granted: Access granted - access_granted_screen_reader: "Access granted. You are now ready to add projects to %{storage}" - storage_ready: "You are now ready add projects to %{storage}" + access_granted_screen_reader: Access granted. You are now ready to add projects to %{storage} + storage_ready: You are now ready to add projects to %{storage} oauth_grant_nudge_modal: - body: In order to add projects to this storage you need to be logged into %{provider_type}. Please, log in and try again. cancel_button_label: I will do it later - confirm_button_aria_label: "Login to %{storage}" - confirm_button_label: "%{provider_type} log in" - heading: "Login to %{provider_type} required" + description: In order to add projects to this storage you need to be logged into %{provider_type}. Please, log in and try again. + heading: Login to %{provider_type} required + login_button_aria_label: Login to %{storage} + login_button_label: "%{provider_type} log in" requesting_access_to: Requesting access to %{storage} open_project_storage_modal: success: From dffdca95364d068214432a344b05e3b8ddb6c30c Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Fri, 16 Aug 2024 11:30:21 +0300 Subject: [PATCH 24/91] tests[Op#56922]: cover edit login case --- .../storages/admin/project_storages_spec.rb | 119 +++++++++++------- 1 file changed, 77 insertions(+), 42 deletions(-) diff --git a/modules/storages/spec/features/storages/admin/project_storages_spec.rb b/modules/storages/spec/features/storages/admin/project_storages_spec.rb index 5f59524efaf5..f4d19990791c 100644 --- a/modules/storages/spec/features/storages/admin/project_storages_spec.rb +++ b/modules/storages/spec/features/storages/admin/project_storages_spec.rb @@ -186,36 +186,8 @@ context "when the user has granted OAuth access" do let(:location_picker) { Components::FilePickerDialog.new } - let(:root_xml_response) { build(:webdav_data) } - let(:folder1_xml_response) { build(:webdav_data_folder) } - let(:folder1_fileinfo_response) do - { - ocs: { - data: { - status: "OK", - statuscode: 200, - id: 11, - name: "Folder1", - path: "files/Folder1", - mtime: 1682509719, - ctime: 0 - } - } - } - end - before do - stub_request(:propfind, "#{storage.host}/remote.php/dav/files/#{remote_identity.origin_user_id}") - .to_return(status: 207, body: root_xml_response, headers: {}) - stub_request(:propfind, "#{storage.host}/remote.php/dav/files/#{remote_identity.origin_user_id}/Folder1") - .to_return(status: 207, body: folder1_xml_response, headers: {}) - stub_request(:get, "#{storage.host}/ocs/v1.php/apps/integration_openproject/fileinfo/11") - .to_return(status: 200, body: folder1_fileinfo_response.to_json, headers: {}) - stub_request(:get, "#{storage.host}/ocs/v1.php/cloud/user").to_return(status: 200, body: "{}") - stub_request( - :delete, - "#{storage.host}/remote.php/dav/files/OpenProject/OpenProject/Project%20name%20without%20sequence%20(#{project.id})" - ).to_return(status: 200, body: "", headers: {}) + stub_outbound_storage_files_request_for(storage:, remote_identity:) end it "allows linking a project to a storage" do @@ -252,19 +224,6 @@ expect(page).to have_text(project.name) expect(page).to have_text(subproject.name) - - aggregate_failures "can edit the project folder" do - project_storages_index_page.click_menu_item_of("Edit project folder", project) - - within("dialog") do - choose "No specific folder" - click_on "Save" - end - - project_storages_index_page.within_the_table_row_containing(project.name) do - expect(page).to have_text("No specific folder") - end - end end context "when the user does not select a folder" do @@ -312,6 +271,52 @@ end end + describe "Editing of a project storage" do + let(:project_storage) { create(:project_storage, storage:) } + + before do + project_storage + + login_as(admin) + visit admin_settings_storage_project_storages_path(storage) + end + + it "allows changing the project folder mode" do + project = project_storage.project + project_storages_index_page.click_menu_item_of("Edit project folder", project) + + page.within("dialog") do + choose "No specific folder" + click_on "Save" + end + + project_storages_index_page.within_the_table_row_containing(project.name) do + expect(page).to have_text("No specific folder") + end + end + + context "when oauth access has not been granted and manual selection" do + before do + stub_outbound_storage_files_request_for(storage:, remote_identity:) + end + + it "presents a storage login button to the user" do + OAuthClientToken.where(user: admin, oauth_client: storage.oauth_client).destroy_all + + project_storages_index_page.click_menu_item_of("Edit project folder", project_storage.project) + + within("dialog") do + choose "Existing folder with manually managed permissions" + wait_for(page).to have_button("Nextcloud login") + click_on("Nextcloud login") + wait_for(page).to have_current_path( + %r{/index.php/apps/oauth2/authorize\?client_id=.*&redirect_uri=.*&response_type=code&state=.*} + ) + end + end + end + end + describe "Removal of a project from a storage" do let(:success_delete_service) do Class.new do @@ -366,5 +371,35 @@ def call expect(page).to have_no_text(project.name) end end + + def stub_outbound_storage_files_request_for(storage:, remote_identity:) + root_xml_response = build(:webdav_data) + folder1_xml_response = build(:webdav_data_folder) + folder1_fileinfo_response = { + ocs: { + data: { + status: "OK", + statuscode: 200, + id: 11, + name: "Folder1", + path: "files/Folder1", + mtime: 1682509719, + ctime: 0 + } + } + } + + stub_request(:propfind, "#{storage.host}/remote.php/dav/files/#{remote_identity.origin_user_id}") + .to_return(status: 207, body: root_xml_response, headers: {}) + stub_request(:propfind, "#{storage.host}/remote.php/dav/files/#{remote_identity.origin_user_id}/Folder1") + .to_return(status: 207, body: folder1_xml_response, headers: {}) + stub_request(:get, "#{storage.host}/ocs/v1.php/apps/integration_openproject/fileinfo/11") + .to_return(status: 200, body: folder1_fileinfo_response.to_json, headers: {}) + stub_request(:get, "#{storage.host}/ocs/v1.php/cloud/user").to_return(status: 200, body: "{}") + stub_request( + :delete, + "#{storage.host}/remote.php/dav/files/OpenProject/OpenProject/Project%20name%20without%20sequence%20(#{project.id})" + ).to_return(status: 200, body: "", headers: {}) + end end end From 717d0c1277a0b3d50dc5e177fc22f0662f904766 Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Fri, 16 Aug 2024 12:33:32 +0300 Subject: [PATCH 25/91] chore[Op#56922]: extract oauth access grantable concern --- .../storages/oauth_access_grantable.rb | 62 +++++++++++++++++++ .../admin/project_storages_controller.rb | 39 +++--------- .../storages/project_storages_controller.rb | 26 ++------ 3 files changed, 77 insertions(+), 50 deletions(-) create mode 100644 modules/storages/app/controllers/concerns/storages/oauth_access_grantable.rb diff --git a/modules/storages/app/controllers/concerns/storages/oauth_access_grantable.rb b/modules/storages/app/controllers/concerns/storages/oauth_access_grantable.rb new file mode 100644 index 000000000000..820b0efaed8c --- /dev/null +++ b/modules/storages/app/controllers/concerns/storages/oauth_access_grantable.rb @@ -0,0 +1,62 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Storages + module OAuthAccessGrantable + extend ActiveSupport::Concern + + def open_redirect_to_storage_authorization_with(callback_url:, storage:) + nonce = SecureRandom.uuid + cookies["oauth_state_#{nonce}"] = { + value: { href: callback_url, + storageId: storage.id }.to_json, + expires: 1.hour + } + session[:oauth_callback_flash_modal] = storage_oauth_access_granted_modal(storage:) + redirect_to(storage.oauth_configuration.authorization_uri(state: nonce), allow_other_host: true) + end + + def storage_oauth_access_granted?(storage:) + OAuthClientToken.exists?(user: User.current, oauth_client: storage.oauth_client) + end + + def project_storage_oauth_access_grant_nudge_modal(project_storage:) + { + type: ::Storages::ProjectStorages::OAuthAccessGrantNudgeModalComponent.name, + parameters: { project_storage: project_storage.id } + } + end + + def storage_oauth_access_granted_modal(storage:) + { + type: ::Storages::Admin::Storages::OAuthAccessGrantedModalComponent.name, + parameters: { storage: storage.id } + } + end + end +end diff --git a/modules/storages/app/controllers/storages/admin/project_storages_controller.rb b/modules/storages/app/controllers/storages/admin/project_storages_controller.rb index 931d3fdbd114..bf62970463b0 100644 --- a/modules/storages/app/controllers/storages/admin/project_storages_controller.rb +++ b/modules/storages/app/controllers/storages/admin/project_storages_controller.rb @@ -27,6 +27,8 @@ #++ class Storages::Admin::ProjectStoragesController < Projects::SettingsController + include Storages::OAuthAccessGrantable + model_object Storages::ProjectStorage before_action :find_model_object, only: %i[oauth_access_grant edit update destroy destroy_info] @@ -70,7 +72,7 @@ def create end end - def oauth_access_grant # rubocop:disable Metrics/AbcSize + def oauth_access_grant @project_storage = @object storage = @project_storage.storage auth_state = ::Storages::Peripherals::StorageInteraction::Authentication @@ -79,14 +81,10 @@ def oauth_access_grant # rubocop:disable Metrics/AbcSize if auth_state == :connected redirect_to(external_file_storages_project_settings_project_storages_path) else - nonce = SecureRandom.uuid - cookies["oauth_state_#{nonce}"] = { - value: { href: external_file_storages_project_settings_project_storages_url(project_id: @project_storage.project_id), - storageId: @project_storage.storage_id }.to_json, - expires: 1.hour - } - session[:oauth_callback_flash_modal] = storage_oauth_access_granted_modal(storage:) - redirect_to(storage.oauth_configuration.authorization_uri(state: nonce), allow_other_host: true) + open_redirect_to_storage_authorization_with( + callback_url: external_file_storages_project_settings_project_storages_url(project_id: @project_storage.project_id), + storage: @project_storage.storage + ) end end @@ -156,36 +154,17 @@ def available_storages end def redirect_to_project_storages_path_with_oauth_access_grant_confirmation - if storage_oauth_access_granted? + if storage_oauth_access_granted?(storage: @project_storage.storage) redirect_to external_file_storages_project_settings_project_storages_path else redirect_to_project_storages_path_with_nudge_modal end end - def storage_oauth_access_granted? - OAuthClientToken - .exists?(user: current_user, oauth_client: @project_storage.storage.oauth_client) - end - def redirect_to_project_storages_path_with_nudge_modal redirect_to( external_file_storages_project_settings_project_storages_path, - flash: { modal: oauth_access_grant_nudge_modal(project_storage: @project_storage) } + flash: { modal: project_storage_oauth_access_grant_nudge_modal(project_storage: @project_storage) } ) end - - def oauth_access_grant_nudge_modal(project_storage:) - { - type: Storages::ProjectStorages::OAuthAccessGrantNudgeModalComponent.name, - parameters: { project_storage: project_storage.id } - } - end - - def storage_oauth_access_granted_modal(storage:) - { - type: Storages::Admin::Storages::OAuthAccessGrantedModalComponent.name, - parameters: { storage: storage.id } - } - end end diff --git a/modules/storages/app/controllers/storages/admin/storages/project_storages_controller.rb b/modules/storages/app/controllers/storages/admin/storages/project_storages_controller.rb index afc0e37ebf1f..bf7b4a7fceb9 100644 --- a/modules/storages/app/controllers/storages/admin/storages/project_storages_controller.rb +++ b/modules/storages/app/controllers/storages/admin/storages/project_storages_controller.rb @@ -33,6 +33,7 @@ class Storages::Admin::Storages::ProjectStoragesController < ApplicationControll include OpTurbo::DialogStreamHelper include FlashMessagesOutputSafetyHelper include ApplicationComponentStreams + include Storages::OAuthAccessGrantable layout "admin" @@ -53,7 +54,7 @@ def index; end def new respond_with_dialog( - if storage_oauth_access_granted? + if storage_oauth_access_granted?(storage: @storage) ::Storages::Admin::Storages::ProjectsStorageModalComponent.new( project_storage: @project_storage, last_project_folders: {} ) @@ -64,14 +65,10 @@ def new end def oauth_access_grant - nonce = SecureRandom.uuid - cookies["oauth_state_#{nonce}"] = { - value: { href: admin_settings_storage_project_storages_url(@storage), - storageId: @storage.id }.to_json, - expires: 1.hour - } - session[:oauth_callback_flash_modal] = oauth_access_granted_modal_params - redirect_to(@storage.oauth_configuration.authorization_uri(state: nonce), allow_other_host: true) + open_redirect_to_storage_authorization_with( + callback_url: admin_settings_storage_project_storages_url(@storage), + storage: @storage + ) end def create # rubocop:disable Metrics/AbcSize @@ -205,10 +202,6 @@ def initialize_project_storage .result end - def storage_oauth_access_granted? - OAuthClientToken.exists?(user: current_user, oauth_client: @storage.oauth_client) - end - def include_sub_projects? ActiveRecord::Type::Boolean.new.cast(params.to_unsafe_h[:storages_project_storage][:include_sub_projects]) end @@ -225,11 +218,4 @@ def ensure_storage_configured! respond_with_turbo_streams false end - - def oauth_access_granted_modal_params - { - type: Storages::Admin::Storages::OAuthAccessGrantedModalComponent.name, - parameters: { storage: @storage.id } - } - end end From 58a7130bdfe0fa428220dd5daefa87d97752a2bb Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Thu, 15 Aug 2024 14:37:24 +0200 Subject: [PATCH 26/91] Test some functionality of the tabs of the primerized split screen as it should behave the same way, the old tabs behave --- .../details/tab_component.html.erb | 2 +- .../details/update_counter_component.rb | 2 +- .../config/locales/js-en.yml | 3 +++ .../features/work_package_github_tab_spec.rb | 12 +++++++-- .../support/pages/work_package_github_tab.rb | 4 --- .../config/locales/js-en.yml | 3 +++ modules/meeting/config/locales/js-en.yml | 3 +++ .../notification_center_date_alerts_spec.rb | 6 ++--- .../details/relations/hierarchy_spec.rb | 21 ++++++++++++---- .../details/relations/relations_spec.rb | 25 ++++++++++++++----- .../work_packages/tabs/watcher_tab_spec.rb | 14 ++++++++--- .../components/work_packages/activities.rb | 8 ------ .../work_packages/primerized_tabs.rb | 19 ++++++++++++++ .../work_packages/abstract_work_package.rb | 4 +++ .../primerized_split_work_package.rb | 8 ++---- 15 files changed, 95 insertions(+), 39 deletions(-) create mode 100644 spec/support/components/work_packages/primerized_tabs.rb diff --git a/app/components/work_packages/details/tab_component.html.erb b/app/components/work_packages/details/tab_component.html.erb index 9e47fba192cc..1561aa56938e 100644 --- a/app/components/work_packages/details/tab_component.html.erb +++ b/app/components/work_packages/details/tab_component.html.erb @@ -10,7 +10,7 @@ ) do |c| c.with_text { t("js.work_packages.tabs.#{node.name}") } count = node.badge(work_package:).to_i - c.with_counter(count:, hide_if_zero: true, id: "wp-details-tab-#{node.name}-counter", test_selector: "wp-details-tab-component--tab-counter") + c.with_counter(count:, hide_if_zero: true, id: "wp-details-tab-#{node.name}-counter", test_selector: "wp-details-tab-component--#{node.name}-counter") end end end diff --git a/app/components/work_packages/details/update_counter_component.rb b/app/components/work_packages/details/update_counter_component.rb index 38fef212bafc..48d0c64f4ce8 100644 --- a/app/components/work_packages/details/update_counter_component.rb +++ b/app/components/work_packages/details/update_counter_component.rb @@ -18,7 +18,7 @@ def call .new(count:, hide_if_zero: true, id: wrapper_key, - test_selector: "wp-details-tab-component--tab-counter") + test_selector: "wp-details-tab-component--#{@menu.name}-counter") end # We don't need a wrapper component, but wrap on the counter id diff --git a/modules/github_integration/config/locales/js-en.yml b/modules/github_integration/config/locales/js-en.yml index 8e856069d206..a54613b6a8d0 100644 --- a/modules/github_integration/config/locales/js-en.yml +++ b/modules/github_integration/config/locales/js-en.yml @@ -57,3 +57,6 @@ en: draft: 'drafted' merged: 'merged' ready_for_review: 'marked ready for review' + work_packages: + tabs: + github: "GitHub" diff --git a/modules/github_integration/spec/features/work_package_github_tab_spec.rb b/modules/github_integration/spec/features/work_package_github_tab_spec.rb index 50a59b65bb8f..b2d74548441f 100644 --- a/modules/github_integration/spec/features/work_package_github_tab_spec.rb +++ b/modules/github_integration/spec/features/work_package_github_tab_spec.rb @@ -106,7 +106,7 @@ def expect_clipboard_content(text) it "does not show the github tab" do work_package_page.visit! - github_tab.expect_tab_not_present + work_package_page.expect_no_tab "Github" end end @@ -116,7 +116,7 @@ def expect_clipboard_content(text) it "does not show the github tab" do work_package_page.visit! - github_tab.expect_tab_not_present + work_package_page.expect_no_tab "Github" end end end @@ -132,4 +132,12 @@ def expect_clipboard_content(text) it_behaves_like "a github tab" end + + describe "primerized work package split view" do + let(:work_package_page) { Pages::PrimerizedSplitWorkPackage.new(work_package) } + let(:tabs) { Components::WorkPackages::PrimerizedTabs.new } + let(:github_tab_element) { "github" } + + it_behaves_like "a github tab" + end end diff --git a/modules/github_integration/spec/support/pages/work_package_github_tab.rb b/modules/github_integration/spec/support/pages/work_package_github_tab.rb index 39c3f7d59a13..eeb429ec3906 100644 --- a/modules/github_integration/spec/support/pages/work_package_github_tab.rb +++ b/modules/github_integration/spec/support/pages/work_package_github_tab.rb @@ -55,10 +55,6 @@ def paste_clipboard_content page.send_keys(meta_key, "v") end - def expect_tab_not_present - expect(page).to have_no_css(".op-tab-row--link", text: "GITHUB") - end - private def osx? diff --git a/modules/gitlab_integration/config/locales/js-en.yml b/modules/gitlab_integration/config/locales/js-en.yml index 83c76586c3f3..8db996c5c66b 100644 --- a/modules/gitlab_integration/config/locales/js-en.yml +++ b/modules/gitlab_integration/config/locales/js-en.yml @@ -55,3 +55,6 @@ en: empty: There are no merge requests linked yet. Link an existing MR by using the code OP#%{wp_id} (or PP#%{wp_id} for private links) in the MR title/description or create a new MR. gitlab_pipelines: Pipelines updated_on: Updated on + work_packages: + tabs: + gitlab: "GitLab" diff --git a/modules/meeting/config/locales/js-en.yml b/modules/meeting/config/locales/js-en.yml index d14ff2dc2ec9..1572bbff9c97 100644 --- a/modules/meeting/config/locales/js-en.yml +++ b/modules/meeting/config/locales/js-en.yml @@ -29,3 +29,6 @@ en: js: label_meetings: 'Meetings' + work_packages: + tabs: + meetings: 'Meetings' diff --git a/spec/features/notifications/notification_center/notification_center_date_alerts_spec.rb b/spec/features/notifications/notification_center/notification_center_date_alerts_spec.rb index 96726418d7f9..92512d6a7773 100644 --- a/spec/features/notifications/notification_center/notification_center_date_alerts_spec.rb +++ b/spec/features/notifications/notification_center/notification_center_date_alerts_spec.rb @@ -168,7 +168,7 @@ def create_alertable(**attributes) let(:center) { Pages::Notifications::Center.new } let(:side_menu) { Components::Submenu.new } let(:toaster) { PageObjects::Notifications.new(page) } - let(:activity_tab) { Components::WorkPackages::Activities.new(notification_wp_due_today) } + let(:tabs) { Components::WorkPackages::PrimerizedTabs.new } # Converts "hh:mm" into { hour: h, min: m } def time_hash(time) @@ -254,7 +254,7 @@ def run_create_date_alerts_notifications_job wait_for_network_idle # We expect no badge count - split_screen.expect_no_notification_badge + tabs.expect_no_counter "activity" # The same is true for the mention item that is opened in date alerts filter center.click_item notification_wp_double_date_alert @@ -263,7 +263,7 @@ def run_create_date_alerts_notifications_job wait_for_network_idle # We expect one badge - split_screen.expect_notification_count 1 + tabs.expect_counter "activity", 1 # When a work package is updated to a different date wp_double_notification.update_column(:due_date, time_zone.now + 5.days) diff --git a/spec/features/work_packages/details/relations/hierarchy_spec.rb b/spec/features/work_packages/details/relations/hierarchy_spec.rb index 7394e0f4fffa..a66690e17fa8 100644 --- a/spec/features/work_packages/details/relations/hierarchy_spec.rb +++ b/spec/features/work_packages/details/relations/hierarchy_spec.rb @@ -36,9 +36,6 @@ let(:project) { create(:project) } let(:work_package) { create(:work_package, project:) } let(:relations) { Components::WorkPackages::Relations.new(work_package) } - let(:tabs) { Components::WorkPackages::Tabs.new(work_package) } - - let(:relations_tab) { find(".op-tab-row--link_selected", text: "RELATIONS") } let(:visit) { true } @@ -266,14 +263,28 @@ def visit_relations end end -RSpec.context "Split screen" do +RSpec.context "within a split screen" do let(:wp_page) { Pages::SplitWorkPackage.new(work_package) } + let(:tabs) { Components::WorkPackages::Tabs.new(work_package) } + + let(:relations_tab) { find(".op-tab-row--link_selected", text: "RELATIONS") } it_behaves_like "work package relations tab" end -RSpec.context "Full screen" do +RSpec.context "within a primerized split screen" do + let(:wp_page) { Pages::PrimerizedSplitWorkPackage.new(work_package) } + let(:tabs) { Components::WorkPackages::PrimerizedTabs.new } + let(:relations_tab) { "relations" } + + it_behaves_like "work package relations tab" +end + +RSpec.context "within a full screen" do let(:wp_page) { Pages::FullWorkPackage.new(work_package) } + let(:tabs) { Components::WorkPackages::Tabs.new(work_package) } + + let(:relations_tab) { find(".op-tab-row--link_selected", text: "RELATIONS") } it_behaves_like "work package relations tab" end diff --git a/spec/features/work_packages/details/relations/relations_spec.rb b/spec/features/work_packages/details/relations/relations_spec.rb index 8f2f73e11264..ba513eb55ce9 100644 --- a/spec/features/work_packages/details/relations/relations_spec.rb +++ b/spec/features/work_packages/details/relations/relations_spec.rb @@ -28,19 +28,15 @@ require "spec_helper" -RSpec.describe "Work package relations tab", :js, :selenium do +RSpec.shared_examples "Work package relations tab", :js, :selenium do include_context "ng-select-autocomplete helpers" let(:user) { create(:admin) } let(:project) { create(:project) } let(:work_package) { create(:work_package, project:) } - let(:work_packages_page) { Pages::SplitWorkPackage.new(work_package) } let(:full_wp) { Pages::FullWorkPackage.new(work_package) } let(:relations) { Components::WorkPackages::Relations.new(work_package) } - let(:tabs) { Components::WorkPackages::Tabs.new(work_package) } - - let(:relations_tab) { find(".op-tab-row--link_selected", text: "RELATIONS") } let(:visit) { true } @@ -198,7 +194,7 @@ def visit_relations tabs.expect_counter(relations_tab, 1) # Switch to full view - find(".work-packages--details-fullscreen-icon").click + work_packages_page.switch_to_fullscreen # Expect to have row relations.hover_action(relatable, :delete) @@ -278,3 +274,20 @@ def visit_relations end end end + +RSpec.context "within a split screen" do + let(:work_packages_page) { Pages::SplitWorkPackage.new(work_package) } + let(:tabs) { Components::WorkPackages::Tabs.new(work_package) } + + let(:relations_tab) { find(".op-tab-row--link_selected", text: "RELATIONS") } + + it_behaves_like "Work package relations tab" +end + +RSpec.context "within a primerized split screen" do + let(:work_packages_page) { Pages::PrimerizedSplitWorkPackage.new(work_package) } + let(:tabs) { Components::WorkPackages::PrimerizedTabs.new } + let(:relations_tab) { "relations" } + + it_behaves_like "Work package relations tab" +end diff --git a/spec/features/work_packages/tabs/watcher_tab_spec.rb b/spec/features/work_packages/tabs/watcher_tab_spec.rb index 91a93e617c22..a48105336f25 100644 --- a/spec/features/work_packages/tabs/watcher_tab_spec.rb +++ b/spec/features/work_packages/tabs/watcher_tab_spec.rb @@ -53,7 +53,7 @@ def expect_button_is_not_watching login_as(user) wp_page.visit_tab! :watchers expect_angular_frontend_initialized - expect(page).to have_css(".op-tab-row--link_selected", text: "WATCHERS") + wp_page.expect_tab "Watchers" end it "modifying the watcher list modifies the watch button" do @@ -113,13 +113,21 @@ def expect_button_is_not_watching end end - context "split screen" do + context "within a split screen" do let(:wp_page) { Pages::SplitWorkPackage.new(work_package) } it_behaves_like "watchers tab" end - context "full screen" do + context "within a primerized split screen" do + let(:wp_page) { Pages::PrimerizedSplitWorkPackage.new(work_package) } + let(:tabs) { Components::WorkPackages::PrimerizedTabs.new } + let(:watchers_tab) { "watchers" } + + it_behaves_like "watchers tab" + end + + context "within a full screen" do let(:wp_page) { Pages::FullWorkPackage.new(work_package) } it_behaves_like "watchers tab" diff --git a/spec/support/components/work_packages/activities.rb b/spec/support/components/work_packages/activities.rb index c2d09a309517..8a934eb147b9 100644 --- a/spec/support/components/work_packages/activities.rb +++ b/spec/support/components/work_packages/activities.rb @@ -46,14 +46,6 @@ def expect_wp_has_been_created_activity(work_package) end end - def expect_notification_count(count) - expect(page).to have_css('[data-test-selector="tab-counter-Activity"] span', text: count) - end - - def expect_no_notification_badge - expect(page).to have_no_css('[data-test-selector="tab-counter-Activity"] span') - end - def hover_action(journal_id, action) retry_block do # Focus type edit to expose buttons diff --git a/spec/support/components/work_packages/primerized_tabs.rb b/spec/support/components/work_packages/primerized_tabs.rb new file mode 100644 index 000000000000..f33eb94608a9 --- /dev/null +++ b/spec/support/components/work_packages/primerized_tabs.rb @@ -0,0 +1,19 @@ +module Components + module WorkPackages + class PrimerizedTabs + include Capybara::DSL + include Capybara::RSpecMatchers + include RSpec::Matchers + + # Check value of counter for the given tab + def expect_counter(tab, count) + expect(page).to have_test_selector("wp-details-tab-component--#{tab}-counter", text: count) + end + + # Counter should not be displayed, if there are no relations or watchers + def expect_no_counter(tab) + expect(page).not_to have_test_selector("wp-details-tab-component--#{tab}-counter") + end + end + end +end diff --git a/spec/support/pages/work_packages/abstract_work_package.rb b/spec/support/pages/work_packages/abstract_work_package.rb index d4ffa958c4de..c79bdef2d031 100644 --- a/spec/support/pages/work_packages/abstract_work_package.rb +++ b/spec/support/pages/work_packages/abstract_work_package.rb @@ -53,6 +53,10 @@ def expect_tab(tab) expect(page).to have_css(".op-tab-row--link_selected", text: tab.to_s.upcase) end + def expect_no_tab(tab) + expect(page).to have_no_css(".op-tab-row--link", text: tab.to_s.upcase) + end + def within_active_tab(&) within(".work-packages-full-view--split-right .work-packages--panel-inner", &) end diff --git a/spec/support/pages/work_packages/primerized_split_work_package.rb b/spec/support/pages/work_packages/primerized_split_work_package.rb index 363ff21a6d3b..f1ab2ac3a49d 100644 --- a/spec/support/pages/work_packages/primerized_split_work_package.rb +++ b/spec/support/pages/work_packages/primerized_split_work_package.rb @@ -59,12 +59,8 @@ def within_active_tab(&) within(".work-packages--details-content", &) end - def expect_notification_count(count) - expect(page).to have_test_selector("wp-details-tab-component--tab-counter", text: count) - end - - def expect_no_notification_badge - expect(page).not_to have_test_selector("wp-details-tab-component--tab-counter") + def path(tab = "overview") + details_notifications_path(work_package.id, tab:) end private From 5dddb5932c123a9ae261d9ef07ec92e4f907074a Mon Sep 17 00:00:00 2001 From: ulferts Date: Fri, 16 Aug 2024 15:58:14 +0200 Subject: [PATCH 27/91] add rel="nofollow" to table sort links --- app/helpers/sort_helper.rb | 2 +- .../wp-table/sort-header/sort-header.directive.html | 6 ++++-- spec/helpers/sort_helper_spec.rb | 5 +++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/helpers/sort_helper.rb b/app/helpers/sort_helper.rb index cbbda73fd754..b578490095d4 100644 --- a/app/helpers/sort_helper.rb +++ b/app/helpers/sort_helper.rb @@ -286,7 +286,7 @@ def sort_link(column, caption, default_order, allowed_params: nil, **html_option allowed_params ||= %w[filters per_page expand columns] # Don't lose other params. - link_to_content_update(h(caption), safe_query_params(allowed_params).merge(sort_options), html_options) + link_to_content_update(h(caption), safe_query_params(allowed_params).merge(sort_options), html_options.merge(rel: :nofollow)) end # Returns a table header tag with a sort link for the named column diff --git a/frontend/src/app/features/work-packages/components/wp-table/sort-header/sort-header.directive.html b/frontend/src/app/features/work-packages/components/wp-table/sort-header/sort-header.directive.html index 6ebe2dce1a3d..3be2f295f231 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/sort-header/sort-header.directive.html +++ b/frontend/src/app/features/work-packages/components/wp-table/sort-header/sort-header.directive.html @@ -17,7 +17,8 @@ [ngClass]="[directionClass && 'sort', directionClass]" lang-attribute lang="{{locale}}" - id="{{ headerColumn.id }}">{{headerColumn.name}} + id="{{ headerColumn.id }}" + rel="nofollow">{{headerColumn.name}} {{headerColumn.name}} {{headerColumn.name}} + id="{{ headerColumn.id }}" + rel="nofollow">{{headerColumn.name}} {{headerColumn.name}} diff --git a/spec/helpers/sort_helper_spec.rb b/spec/helpers/sort_helper_spec.rb index 768f5253ec72..29d1ed8ff6d0 100644 --- a/spec/helpers/sort_helper_spec.rb +++ b/spec/helpers/sort_helper_spec.rb @@ -148,6 +148,7 @@ def session; @session ||= {}; end
Id
@@ -166,6 +167,7 @@ def session; @session ||= {}; end
Id
@@ -186,6 +188,7 @@ def session; @session ||= {}; end
Id
@@ -216,6 +219,7 @@ def session; @session ||= {}; end
Id
@@ -235,6 +239,7 @@ def session; @session ||= {}; end
Id
From a8e418bbb367804c53437b6f3cbc3b38c4d526a0 Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Fri, 16 Aug 2024 17:31:28 +0200 Subject: [PATCH 28/91] remove years from copyrights --- .../access_token_created_dialog_component.html.erb | 2 +- .../my/access_token/access_token_created_dialog_component.rb | 2 +- app/components/my/access_token/api/row_component.rb | 2 +- app/components/my/access_token/api/table_component.rb | 2 +- .../my/access_token/api_tokens_section_component.html.erb | 2 +- .../my/access_token/api_tokens_section_component.rb | 2 +- app/components/users/delete_page_header_component.html.erb | 2 +- app/controllers/concerns/work_packages/with_split_view.rb | 2 +- app/controllers/notifications_controller.rb | 2 +- app/helpers/menus/notifications.rb | 2 +- .../set_attributes_service/derive_progress_values_base.rb | 2 +- .../derive_progress_values_status_based.rb | 2 +- .../derive_progress_values_work_based.rb | 2 +- .../set_attributes_service/pre_14_4_derive_progress_values.rb | 2 +- app/views/my/notifications.html.erb | 2 +- app/views/my/reminders.html.erb | 2 +- app/views/users/_toolbar.html.erb | 2 +- db/migrate/20240620115412_add_project_attribute_roles.rb | 2 +- ...805104004_rename_visible_to_admin_only_in_custom_fields.rb | 2 +- db/migrate/20240806144333_create_remote_identities.rb | 2 +- db/migrate/20240808125617_populate_remote_identities.rb | 2 +- .../20240808133947_add_lock_version_to_oauth_client_tokens.rb | 2 +- frontend/src/app/core/navigation/navigation.service.ts | 4 ++-- frontend/src/app/core/navigation/url-params.service.ts | 4 ++-- .../routing/wp-split-view/wp-split-view-entry.component.ts | 4 ++-- .../shared/components/modal/custom-modal-overlay.component.ts | 4 ++-- .../app/shared/components/modal/portal-outlet-target.enum.ts | 4 ++-- .../shared/components/resizer/resizer/wp-resizer.component.ts | 2 +- lib/open_project/custom_field_format_dependent.rb | 2 +- modules/storages/app/helpers/storages/open_storage_links.rb | 2 +- modules/storages/app/services/storages/base_service.rb | 2 +- .../storages/spec/common/storages/open_storage_links_spec.rb | 2 +- .../components/storages/admin/general_info_component_spec.rb | 2 +- .../shared_examples_for_adapters/files_query_examples.rb | 2 +- ...contract_pre_14_4_without_percent_complete_edition_spec.rb | 2 +- ...ntroller_pre_14_4_without_percent_complete_edition_spec.rb | 2 +- ...e_schema_pre_14_4_without_percent_complete_edition_spec.rb | 2 +- ...e_schema_pre_14_4_without_percent_complete_edition_spec.rb | 2 +- ...resenter_pre_14_4_without_percent_complete_edition_spec.rb | 2 +- spec/lib/open_project/custom_field_format_dependent_spec.rb | 2 +- spec/migrations/add_project_attribute_roles_spec.rb | 2 +- .../rename_visible_to_admin_only_in_custom_fields_spec.rb | 2 +- spec/requests/notifications/notifications_spec.rb | 2 +- spec/requests/wiki/menu_items_order_spec.rb | 2 +- .../basic_data/project_custom_field_section_seeder_spec.rb | 2 +- .../derive_progress_values_status_based_spec.rb | 2 +- .../derive_progress_values_work_based_spec.rb | 2 +- ..._service_pre_14_4_without_percent_complete_edition_spec.rb | 2 +- .../pages/work_packages/primerized_split_work_package.rb | 2 +- 49 files changed, 54 insertions(+), 54 deletions(-) diff --git a/app/components/my/access_token/access_token_created_dialog_component.html.erb b/app/components/my/access_token/access_token_created_dialog_component.html.erb index 366a3b613888..bdc7c01fa5d0 100644 --- a/app/components/my/access_token/access_token_created_dialog_component.html.erb +++ b/app/components/my/access_token/access_token_created_dialog_component.html.erb @@ -1,6 +1,6 @@ <%#-- copyright OpenProject is an open source project management software. -Copyright (C) 2012-2024 the OpenProject GmbH +Copyright (C) the OpenProject GmbH This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 3. diff --git a/app/components/my/access_token/access_token_created_dialog_component.rb b/app/components/my/access_token/access_token_created_dialog_component.rb index b9c4924c79ae..4dce63aade45 100644 --- a/app/components/my/access_token/access_token_created_dialog_component.rb +++ b/app/components/my/access_token/access_token_created_dialog_component.rb @@ -1,6 +1,6 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/app/components/my/access_token/api/row_component.rb b/app/components/my/access_token/api/row_component.rb index 6e89dd97804d..70641421e9c6 100644 --- a/app/components/my/access_token/api/row_component.rb +++ b/app/components/my/access_token/api/row_component.rb @@ -2,7 +2,7 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2023 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/app/components/my/access_token/api/table_component.rb b/app/components/my/access_token/api/table_component.rb index 69d4d9fe5b77..dc0cd690e538 100644 --- a/app/components/my/access_token/api/table_component.rb +++ b/app/components/my/access_token/api/table_component.rb @@ -2,7 +2,7 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2023 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/app/components/my/access_token/api_tokens_section_component.html.erb b/app/components/my/access_token/api_tokens_section_component.html.erb index 0e89fa591810..cd6206d5638d 100644 --- a/app/components/my/access_token/api_tokens_section_component.html.erb +++ b/app/components/my/access_token/api_tokens_section_component.html.erb @@ -1,6 +1,6 @@ <%#-- copyright OpenProject is an open source project management software. -Copyright (C) 2012-2024 the OpenProject GmbH +Copyright (C) the OpenProject GmbH This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 3. diff --git a/app/components/my/access_token/api_tokens_section_component.rb b/app/components/my/access_token/api_tokens_section_component.rb index b5faee9bd59b..e6aa64c28352 100644 --- a/app/components/my/access_token/api_tokens_section_component.rb +++ b/app/components/my/access_token/api_tokens_section_component.rb @@ -1,6 +1,6 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/app/components/users/delete_page_header_component.html.erb b/app/components/users/delete_page_header_component.html.erb index a732ba75b105..c96a1e014b73 100644 --- a/app/components/users/delete_page_header_component.html.erb +++ b/app/components/users/delete_page_header_component.html.erb @@ -1,6 +1,6 @@ <%#-- copyright OpenProject is an open source project management software. -Copyright (C) 2012-2024 the OpenProject GmbH +Copyright (C) the OpenProject GmbH This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 3. diff --git a/app/controllers/concerns/work_packages/with_split_view.rb b/app/controllers/concerns/work_packages/with_split_view.rb index 0f9cfe748960..73df17bedf52 100644 --- a/app/controllers/concerns/work_packages/with_split_view.rb +++ b/app/controllers/concerns/work_packages/with_split_view.rb @@ -1,6 +1,6 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb index 2db952aeb488..94181ad56f79 100644 --- a/app/controllers/notifications_controller.rb +++ b/app/controllers/notifications_controller.rb @@ -1,6 +1,6 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/app/helpers/menus/notifications.rb b/app/helpers/menus/notifications.rb index 6921e6922091..fcba2962fa23 100644 --- a/app/helpers/menus/notifications.rb +++ b/app/helpers/menus/notifications.rb @@ -1,6 +1,6 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/app/services/work_packages/set_attributes_service/derive_progress_values_base.rb b/app/services/work_packages/set_attributes_service/derive_progress_values_base.rb index 9c637bc96f26..0138952612d1 100644 --- a/app/services/work_packages/set_attributes_service/derive_progress_values_base.rb +++ b/app/services/work_packages/set_attributes_service/derive_progress_values_base.rb @@ -1,6 +1,6 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/app/services/work_packages/set_attributes_service/derive_progress_values_status_based.rb b/app/services/work_packages/set_attributes_service/derive_progress_values_status_based.rb index 5fdc67429e48..b38db2a6744a 100644 --- a/app/services/work_packages/set_attributes_service/derive_progress_values_status_based.rb +++ b/app/services/work_packages/set_attributes_service/derive_progress_values_status_based.rb @@ -1,6 +1,6 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/app/services/work_packages/set_attributes_service/derive_progress_values_work_based.rb b/app/services/work_packages/set_attributes_service/derive_progress_values_work_based.rb index 155e0796c984..c599d8456876 100644 --- a/app/services/work_packages/set_attributes_service/derive_progress_values_work_based.rb +++ b/app/services/work_packages/set_attributes_service/derive_progress_values_work_based.rb @@ -1,6 +1,6 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/app/services/work_packages/set_attributes_service/pre_14_4_derive_progress_values.rb b/app/services/work_packages/set_attributes_service/pre_14_4_derive_progress_values.rb index a1766f16c601..0248a413bf8f 100644 --- a/app/services/work_packages/set_attributes_service/pre_14_4_derive_progress_values.rb +++ b/app/services/work_packages/set_attributes_service/pre_14_4_derive_progress_values.rb @@ -1,6 +1,6 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/app/views/my/notifications.html.erb b/app/views/my/notifications.html.erb index 98c0331d9304..645df5f0ac7c 100644 --- a/app/views/my/notifications.html.erb +++ b/app/views/my/notifications.html.erb @@ -1,6 +1,6 @@ <%#-- copyright OpenProject is an open source project management software. -Copyright (C) 2012-2024 the OpenProject GmbH +Copyright (C) the OpenProject GmbH This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 3. diff --git a/app/views/my/reminders.html.erb b/app/views/my/reminders.html.erb index a1f7177d3b7f..fc77cbf56a2e 100644 --- a/app/views/my/reminders.html.erb +++ b/app/views/my/reminders.html.erb @@ -1,6 +1,6 @@ <%#-- copyright OpenProject is an open source project management software. -Copyright (C) 2012-2024 the OpenProject GmbH +Copyright (C) the OpenProject GmbH This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 3. diff --git a/app/views/users/_toolbar.html.erb b/app/views/users/_toolbar.html.erb index 1f06c344f1ac..5140af359e80 100644 --- a/app/views/users/_toolbar.html.erb +++ b/app/views/users/_toolbar.html.erb @@ -1,6 +1,6 @@ <%#-- copyright OpenProject is an open source project management software. -Copyright (C) 2012-2024 the OpenProject GmbH +Copyright (C) the OpenProject GmbH This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 3. diff --git a/db/migrate/20240620115412_add_project_attribute_roles.rb b/db/migrate/20240620115412_add_project_attribute_roles.rb index e7a43d647fe5..296c3743c859 100644 --- a/db/migrate/20240620115412_add_project_attribute_roles.rb +++ b/db/migrate/20240620115412_add_project_attribute_roles.rb @@ -1,6 +1,6 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/db/migrate/20240805104004_rename_visible_to_admin_only_in_custom_fields.rb b/db/migrate/20240805104004_rename_visible_to_admin_only_in_custom_fields.rb index ca00f1180ebd..be29d9ad42af 100644 --- a/db/migrate/20240805104004_rename_visible_to_admin_only_in_custom_fields.rb +++ b/db/migrate/20240805104004_rename_visible_to_admin_only_in_custom_fields.rb @@ -1,6 +1,6 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/db/migrate/20240806144333_create_remote_identities.rb b/db/migrate/20240806144333_create_remote_identities.rb index aba4bd9e7f8f..c6829fdad92b 100644 --- a/db/migrate/20240806144333_create_remote_identities.rb +++ b/db/migrate/20240806144333_create_remote_identities.rb @@ -2,7 +2,7 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/db/migrate/20240808125617_populate_remote_identities.rb b/db/migrate/20240808125617_populate_remote_identities.rb index f526f1e084b4..e315596abee5 100644 --- a/db/migrate/20240808125617_populate_remote_identities.rb +++ b/db/migrate/20240808125617_populate_remote_identities.rb @@ -2,7 +2,7 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/db/migrate/20240808133947_add_lock_version_to_oauth_client_tokens.rb b/db/migrate/20240808133947_add_lock_version_to_oauth_client_tokens.rb index 69d1f347f8d0..6ed2a7ff4c10 100644 --- a/db/migrate/20240808133947_add_lock_version_to_oauth_client_tokens.rb +++ b/db/migrate/20240808133947_add_lock_version_to_oauth_client_tokens.rb @@ -2,7 +2,7 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/frontend/src/app/core/navigation/navigation.service.ts b/frontend/src/app/core/navigation/navigation.service.ts index 0b775e55f0f7..72c6a18f0369 100644 --- a/frontend/src/app/core/navigation/navigation.service.ts +++ b/frontend/src/app/core/navigation/navigation.service.ts @@ -1,6 +1,6 @@ -// -- copyright +//-- copyright // OpenProject is an open source project management software. -// Copyright (C) 2012-2024 the OpenProject GmbH +// Copyright (C) the OpenProject GmbH // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License version 3. diff --git a/frontend/src/app/core/navigation/url-params.service.ts b/frontend/src/app/core/navigation/url-params.service.ts index 735d4fe31430..7cf07168c82e 100644 --- a/frontend/src/app/core/navigation/url-params.service.ts +++ b/frontend/src/app/core/navigation/url-params.service.ts @@ -1,6 +1,6 @@ -// -- copyright +//-- copyright // OpenProject is an open source project management software. -// Copyright (C) 2012-2024 the OpenProject GmbH +// Copyright (C) the OpenProject GmbH // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License version 3. diff --git a/frontend/src/app/features/work-packages/routing/wp-split-view/wp-split-view-entry.component.ts b/frontend/src/app/features/work-packages/routing/wp-split-view/wp-split-view-entry.component.ts index 7ef0961f93a0..43a3942d7d9b 100644 --- a/frontend/src/app/features/work-packages/routing/wp-split-view/wp-split-view-entry.component.ts +++ b/frontend/src/app/features/work-packages/routing/wp-split-view/wp-split-view-entry.component.ts @@ -1,6 +1,6 @@ -// -- copyright +//-- copyright // OpenProject is an open source project management software. -// Copyright (C) 2012-2024 the OpenProject GmbH +// Copyright (C) the OpenProject GmbH // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License version 3. diff --git a/frontend/src/app/shared/components/modal/custom-modal-overlay.component.ts b/frontend/src/app/shared/components/modal/custom-modal-overlay.component.ts index a919192fced3..4ca58caceb99 100644 --- a/frontend/src/app/shared/components/modal/custom-modal-overlay.component.ts +++ b/frontend/src/app/shared/components/modal/custom-modal-overlay.component.ts @@ -1,6 +1,6 @@ -// -- copyright +//-- copyright // OpenProject is an open source project management software. -// Copyright (C) 2012-2024 the OpenProject GmbH +// Copyright (C) the OpenProject GmbH // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License version 3. diff --git a/frontend/src/app/shared/components/modal/portal-outlet-target.enum.ts b/frontend/src/app/shared/components/modal/portal-outlet-target.enum.ts index 35fe06e77d92..1aa057d5bf12 100644 --- a/frontend/src/app/shared/components/modal/portal-outlet-target.enum.ts +++ b/frontend/src/app/shared/components/modal/portal-outlet-target.enum.ts @@ -1,6 +1,6 @@ -// -- copyright +//-- copyright // OpenProject is an open source project management software. -// Copyright (C) 2012-2024 the OpenProject GmbH +// Copyright (C) the OpenProject GmbH // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License version 3. diff --git a/frontend/src/app/shared/components/resizer/resizer/wp-resizer.component.ts b/frontend/src/app/shared/components/resizer/resizer/wp-resizer.component.ts index fe7a04d7950c..75d7426a33e2 100644 --- a/frontend/src/app/shared/components/resizer/resizer/wp-resizer.component.ts +++ b/frontend/src/app/shared/components/resizer/resizer/wp-resizer.component.ts @@ -1,4 +1,4 @@ -// -- copyright +//-- copyright // OpenProject is an open source project management software. // Copyright (C) the OpenProject GmbH // diff --git a/lib/open_project/custom_field_format_dependent.rb b/lib/open_project/custom_field_format_dependent.rb index 75a7db502056..e307dba16abb 100644 --- a/lib/open_project/custom_field_format_dependent.rb +++ b/lib/open_project/custom_field_format_dependent.rb @@ -1,6 +1,6 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/modules/storages/app/helpers/storages/open_storage_links.rb b/modules/storages/app/helpers/storages/open_storage_links.rb index ddc50c4c276f..a32675f58f5f 100644 --- a/modules/storages/app/helpers/storages/open_storage_links.rb +++ b/modules/storages/app/helpers/storages/open_storage_links.rb @@ -2,7 +2,7 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/modules/storages/app/services/storages/base_service.rb b/modules/storages/app/services/storages/base_service.rb index df3e08f70ffb..a5dea3f631f5 100644 --- a/modules/storages/app/services/storages/base_service.rb +++ b/modules/storages/app/services/storages/base_service.rb @@ -2,7 +2,7 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2023 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/modules/storages/spec/common/storages/open_storage_links_spec.rb b/modules/storages/spec/common/storages/open_storage_links_spec.rb index 9836f059fb75..daf7689820e8 100644 --- a/modules/storages/spec/common/storages/open_storage_links_spec.rb +++ b/modules/storages/spec/common/storages/open_storage_links_spec.rb @@ -2,7 +2,7 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/modules/storages/spec/components/storages/admin/general_info_component_spec.rb b/modules/storages/spec/components/storages/admin/general_info_component_spec.rb index 2b9b508b9916..328d8cd4003d 100644 --- a/modules/storages/spec/components/storages/admin/general_info_component_spec.rb +++ b/modules/storages/spec/components/storages/admin/general_info_component_spec.rb @@ -2,7 +2,7 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/modules/storages/spec/support/shared_examples_for_adapters/files_query_examples.rb b/modules/storages/spec/support/shared_examples_for_adapters/files_query_examples.rb index 4abb3edad1aa..7e68098f9c59 100644 --- a/modules/storages/spec/support/shared_examples_for_adapters/files_query_examples.rb +++ b/modules/storages/spec/support/shared_examples_for_adapters/files_query_examples.rb @@ -2,7 +2,7 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/spec/contracts/work_packages/base_contract_pre_14_4_without_percent_complete_edition_spec.rb b/spec/contracts/work_packages/base_contract_pre_14_4_without_percent_complete_edition_spec.rb index 73c7c7d24b28..e66dccce3282 100644 --- a/spec/contracts/work_packages/base_contract_pre_14_4_without_percent_complete_edition_spec.rb +++ b/spec/contracts/work_packages/base_contract_pre_14_4_without_percent_complete_edition_spec.rb @@ -1,6 +1,6 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/spec/controllers/work_packages/progress_controller_pre_14_4_without_percent_complete_edition_spec.rb b/spec/controllers/work_packages/progress_controller_pre_14_4_without_percent_complete_edition_spec.rb index b8645014514f..7af7490b168c 100644 --- a/spec/controllers/work_packages/progress_controller_pre_14_4_without_percent_complete_edition_spec.rb +++ b/spec/controllers/work_packages/progress_controller_pre_14_4_without_percent_complete_edition_spec.rb @@ -2,7 +2,7 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/spec/lib/api/v3/work_packages/schema/specific_work_package_schema_pre_14_4_without_percent_complete_edition_spec.rb b/spec/lib/api/v3/work_packages/schema/specific_work_package_schema_pre_14_4_without_percent_complete_edition_spec.rb index 213bc3d8fb97..325b4cff41f3 100644 --- a/spec/lib/api/v3/work_packages/schema/specific_work_package_schema_pre_14_4_without_percent_complete_edition_spec.rb +++ b/spec/lib/api/v3/work_packages/schema/specific_work_package_schema_pre_14_4_without_percent_complete_edition_spec.rb @@ -1,6 +1,6 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/spec/lib/api/v3/work_packages/schema/typed_work_package_schema_pre_14_4_without_percent_complete_edition_spec.rb b/spec/lib/api/v3/work_packages/schema/typed_work_package_schema_pre_14_4_without_percent_complete_edition_spec.rb index 56a949c57853..0384c323fe52 100644 --- a/spec/lib/api/v3/work_packages/schema/typed_work_package_schema_pre_14_4_without_percent_complete_edition_spec.rb +++ b/spec/lib/api/v3/work_packages/schema/typed_work_package_schema_pre_14_4_without_percent_complete_edition_spec.rb @@ -1,6 +1,6 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_pre_14_4_without_percent_complete_edition_spec.rb b/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_pre_14_4_without_percent_complete_edition_spec.rb index 5ed76ecff2c0..475ff12cd28d 100644 --- a/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_pre_14_4_without_percent_complete_edition_spec.rb +++ b/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_pre_14_4_without_percent_complete_edition_spec.rb @@ -1,6 +1,6 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/spec/lib/open_project/custom_field_format_dependent_spec.rb b/spec/lib/open_project/custom_field_format_dependent_spec.rb index 887fbd1599f1..dfeaa3a05b68 100644 --- a/spec/lib/open_project/custom_field_format_dependent_spec.rb +++ b/spec/lib/open_project/custom_field_format_dependent_spec.rb @@ -1,6 +1,6 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/spec/migrations/add_project_attribute_roles_spec.rb b/spec/migrations/add_project_attribute_roles_spec.rb index 5fe14ff3807a..88b31659ce6f 100644 --- a/spec/migrations/add_project_attribute_roles_spec.rb +++ b/spec/migrations/add_project_attribute_roles_spec.rb @@ -1,6 +1,6 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/spec/migrations/rename_visible_to_admin_only_in_custom_fields_spec.rb b/spec/migrations/rename_visible_to_admin_only_in_custom_fields_spec.rb index b6ac4a196064..3e5cf99016eb 100644 --- a/spec/migrations/rename_visible_to_admin_only_in_custom_fields_spec.rb +++ b/spec/migrations/rename_visible_to_admin_only_in_custom_fields_spec.rb @@ -1,6 +1,6 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/spec/requests/notifications/notifications_spec.rb b/spec/requests/notifications/notifications_spec.rb index 7bab45f14073..cbcda71bd58e 100644 --- a/spec/requests/notifications/notifications_spec.rb +++ b/spec/requests/notifications/notifications_spec.rb @@ -1,6 +1,6 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/spec/requests/wiki/menu_items_order_spec.rb b/spec/requests/wiki/menu_items_order_spec.rb index 3d0b90b20af4..7d9daafde5d4 100644 --- a/spec/requests/wiki/menu_items_order_spec.rb +++ b/spec/requests/wiki/menu_items_order_spec.rb @@ -1,6 +1,6 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/spec/seeders/basic_data/project_custom_field_section_seeder_spec.rb b/spec/seeders/basic_data/project_custom_field_section_seeder_spec.rb index d4ff994cefd7..da65a7931a31 100644 --- a/spec/seeders/basic_data/project_custom_field_section_seeder_spec.rb +++ b/spec/seeders/basic_data/project_custom_field_section_seeder_spec.rb @@ -2,7 +2,7 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/spec/services/work_packages/set_attributes_service/derive_progress_values_status_based_spec.rb b/spec/services/work_packages/set_attributes_service/derive_progress_values_status_based_spec.rb index 6d695996233d..c85276764673 100644 --- a/spec/services/work_packages/set_attributes_service/derive_progress_values_status_based_spec.rb +++ b/spec/services/work_packages/set_attributes_service/derive_progress_values_status_based_spec.rb @@ -1,6 +1,6 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/spec/services/work_packages/set_attributes_service/derive_progress_values_work_based_spec.rb b/spec/services/work_packages/set_attributes_service/derive_progress_values_work_based_spec.rb index 88eebf1b8eee..b9ab6e88c34a 100644 --- a/spec/services/work_packages/set_attributes_service/derive_progress_values_work_based_spec.rb +++ b/spec/services/work_packages/set_attributes_service/derive_progress_values_work_based_spec.rb @@ -1,6 +1,6 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/spec/services/work_packages/set_attributes_service_pre_14_4_without_percent_complete_edition_spec.rb b/spec/services/work_packages/set_attributes_service_pre_14_4_without_percent_complete_edition_spec.rb index e0b9671a0fb6..1444b7e4d6a4 100644 --- a/spec/services/work_packages/set_attributes_service_pre_14_4_without_percent_complete_edition_spec.rb +++ b/spec/services/work_packages/set_attributes_service_pre_14_4_without_percent_complete_edition_spec.rb @@ -1,6 +1,6 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. diff --git a/spec/support/pages/work_packages/primerized_split_work_package.rb b/spec/support/pages/work_packages/primerized_split_work_package.rb index 363ff21a6d3b..424928daac1b 100644 --- a/spec/support/pages/work_packages/primerized_split_work_package.rb +++ b/spec/support/pages/work_packages/primerized_split_work_package.rb @@ -1,6 +1,6 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) 2012-2024 the OpenProject GmbH +# Copyright (C) the OpenProject GmbH # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License version 3. From 73c56e33eed147d6843756fb59b840b8d2614436 Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Fri, 16 Aug 2024 18:52:03 +0200 Subject: [PATCH 29/91] fix copyright years for files which got them from 7cf11bb68997b886986ee06613e5fd6e8f12c4af --- .../placeholder_users/placeholder_user_filter_component.rb | 2 +- app/components/placeholder_users/row_component.rb | 2 +- app/components/placeholder_users/table_component.rb | 2 +- app/controllers/placeholder_users/memberships_controller.rb | 2 +- app/models/placeholder_user.rb | 2 +- app/models/queries/notifications.rb | 2 +- app/models/queries/notifications/notification_query.rb | 2 +- app/models/queries/placeholder_users/placeholder_user_query.rb | 2 +- app/views/individual_principals/_memberships.html.erb | 2 +- app/views/placeholder_users/_general.html.erb | 2 +- app/views/placeholder_users/index.html.erb | 2 +- app/views/placeholder_users/new.html.erb | 2 +- app/workers/cron/clear_tmp_cache_job.rb | 2 +- lib/api/v3/actions/action_sql_representer.rb | 2 +- lib/api/v3/projects/project_sql_representer.rb | 2 +- lib/api/v3/utilities/sql_walker_results.rb | 2 +- modules/calendar/app/components/calendar/row_component.rb | 2 +- .../app/components/team_planner/overview/table_component.rb | 2 +- .../team_planner/app/components/team_planner/row_component.rb | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/app/components/placeholder_users/placeholder_user_filter_component.rb b/app/components/placeholder_users/placeholder_user_filter_component.rb index 5afa134f538c..7188d1b20d83 100644 --- a/app/components/placeholder_users/placeholder_user_filter_component.rb +++ b/app/components/placeholder_users/placeholder_user_filter_component.rb @@ -6,7 +6,7 @@ # 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-2017 Jean-Philippe Lang +# 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 diff --git a/app/components/placeholder_users/row_component.rb b/app/components/placeholder_users/row_component.rb index 29556731ffa0..a3521b287f53 100644 --- a/app/components/placeholder_users/row_component.rb +++ b/app/components/placeholder_users/row_component.rb @@ -6,7 +6,7 @@ # 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-2017 Jean-Philippe Lang +# 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 diff --git a/app/components/placeholder_users/table_component.rb b/app/components/placeholder_users/table_component.rb index 450a7f50e3f1..d43d368a3c3e 100644 --- a/app/components/placeholder_users/table_component.rb +++ b/app/components/placeholder_users/table_component.rb @@ -6,7 +6,7 @@ # 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-2017 Jean-Philippe Lang +# 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 diff --git a/app/controllers/placeholder_users/memberships_controller.rb b/app/controllers/placeholder_users/memberships_controller.rb index 43997df5d8f1..7b164709921f 100644 --- a/app/controllers/placeholder_users/memberships_controller.rb +++ b/app/controllers/placeholder_users/memberships_controller.rb @@ -6,7 +6,7 @@ # 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-2017 Jean-Philippe Lang +# 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 diff --git a/app/models/placeholder_user.rb b/app/models/placeholder_user.rb index edb42eec151f..61b3a826cbc8 100644 --- a/app/models/placeholder_user.rb +++ b/app/models/placeholder_user.rb @@ -6,7 +6,7 @@ # 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-2017 Jean-Philippe Lang +# 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 diff --git a/app/models/queries/notifications.rb b/app/models/queries/notifications.rb index ddb7beb0a338..538d3987194c 100644 --- a/app/models/queries/notifications.rb +++ b/app/models/queries/notifications.rb @@ -6,7 +6,7 @@ # 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-2017 Jean-Philippe Lang +# 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 diff --git a/app/models/queries/notifications/notification_query.rb b/app/models/queries/notifications/notification_query.rb index ca941033bcf5..2bb91ec02b68 100644 --- a/app/models/queries/notifications/notification_query.rb +++ b/app/models/queries/notifications/notification_query.rb @@ -6,7 +6,7 @@ # 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-2017 Jean-Philippe Lang +# 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 diff --git a/app/models/queries/placeholder_users/placeholder_user_query.rb b/app/models/queries/placeholder_users/placeholder_user_query.rb index ae89cb42944c..ee592f7b7d9f 100644 --- a/app/models/queries/placeholder_users/placeholder_user_query.rb +++ b/app/models/queries/placeholder_users/placeholder_user_query.rb @@ -6,7 +6,7 @@ # 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-2017 Jean-Philippe Lang +# 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 diff --git a/app/views/individual_principals/_memberships.html.erb b/app/views/individual_principals/_memberships.html.erb index 5879ceaa5816..f23a26effa8e 100644 --- a/app/views/individual_principals/_memberships.html.erb +++ b/app/views/individual_principals/_memberships.html.erb @@ -6,7 +6,7 @@ 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-2017 Jean-Philippe Lang +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 diff --git a/app/views/placeholder_users/_general.html.erb b/app/views/placeholder_users/_general.html.erb index c05de326a808..9c11d9316a59 100644 --- a/app/views/placeholder_users/_general.html.erb +++ b/app/views/placeholder_users/_general.html.erb @@ -6,7 +6,7 @@ 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-2017 Jean-Philippe Lang +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 diff --git a/app/views/placeholder_users/index.html.erb b/app/views/placeholder_users/index.html.erb index 5baf1740ff90..0f551607a4de 100644 --- a/app/views/placeholder_users/index.html.erb +++ b/app/views/placeholder_users/index.html.erb @@ -6,7 +6,7 @@ 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-2017 Jean-Philippe Lang +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 diff --git a/app/views/placeholder_users/new.html.erb b/app/views/placeholder_users/new.html.erb index 1c9d01987024..30eeecc32614 100644 --- a/app/views/placeholder_users/new.html.erb +++ b/app/views/placeholder_users/new.html.erb @@ -6,7 +6,7 @@ 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-2017 Jean-Philippe Lang +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 diff --git a/app/workers/cron/clear_tmp_cache_job.rb b/app/workers/cron/clear_tmp_cache_job.rb index df8f394ee370..b07a08c748f5 100644 --- a/app/workers/cron/clear_tmp_cache_job.rb +++ b/app/workers/cron/clear_tmp_cache_job.rb @@ -6,7 +6,7 @@ # 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-2017 Jean-Philippe Lang +# 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 diff --git a/lib/api/v3/actions/action_sql_representer.rb b/lib/api/v3/actions/action_sql_representer.rb index 777267a7525a..4ed1cd692552 100644 --- a/lib/api/v3/actions/action_sql_representer.rb +++ b/lib/api/v3/actions/action_sql_representer.rb @@ -6,7 +6,7 @@ # 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-2017 Jean-Philippe Lang +# 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 diff --git a/lib/api/v3/projects/project_sql_representer.rb b/lib/api/v3/projects/project_sql_representer.rb index 9cec3c7860bb..8eb8167e10d7 100644 --- a/lib/api/v3/projects/project_sql_representer.rb +++ b/lib/api/v3/projects/project_sql_representer.rb @@ -6,7 +6,7 @@ # 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-2017 Jean-Philippe Lang +# 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 diff --git a/lib/api/v3/utilities/sql_walker_results.rb b/lib/api/v3/utilities/sql_walker_results.rb index 88c3672d130d..97b37e77fad5 100644 --- a/lib/api/v3/utilities/sql_walker_results.rb +++ b/lib/api/v3/utilities/sql_walker_results.rb @@ -6,7 +6,7 @@ # 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-2017 Jean-Philippe Lang +# 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 diff --git a/modules/calendar/app/components/calendar/row_component.rb b/modules/calendar/app/components/calendar/row_component.rb index 26b54500ca06..2406a97e61ef 100644 --- a/modules/calendar/app/components/calendar/row_component.rb +++ b/modules/calendar/app/components/calendar/row_component.rb @@ -6,7 +6,7 @@ # 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-2017 Jean-Philippe Lang +# 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 diff --git a/modules/team_planner/app/components/team_planner/overview/table_component.rb b/modules/team_planner/app/components/team_planner/overview/table_component.rb index a546de07f0ba..91b5b4911ba9 100644 --- a/modules/team_planner/app/components/team_planner/overview/table_component.rb +++ b/modules/team_planner/app/components/team_planner/overview/table_component.rb @@ -6,7 +6,7 @@ # 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-2017 Jean-Philippe Lang +# 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 diff --git a/modules/team_planner/app/components/team_planner/row_component.rb b/modules/team_planner/app/components/team_planner/row_component.rb index 4a911b23c4ab..fce22a3c579c 100644 --- a/modules/team_planner/app/components/team_planner/row_component.rb +++ b/modules/team_planner/app/components/team_planner/row_component.rb @@ -6,7 +6,7 @@ # 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-2017 Jean-Philippe Lang +# 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 From 42bcde050a2de617c09983d7ed167bc038c883fc Mon Sep 17 00:00:00 2001 From: Ivan Kuchin Date: Fri, 16 Aug 2024 19:06:57 +0200 Subject: [PATCH 30/91] fix copyright years for files which got them from a18954b2c9c8a1b3f2891cff9910b2b96306b7c0 --- app/components/individual_principal_base_filter_component.rb | 2 +- app/components/user_filter_component.rb | 2 +- app/controllers/placeholder_users_controller.rb | 2 +- app/models/queries/placeholder_users.rb | 2 +- app/views/placeholder_users/_form.html.erb | 2 +- app/views/placeholder_users/edit.html.erb | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/components/individual_principal_base_filter_component.rb b/app/components/individual_principal_base_filter_component.rb index c8dc7d32d949..7e1291b6cfe9 100644 --- a/app/components/individual_principal_base_filter_component.rb +++ b/app/components/individual_principal_base_filter_component.rb @@ -8,7 +8,7 @@ # 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-2017 Jean-Philippe Lang +# 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 diff --git a/app/components/user_filter_component.rb b/app/components/user_filter_component.rb index 009df75a4095..42c49f8d8cb6 100644 --- a/app/components/user_filter_component.rb +++ b/app/components/user_filter_component.rb @@ -6,7 +6,7 @@ # 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-2017 Jean-Philippe Lang +# 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 diff --git a/app/controllers/placeholder_users_controller.rb b/app/controllers/placeholder_users_controller.rb index 15caa91125c9..82d6660e274c 100644 --- a/app/controllers/placeholder_users_controller.rb +++ b/app/controllers/placeholder_users_controller.rb @@ -6,7 +6,7 @@ # 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-2017 Jean-Philippe Lang +# 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 diff --git a/app/models/queries/placeholder_users.rb b/app/models/queries/placeholder_users.rb index a98c53f8eea7..c2dabcf62664 100644 --- a/app/models/queries/placeholder_users.rb +++ b/app/models/queries/placeholder_users.rb @@ -6,7 +6,7 @@ # 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-2017 Jean-Philippe Lang +# 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 diff --git a/app/views/placeholder_users/_form.html.erb b/app/views/placeholder_users/_form.html.erb index 7ce48536b06b..4fe191575a4a 100644 --- a/app/views/placeholder_users/_form.html.erb +++ b/app/views/placeholder_users/_form.html.erb @@ -6,7 +6,7 @@ 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-2017 Jean-Philippe Lang +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 diff --git a/app/views/placeholder_users/edit.html.erb b/app/views/placeholder_users/edit.html.erb index fdf6a11debbd..f644b403763e 100644 --- a/app/views/placeholder_users/edit.html.erb +++ b/app/views/placeholder_users/edit.html.erb @@ -6,7 +6,7 @@ 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-2017 Jean-Philippe Lang +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 From ac3940634732a1b7a24cc3929e960b493732c381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Tue, 13 Aug 2024 19:19:09 +0200 Subject: [PATCH 31/91] Replace dynamic bootstrapped components with angular elements --- app/helpers/application_helper.rb | 32 +-- app/helpers/custom_fields_helper.rb | 4 +- app/views/admin/backups/show.html.erb | 2 +- .../settings/projects_settings/show.html.erb | 15 +- .../_enterprise_feature_hint.html.erb | 2 +- .../show.html.erb | 2 +- app/views/attribute_help_texts/_tab.html.erb | 2 +- .../augmented/_collapsible_section.html.erb | 6 +- .../colors/_color_autocomplete_field.html.erb | 2 +- app/views/common/_tabs.html.erb | 2 +- app/views/common/upsale.html.erb | 2 +- app/views/custom_actions/_form.html.erb | 9 +- app/views/custom_styles/show.html.erb | 6 +- app/views/enterprises/_current.html.erb | 4 +- app/views/enterprises/_info.html.erb | 2 +- app/views/filters/_boolean.html.erb | 2 +- .../filters/date/_between_dates.html.erb | 4 +- app/views/filters/date/_on_date.html.erb | 2 +- .../homescreen/blocks/_new_features.html.erb | 2 +- app/views/layouts/base.html.erb | 10 +- app/views/members/_member_form.html.erb | 2 +- .../repositories/_repository_header.html.erb | 8 +- app/views/search/index.html.erb | 6 +- app/views/statuses/_form.html.erb | 2 +- .../types/form/_form_configuration.html.erb | 6 +- app/views/versions/show.html.erb | 4 +- app/views/wiki/_page_form.html.erb | 2 +- app/views/work_packages/bulk/edit.html.erb | 4 +- app/views/work_packages/moves/new.html.erb | 4 +- .../application-architecture/README.md | 2 +- frontend/.eslintrc.js | 2 + frontend/src/app/app.module.ts | 138 +++++++++-- ...al-search-work-packages-entry.component.ts | 46 ---- .../global-search-work-packages.component.ts | 28 ++- .../openproject-global-search.module.ts | 4 - .../tabs/global-search-tabs.component.ts | 12 +- .../title/global-search-title.component.ts | 13 +- .../main-menu/main-menu-toggle.component.ts | 7 +- .../setup/global-dynamic-components.const.ts | 215 ------------------ .../components/admin/backup.component.ts | 12 +- .../setup/globals/dynamic-bootstrapper.ts | 129 ----------- frontend/src/app/core/setup/init-globals.ts | 1 - .../editable-query-props.component.ts | 11 +- .../type-form-configuration.component.ts | 4 +- .../ee-active-saved-trial.component.ts | 3 +- .../enterprise/enterprise-base.component.ts | 3 +- .../free-trial-button.component.ts | 11 +- .../blocks/new-features.component.ts | 4 +- .../in-app-notification-bell.component.ts | 3 +- .../app/features/plugins/plugin-context.ts | 26 +-- .../user/user-activity.component.ts | 13 +- .../zen-mode-toggle-button.component.ts | 3 +- .../custom-date-action-admin.component.ts | 7 +- .../components/wp-new/wp-new-full-view.html | 2 +- .../routing/wp-full-view/wp-full-view.html | 4 +- .../static-attribute-help-text.component.ts | 3 +- .../collapsible-section.component.ts | 7 +- .../colors/colors-autocompleter.component.ts | 7 +- .../copy-to-clipboard.component.ts | 10 +- .../basic-single-date-picker.component.ts | 13 +- .../modal-single-date-picker.component.html | 2 +- .../modal-single-date-picker.component.ts | 19 +- .../dynamic-bootstrap.component.html | 4 - .../dynamic-bootstrap.component.spec.ts | 57 ----- .../dynamic-bootstrap.component.ts | 41 ---- .../dynamic-bootstrap.module.ts | 8 - .../ckeditor-augmented-textarea.component.ts | 2 - .../ckeditor/ckeditor-preview.service.ts | 26 +-- .../enterprise-banner.component.ts | 12 +- .../enterprise-page.component.html | 2 +- .../enterprise-page.component.ts | 12 +- .../formattable-display-field.module.ts | 4 - .../grids/grid/page/grid-page.component.html | 2 +- .../custom-text/custom-text.component.ts | 10 +- .../wp-overview/wp-overview.component.html | 4 +- .../header-project-select.component.ts | 3 +- .../add-section-dropdown.component.ts | 7 +- .../hide-section-link.component.ts | 3 +- .../show-section-dropdown.component.ts | 63 ----- .../modal/modal-overlay.component.ts | 8 +- .../no-results/no-results.component.ts | 11 +- .../op-non-working-days-list.component.html | 5 +- .../op-non-working-days-list.component.ts | 3 +- .../persistent-toggle.component.ts | 3 +- .../resizer/main-menu-resizer.component.ts | 11 +- .../storage-information.html | 4 +- .../storage-login-button.component.ts | 11 +- .../content-tabs/content-tabs.component.ts | 11 +- .../edit/trigger-actions-entry.component.ts | 20 +- .../toaster/toasts-container.component.ts | 7 +- .../overview/wp-overview-graph.component.ts | 7 +- frontend/src/app/shared/shared.module.ts | 47 ++-- .../drop-modal/drop-modal-portal.component.ts | 12 +- .../components/switch/switch.component.ts | 9 +- .../text_formatting/filters/macro_filter.rb | 3 +- .../filters/macros/github_pull_request.rb | 48 ++++ .../menu_manager/top_menu/projects_menu.rb | 2 +- lib/redmine/menu_manager/top_menu_helper.rb | 2 +- lib/tabular_form_builder.rb | 2 +- .../default.html.erb | 12 +- .../avatars/users/_local_avatars.html.erb | 2 +- .../avatar-upload-form.component.ts | 11 +- modules/avatars/frontend/module/main.ts | 5 - .../app/views/rb_sprints/_sprint.html.erb | 4 +- .../augment/cost-subform.augment.service.ts | 8 - .../costs/app/views/cost_types/index.html.erb | 2 +- modules/costs/app/views/costlog/edit.html.erb | 4 +- .../costs/app/views/my/timer/_menu.html.erb | 2 +- .../frontend/module/main.ts | 20 +- .../pull-request-macro.component.ts | 3 - modules/reporting/lib/widget/filters/date.rb | 4 +- .../reporting/lib/widget/reporting_widget.rb | 1 + .../reporting/lib/widget/table/entry_table.rb | 8 +- .../_project_folder_form.html.erb | 4 +- spec/lib/custom_field_form_builder_spec.rb | 6 +- spec/lib/tabular_form_builder_spec.rb | 4 +- spec/support/capybara_browser_logs.rb | 2 +- spec/support/pages/custom_fields.rb | 2 +- spec/support/pages/notifications/center.rb | 2 +- 119 files changed, 453 insertions(+), 1053 deletions(-) delete mode 100644 frontend/src/app/core/global_search/global-search-work-packages-entry.component.ts delete mode 100644 frontend/src/app/core/setup/global-dynamic-components.const.ts delete mode 100644 frontend/src/app/core/setup/globals/dynamic-bootstrapper.ts delete mode 100644 frontend/src/app/shared/components/dynamic-bootstrap/component/dynamic-bootstrap/dynamic-bootstrap.component.html delete mode 100644 frontend/src/app/shared/components/dynamic-bootstrap/component/dynamic-bootstrap/dynamic-bootstrap.component.spec.ts delete mode 100644 frontend/src/app/shared/components/dynamic-bootstrap/component/dynamic-bootstrap/dynamic-bootstrap.component.ts delete mode 100644 frontend/src/app/shared/components/dynamic-bootstrap/dynamic-bootstrap.module.ts delete mode 100644 frontend/src/app/shared/components/hide-section/show-section-dropdown.component.ts create mode 100644 lib/open_project/text_formatting/filters/macros/github_pull_request.rb diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index e42b67248862..341888e7390d 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -174,7 +174,7 @@ def labeled_check_box_tags(name, collection, options = {}) def html_hours(text) text.gsub(%r{(\d+)\.(\d+)}, '\1.\2') - .html_safe + .html_safe end def authoring(created, author, options = {}) @@ -202,7 +202,7 @@ def time_tag(time) else datetime = time.acts_like?(:time) ? time.xmlschema : time.iso8601 content_tag(:time, text, datetime:, - title: format_time(time), class: "timestamp") + title: format_time(time), class: "timestamp") end end @@ -253,18 +253,18 @@ def accesskey(s) # Same as Rails' simple_format helper without using paragraphs def simple_format_without_paragraph(text) text.to_s - .gsub(/\r\n?/, "\n") # \r\n and \r -> \n - .gsub(/\n\n+/, "

") # 2+ newline -> 2 br - .gsub(/([^\n]\n)(?=[^\n])/, '\1
') # 1 newline -> br - .html_safe + .gsub(/\r\n?/, "\n") # \r\n and \r -> \n + .gsub(/\n\n+/, "

") # 2+ newline -> 2 br + .gsub(/([^\n]\n)(?=[^\n])/, '\1
') # 1 newline -> br + .html_safe end def lang_options_for_select(blank = true) auto = if blank && (valid_languages - all_languages) == (all_languages - valid_languages) - [["(auto)", ""]] - else - [] - end + [["(auto)", ""]] + else + [] + end mapped_languages = valid_languages.map { |lang| translate_language(lang) } @@ -347,7 +347,7 @@ def current_layout def progress_bar(pcts, options = {}) pcts = Array(pcts).map(&:round) closed = pcts[0] - done = pcts[1] || 0 + done = pcts[1] || 0 width = options[:width] || "100px;" legend = options[:legend] || "" total_progress = options[:hide_total_progress] ? "" : t(:total_progress) @@ -356,7 +356,7 @@ def progress_bar(pcts, options = {}) content_tag :span do progress = content_tag :span, class: "progress-bar", style: "width: #{width}" do concat content_tag(:span, "", class: "inner-progress closed", style: "width: #{closed}%") - concat content_tag(:span, "", class: "inner-progress done", style: "width: #{done}%") + concat content_tag(:span, "", class: "inner-progress done", style: "width: #{done}%") end progress + content_tag(:span, "#{legend}#{percent_sign} #{total_progress}", class: "progress-bar-legend") end @@ -369,8 +369,10 @@ def checked_image(checked = true) end def calendar_for(*_args) - ActiveSupport::Deprecation.warn "calendar_for has been removed. Please use the op-basic-single-date-picker angular component instead", - caller + ActiveSupport::Deprecation.warn( + "calendar_for has been removed. Please use the opce-basic-single-date-picker angular component instead", + caller + ) end def locale_first_day_of_week @@ -435,7 +437,7 @@ def permitted_params def translate_language(lang_code) # rename in-context translation language name for the language select box if lang_code.to_sym == Redmine::I18n::IN_CONTEXT_TRANSLATION_CODE && - ::I18n.locale != Redmine::I18n::IN_CONTEXT_TRANSLATION_CODE + ::I18n.locale != Redmine::I18n::IN_CONTEXT_TRANSLATION_CODE [Redmine::I18n::IN_CONTEXT_TRANSLATION_NAME, lang_code.to_s] else [I18n.t("cldr.language_name", locale: lang_code), lang_code.to_s] diff --git a/app/helpers/custom_fields_helper.rb b/app/helpers/custom_fields_helper.rb index fa2e4b641e9d..6d16917b3631 100644 --- a/app/helpers/custom_fields_helper.rb +++ b/app/helpers/custom_fields_helper.rb @@ -72,7 +72,7 @@ def custom_field_tag(name, custom_value) tag = case field_format.try(:edit_as) when "date" - angular_component_tag "op-basic-single-date-picker", + angular_component_tag "opce-basic-single-date-picker", inputs: { required: custom_field.is_required, value: custom_value.value, @@ -150,7 +150,7 @@ def custom_field_tag_for_bulk_edit(name, custom_field, project = nil) case field_format.try(:edit_as) when "date" - angular_component_tag "op-modal-single-date-picker", + angular_component_tag "opce-modal-single-date-picker", inputs: { id: field_id, name: field_name diff --git a/app/views/admin/backups/show.html.erb b/app/views/admin/backups/show.html.erb index 4c9d15683957..1b6f6fa64d16 100644 --- a/app/views/admin/backups/show.html.erb +++ b/app/views/admin/backups/show.html.erb @@ -90,7 +90,7 @@ See COPYRIGHT and LICENSE files for more details. <% end %> <% if @backup_token.present? %> -<%= tag :backup, data: { +<%= angular_component_tag "opce-backup", data: { 'job-status-id': @job_status_id, 'last-backup-date': @last_backup_date, 'last-backup-attachment-id': @last_backup_attachment_id, diff --git a/app/views/admin/settings/projects_settings/show.html.erb b/app/views/admin/settings/projects_settings/show.html.erb index aabbee95cf61..f8ee4cf5648e 100644 --- a/app/views/admin/settings/projects_settings/show.html.erb +++ b/app/views/admin/settings/projects_settings/show.html.erb @@ -61,14 +61,13 @@ See COPYRIGHT and LICENSE files for more details. - <%= content_tag 'editable-query-props', - '', - data: { - name: 'settings[project_gantt_query]', - id: 'settings_project_gantt_query', - query: ::Projects::GanttQueryGeneratorService.current_query, - 'url-params': 'true' - } + <%= angular_component_tag 'opce-editable-query-props', + data: { + name: 'settings[project_gantt_query]', + id: 'settings_project_gantt_query', + query: ::Projects::GanttQueryGeneratorService.current_query, + 'url-params': 'true' + } %> diff --git a/app/views/admin/settings/work_packages_settings/_enterprise_feature_hint.html.erb b/app/views/admin/settings/work_packages_settings/_enterprise_feature_hint.html.erb index 88daaf178482..d82dc602b7c1 100644 --- a/app/views/admin/settings/work_packages_settings/_enterprise_feature_hint.html.erb +++ b/app/views/admin/settings/work_packages_settings/_enterprise_feature_hint.html.erb @@ -1,7 +1,7 @@ <% unless EnterpriseToken.allows_to?(ee_feature) %> <%= - angular_component_tag 'op-enterprise-banner', + angular_component_tag 'opce-enterprise-banner', inputs: { collapsible: true, textMessage: explanation, diff --git a/app/views/admin/settings/working_days_and_hours_settings/show.html.erb b/app/views/admin/settings/working_days_and_hours_settings/show.html.erb index 6dd8ceaac2ba..80f82d5479fc 100644 --- a/app/views/admin/settings/working_days_and_hours_settings/show.html.erb +++ b/app/views/admin/settings/working_days_and_hours_settings/show.html.erb @@ -90,7 +90,7 @@ See COPYRIGHT and LICENSE files for more details.

<%= t("working_days.instance_wide_info") %>

- <%= angular_component_tag "op-non-working-days-list", + <%= angular_component_tag "opce-non-working-days-list", data: { modified_non_working_days: @modified_non_working_days } %> diff --git a/app/views/attribute_help_texts/_tab.html.erb b/app/views/attribute_help_texts/_tab.html.erb index edc64251d148..1da41bb295ac 100644 --- a/app/views/attribute_help_texts/_tab.html.erb +++ b/app/views/attribute_help_texts/_tab.html.erb @@ -42,7 +42,7 @@ edit_attribute_help_text_path(attribute_help_text) %> - <%= angular_component_tag 'attribute-help-text', + <%= angular_component_tag 'opce-attribute-help-text', inputs: { helpTextId: attribute_help_text.id, attribute: attribute_help_text.attribute_name, diff --git a/app/views/augmented/_collapsible_section.html.erb b/app/views/augmented/_collapsible_section.html.erb index b223a5c174aa..4b9986a9ffd9 100644 --- a/app/views/augmented/_collapsible_section.html.erb +++ b/app/views/augmented/_collapsible_section.html.erb @@ -1,6 +1,6 @@ - - - <% end %> @@ -116,8 +117,8 @@ - - + + diff --git a/app/views/custom_styles/show.html.erb b/app/views/custom_styles/show.html.erb index 1368cf54c11d..9ffedb2176f1 100644 --- a/app/views/custom_styles/show.html.erb +++ b/app/views/custom_styles/show.html.erb @@ -156,10 +156,10 @@ See COPYRIGHT and LICENSE files for more details. <% end %> <% custom_export_expanded = @custom_style.id && (@custom_style.export_logo.present? || @custom_style.export_cover.present? || @custom_style.export_cover_text_color.present?) %> - - -