diff --git a/app/services/authorization.rb b/app/services/authorization.rb index 0aafb9acb30..95d73cef537 100644 --- a/app/services/authorization.rb +++ b/app/services/authorization.rb @@ -85,6 +85,8 @@ module Authorization if perms.blank? if !OpenProject::AccessControl.disabled_permission?(action) + # See https://www.openproject.org/docs/development/concepts/permissions/#definition-of-permissions + # if you are wondering where to define permissions Rails.logger.debug { "Used permission \"#{action}\" that is not defined. It will never return true." } raise UnknownPermissionError.new(action) if raise_on_unknown end diff --git a/modules/wikis/app/components/wikis/link_existing_wiki_page_dialog.html.erb b/modules/wikis/app/components/wikis/link_existing_wiki_page_dialog.html.erb new file mode 100644 index 00000000000..9d5062a95a6 --- /dev/null +++ b/modules/wikis/app/components/wikis/link_existing_wiki_page_dialog.html.erb @@ -0,0 +1,40 @@ +<%#-- 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. + +++#%> + +<%= render(Primer::Alpha::Dialog.new(id:, title: t(".title"), size: :large, **system_arguments)) do |dialog| %> + <% dialog.with_body do + primer_form_with(**form_options) do |form| + render(Wikis::LinkExistingWikiPageForm.new(form)) + end + end %> + <% dialog.with_footer do %> + <%= render(Primer::Beta::Button.new(data: { "close-dialog-id": id })) { t("button_cancel") } %> + <%= render(Primer::Beta::Button.new(scheme: :primary, form: form_id, type: :submit)) { t("button_add") } %> + <% end %> +<% end %> diff --git a/modules/wikis/app/components/wikis/link_existing_wiki_page_dialog.rb b/modules/wikis/app/components/wikis/link_existing_wiki_page_dialog.rb new file mode 100644 index 00000000000..ba450e76f35 --- /dev/null +++ b/modules/wikis/app/components/wikis/link_existing_wiki_page_dialog.rb @@ -0,0 +1,63 @@ +# 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 Wikis + class LinkExistingWikiPageDialog < ApplicationComponent + include OpTurbo::Streamable + + attr_reader :linkable, :provider + + def initialize(linkable:, provider:, **) + super(nil, **) + + @linkable = linkable + @provider = provider + end + + def id = "link-existing-wiki-page-dialog" + + def form_id = "#{id}-form" + + def form_options + { + id: form_id, + model: RelationPageLink.new(provider:, linkable:), + url: relation_wiki_page_links_path, + data: { + turbo_frame: WorkPackageWikisTabComponent::TURBO_FRAME_ID + } + } + end + + def system_arguments + options + end + end +end diff --git a/modules/wikis/app/components/wikis/relation_page_links_component.html.erb b/modules/wikis/app/components/wikis/relation_page_links_component.html.erb index 60fb4d2480b..bdbb740acae 100644 --- a/modules/wikis/app/components/wikis/relation_page_links_component.html.erb +++ b/modules/wikis/app/components/wikis/relation_page_links_component.html.erb @@ -37,14 +37,23 @@ See COPYRIGHT and LICENSE files for more details. end header.with_column do render(Primer::Alpha::ActionMenu.new) do |menu| - menu.with_show_button(disabled: true) do |button| + menu.with_show_button(disabled: !can_manage_links?) do |button| button.with_leading_visual_icon(icon: :plus) button.with_trailing_action_icon(icon: :"triangle-down") t("wikis.buttons.wiki_page") end - menu.with_item(label: "placeholder item") + menu.with_item( + label: t(".link_existing"), + tag: :a, + href: link_existing_dialog_relation_wiki_page_links_path(work_package:, provider:), + content_arguments: { data: { controller: "async-dialog" } } + ) + menu.with_item( + label: t(".link_new"), + disabled: true # work in progress + ) end end end @@ -52,7 +61,7 @@ See COPYRIGHT and LICENSE files for more details. if !user_connected? box.with_row do - render(Wikis::OAuthLoginComponent.new(provider, return_url: work_package_url(@work_package, tab: :wikis))) + render(Wikis::OAuthLoginComponent.new(provider, return_url: work_package_url(work_package, tab: :wikis))) end elsif page_links.empty? box.with_row do @@ -62,7 +71,7 @@ See COPYRIGHT and LICENSE files for more details. blankslate.with_description { t(".empty_text") } end else - render(Wikis::OAuthLoginComponent.new(provider, return_url: work_package_url(@work_package, tab: :wikis))) + render(Wikis::OAuthLoginComponent.new(provider, return_url: work_package_url(work_package, tab: :wikis))) end end else diff --git a/modules/wikis/app/components/wikis/relation_page_links_component.rb b/modules/wikis/app/components/wikis/relation_page_links_component.rb index 02686e72414..bd9fe9f2cd3 100644 --- a/modules/wikis/app/components/wikis/relation_page_links_component.rb +++ b/modules/wikis/app/components/wikis/relation_page_links_component.rb @@ -35,13 +35,15 @@ module Wikis alias_method :provider, :model + attr_reader :work_package + def initialize(model = nil, work_package: nil, **) @work_package = work_package super(model, **) end def page_links - @page_links ||= page_link_service.relation_page_links_for(provider:, linkable: @work_package) + @page_links ||= page_link_service.relation_page_links_for(provider:, linkable: work_package) end def user_connected? @@ -53,5 +55,9 @@ module Wikis def page_link_service @page_link_service ||= PageLinkService.new end + + def can_manage_links? + helpers.current_user.allowed_in_project?(:manage_wiki_page_links, work_package.project) + end end end diff --git a/modules/wikis/app/components/wikis/work_package_wikis_tab_component.html.erb b/modules/wikis/app/components/wikis/work_package_wikis_tab_component.html.erb index 22939ef4b85..aae5c4a9193 100644 --- a/modules/wikis/app/components/wikis/work_package_wikis_tab_component.html.erb +++ b/modules/wikis/app/components/wikis/work_package_wikis_tab_component.html.erb @@ -28,7 +28,7 @@ See COPYRIGHT and LICENSE files for more details. ++#%> <%= - content_tag("turbo-frame", id: "work-package-wikis-tab-content") do + content_tag("turbo-frame", id: TURBO_FRAME_ID) do component_wrapper do flex_layout(test_selector: "op-work-package-wikis-tab-container") do |container| providers.each do |provider| diff --git a/modules/wikis/app/components/wikis/work_package_wikis_tab_component.rb b/modules/wikis/app/components/wikis/work_package_wikis_tab_component.rb index ac729e92864..5c0471b593b 100644 --- a/modules/wikis/app/components/wikis/work_package_wikis_tab_component.rb +++ b/modules/wikis/app/components/wikis/work_package_wikis_tab_component.rb @@ -34,6 +34,8 @@ module Wikis include OpPrimer::ComponentHelpers include OpTurbo::Streamable + TURBO_FRAME_ID = "work-package-wikis-tab-content" + alias_method :work_package, :model def providers diff --git a/modules/wikis/app/contracts/wikis/page_links/relation_page_link_create_contract.rb b/modules/wikis/app/contracts/wikis/relation_page_links/create_contract.rb similarity index 86% rename from modules/wikis/app/contracts/wikis/page_links/relation_page_link_create_contract.rb rename to modules/wikis/app/contracts/wikis/relation_page_links/create_contract.rb index a15c5ff5a07..d1697737f41 100644 --- a/modules/wikis/app/contracts/wikis/page_links/relation_page_link_create_contract.rb +++ b/modules/wikis/app/contracts/wikis/relation_page_links/create_contract.rb @@ -29,16 +29,20 @@ #++ module Wikis - module PageLinks - class RelationPageLinkCreateContract < ::ModelContract + module RelationPageLinks + class CreateContract < ::ModelContract attribute :author attribute :identifier - attribute :linkable + attribute :linkable_type + attribute :linkable_id attribute :provider + attribute :type validates :identifier, presence: true - validates :linkable, presence: true + validates :linkable_type, presence: true + validates :linkable_id, presence: true validates :provider, presence: true + validates :type, inclusion: { in: [RelationPageLink.name] } validate :provider_exists? validate :author_must_be_user diff --git a/modules/wikis/app/controllers/wikis/relation_page_links_controller.rb b/modules/wikis/app/controllers/wikis/relation_page_links_controller.rb index 895499df76a..dd48dd4a471 100644 --- a/modules/wikis/app/controllers/wikis/relation_page_links_controller.rb +++ b/modules/wikis/app/controllers/wikis/relation_page_links_controller.rb @@ -32,21 +32,62 @@ module Wikis class RelationPageLinksController < ApplicationController include OpTurbo::ComponentStream - before_action :find_page_link before_action :authorize + def create + service_result = RelationPageLinks::CreateService.new(user: current_user).call(relation_page_link_params) + if service_result.success? + page_link = service_result.result + turbo_redirect_for_linkable(page_link.linkable) + else + message = service_result.errors.full_messages.join(" ") + render_error_flash_message_via_turbo_stream(message:) + respond_to_with_turbo_streams + end + end + def destroy - # TODO: implement delete service + # TODO: Wikis::PageLinks::DeleteService + page_link = find_page_link + page_link.destroy! + + turbo_redirect_for_linkable(page_link.linkable) end def confirm_delete_dialog - respond_with_dialog(DeleteRelationPageLinkConfirmationDialog.new(page_link: @page_link)) + page_link = find_page_link + respond_with_dialog(DeleteRelationPageLinkConfirmationDialog.new(page_link:)) + end + + def link_existing_dialog + linkable = WorkPackage.visible.find(params.expect(:work_package)) + provider = Provider.visible.find(params.expect(:provider)) + respond_with_dialog Wikis::LinkExistingWikiPageDialog.new(linkable:, provider:) end private def find_page_link - @page_link = RelationPageLink.find(params.expect(:id)) + RelationPageLink.find(params.expect(:id)) + end + + def relation_page_link_params + params.expect(wikis_relation_page_link: %i[identifier provider_id linkable_type linkable_id]) + .merge(author_id: current_user.id) + end + + def turbo_redirect_for_linkable(linkable) + path = derive_path_from_linkable(linkable) + return redirect_to path, status: :see_other if path + + head :no_content + end + + def derive_path_from_linkable(linkable) + case linkable + when WorkPackage + project_work_package_wikis_tab_index_path(work_package_id: linkable.id, project_id: linkable.project_id) + end end end end diff --git a/modules/wikis/app/controllers/work_package_wikis_tab_controller.rb b/modules/wikis/app/controllers/work_package_wikis_tab_controller.rb index bb9f14139b9..96483db8526 100644 --- a/modules/wikis/app/controllers/work_package_wikis_tab_controller.rb +++ b/modules/wikis/app/controllers/work_package_wikis_tab_controller.rb @@ -36,7 +36,14 @@ class WorkPackageWikisTabController < ApplicationController before_action :set_work_package def index - render(Wikis::WorkPackageWikisTabComponent.new(@work_package), layout: false) + tab_component = Wikis::WorkPackageWikisTabComponent.new(@work_package) + replace_via_turbo_stream(component: tab_component) + + respond_to_with_turbo_streams do |format| + format.html do + render(tab_component, layout: false) + end + end end private diff --git a/modules/wikis/app/forms/wikis/admin/name_input_form.rb b/modules/wikis/app/forms/wikis/admin/name_input_form.rb index b54b9408062..10af132c9cc 100644 --- a/modules/wikis/app/forms/wikis/admin/name_input_form.rb +++ b/modules/wikis/app/forms/wikis/admin/name_input_form.rb @@ -33,7 +33,7 @@ module Wikis::Admin form do |f| f.text_field( name: :name, - label: I18n.t("activerecord.attributes.wikis/xwiki_provider.name"), + label: model.class.human_attribute_name(:name), required: true, caption: I18n.t("wikis.admin.wiki_providers.name_caption"), placeholder: I18n.t("wikis.admin.wiki_providers.name_placeholder"), diff --git a/modules/wikis/app/forms/wikis/link_existing_wiki_page_form.rb b/modules/wikis/app/forms/wikis/link_existing_wiki_page_form.rb new file mode 100644 index 00000000000..23b852f2cc8 --- /dev/null +++ b/modules/wikis/app/forms/wikis/link_existing_wiki_page_form.rb @@ -0,0 +1,46 @@ +# 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 Wikis + class LinkExistingWikiPageForm < ApplicationForm + form do |f| + f.hidden(name: :provider_id) + f.hidden(name: :linkable_type) + f.hidden(name: :linkable_id) + + f.text_field( + name: :identifier, + label: RelationPageLink.human_attribute_name(:identifier), + required: true, + input_width: :large + ) + end + end +end diff --git a/modules/wikis/app/services/wikis/page_links/create_service.rb b/modules/wikis/app/services/wikis/relation_page_links/create_service.rb similarity index 92% rename from modules/wikis/app/services/wikis/page_links/create_service.rb rename to modules/wikis/app/services/wikis/relation_page_links/create_service.rb index eb9371246f6..d50dfd4a1b8 100644 --- a/modules/wikis/app/services/wikis/page_links/create_service.rb +++ b/modules/wikis/app/services/wikis/relation_page_links/create_service.rb @@ -29,11 +29,8 @@ #++ module Wikis - module PageLinks + module RelationPageLinks class CreateService < ::BaseServices::Create - private - - def default_contract_class = RelationPageLinkCreateContract end end end diff --git a/modules/wikis/app/services/wikis/page_links/set_attributes_service.rb b/modules/wikis/app/services/wikis/relation_page_links/set_attributes_service.rb similarity index 98% rename from modules/wikis/app/services/wikis/page_links/set_attributes_service.rb rename to modules/wikis/app/services/wikis/relation_page_links/set_attributes_service.rb index 1e3b0d5e045..bac99ed69f6 100644 --- a/modules/wikis/app/services/wikis/page_links/set_attributes_service.rb +++ b/modules/wikis/app/services/wikis/relation_page_links/set_attributes_service.rb @@ -29,7 +29,7 @@ #++ module Wikis - module PageLinks + module RelationPageLinks class SetAttributesService < ::BaseServices::SetAttributes end end diff --git a/modules/wikis/config/locales/en.yml b/modules/wikis/config/locales/en.yml index ecdd28f9b10..3f2d66317e4 100644 --- a/modules/wikis/config/locales/en.yml +++ b/modules/wikis/config/locales/en.yml @@ -3,15 +3,17 @@ en: activerecord: attributes: wikis/page_link: + identifier: Identifier provider: Wiki Provider + wikis/provider: + name: Name + universal_identifier: Universal identifier wikis/xwiki_provider: authentication_method: Authentication method authentication_methods: oauth2_sso: Single-Sign-On through OpenID Connect Identity Provider two_way_oauth2: Two-way OAuth 2.0 authorization code flow - name: Name token_exchange_scope: XWiki Scope - universal_identifier: Universal identifier url: Instance URL wiki_audience: XWiki Audience errors: { } @@ -65,6 +67,8 @@ en: work_package_wikis_tab_component: inline_page_links: Inline page links referencing_pages: Referenced in + link_existing_wiki_page_dialog: + title: Add existing wiki page page_links: errors: page_not_found: Linked wiki page no longer available @@ -73,6 +77,8 @@ en: page_link_component: remove: Remove page link relation_page_links_component: + link_existing: Existing wiki page + link_new: New wiki page empty_heading: No related pages empty_text: Manually add links to other related wiki pages. oauth_login_component: diff --git a/modules/wikis/config/routes.rb b/modules/wikis/config/routes.rb index 4a8ac57407f..963162be729 100644 --- a/modules/wikis/config/routes.rb +++ b/modules/wikis/config/routes.rb @@ -49,16 +49,6 @@ Rails.application.routes.draw do end end - resource :wiki_page_link_macro, controller: "wikis/page_link_macro", only: [] do - get :load - end - - resources :relation_wiki_page_links, only: %i[destroy], controller: "wikis/relation_page_links" do - member do - get :confirm_delete_dialog - end - end - resources :projects, only: %i[] do resources :work_packages, only: %i[] do resources :wikis, only: %i[] do @@ -68,4 +58,18 @@ Rails.application.routes.draw do end end end + + resources :relation_wiki_page_links, only: %i[create destroy], controller: "wikis/relation_page_links" do + collection do + get :link_existing_dialog + end + + member do + get :confirm_delete_dialog + end + end + + resource :wiki_page_link_macro, controller: "wikis/page_link_macro", only: [] do + get :load + end end diff --git a/modules/wikis/lib/open_project/wikis/engine.rb b/modules/wikis/lib/open_project/wikis/engine.rb index 5d70952d9aa..a8ce614c2cb 100644 --- a/modules/wikis/lib/open_project/wikis/engine.rb +++ b/modules/wikis/lib/open_project/wikis/engine.rb @@ -72,7 +72,9 @@ module OpenProject::Wikis register "openproject-wikis", author_url: "https://openproject.org" do project_module :work_package_tracking do permission :manage_wiki_page_links, - { "wikis/relation_page_links": %i[destroy confirm_delete_dialog] }, + { + "wikis/relation_page_links": %i[create destroy confirm_delete_dialog link_existing_dialog] + }, permissible_on: :project, dependencies: %i[edit_work_packages], contract_actions: { wiki_page_links: %i[manage] } diff --git a/modules/wikis/spec/contracts/wikis/page_links/relation_page_link_create_contract_spec.rb b/modules/wikis/spec/contracts/wikis/relation_page_links/create_contract_spec.rb similarity index 97% rename from modules/wikis/spec/contracts/wikis/page_links/relation_page_link_create_contract_spec.rb rename to modules/wikis/spec/contracts/wikis/relation_page_links/create_contract_spec.rb index 09c6107b63c..7a25438cfc0 100644 --- a/modules/wikis/spec/contracts/wikis/page_links/relation_page_link_create_contract_spec.rb +++ b/modules/wikis/spec/contracts/wikis/relation_page_links/create_contract_spec.rb @@ -33,8 +33,8 @@ require "contracts/shared/model_contract_shared_context" require_module_spec_helper module Wikis - module PageLinks - RSpec.describe RelationPageLinkCreateContract do + module RelationPageLinks + RSpec.describe CreateContract do include_context "ModelContract shared context" let(:linkable) { create(:work_package) } let(:project) { linkable.project } diff --git a/modules/wikis/spec/services/wikis/page_links/create_service_spec.rb b/modules/wikis/spec/services/wikis/relation_page_links/create_service_spec.rb similarity index 84% rename from modules/wikis/spec/services/wikis/page_links/create_service_spec.rb rename to modules/wikis/spec/services/wikis/relation_page_links/create_service_spec.rb index 91ac63bc390..4030cc0b331 100644 --- a/modules/wikis/spec/services/wikis/page_links/create_service_spec.rb +++ b/modules/wikis/spec/services/wikis/relation_page_links/create_service_spec.rb @@ -34,18 +34,11 @@ require "services/base_services/behaves_like_create_service" require_module_spec_helper module Wikis - module PageLinks + module RelationPageLinks RSpec.describe CreateService do it_behaves_like "BaseServices create service" do - let(:contract_class) { RelationPageLinkCreateContract } let(:factory) { :relation_wiki_page_link } end - - it "defaults to the RelationPageLinkCreateContract" do - service = described_class.new(user: nil) - - expect(service.contract_class).to eq(RelationPageLinkCreateContract) - end end end end diff --git a/modules/wikis/spec/services/wikis/page_links/set_attributes_service_spec.rb b/modules/wikis/spec/services/wikis/relation_page_links/set_attributes_service_spec.rb similarity index 92% rename from modules/wikis/spec/services/wikis/page_links/set_attributes_service_spec.rb rename to modules/wikis/spec/services/wikis/relation_page_links/set_attributes_service_spec.rb index 201aed78e69..70edc90826a 100644 --- a/modules/wikis/spec/services/wikis/page_links/set_attributes_service_spec.rb +++ b/modules/wikis/spec/services/wikis/relation_page_links/set_attributes_service_spec.rb @@ -32,15 +32,15 @@ require "spec_helper" require_module_spec_helper module Wikis - module PageLinks + module RelationPageLinks RSpec.describe SetAttributesService do let(:model_instance) { RelationPageLink.new } let(:contract_instance) do - instance_double(RelationPageLinkCreateContract, validate: contract_valid, errors: contract_errors) + instance_double(CreateContract, validate: contract_valid, errors: contract_errors) end let(:contract_class) do - class_double(RelationPageLinkCreateContract, new: contract_instance) + class_double(CreateContract, new: contract_instance) end let(:contract_errors) { instance_double(ActiveModel::Errors) }