mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
Merge pull request #23236 from opf/create-wiki-link-dialog
Create page link UI
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 %>
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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|
|
||||
|
||||
@@ -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
|
||||
|
||||
+8
-4
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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
|
||||
+1
-4
@@ -29,11 +29,8 @@
|
||||
#++
|
||||
|
||||
module Wikis
|
||||
module PageLinks
|
||||
module RelationPageLinks
|
||||
class CreateService < ::BaseServices::Create
|
||||
private
|
||||
|
||||
def default_contract_class = RelationPageLinkCreateContract
|
||||
end
|
||||
end
|
||||
end
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
#++
|
||||
|
||||
module Wikis
|
||||
module PageLinks
|
||||
module RelationPageLinks
|
||||
class SetAttributesService < ::BaseServices::SetAttributes
|
||||
end
|
||||
end
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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] }
|
||||
|
||||
+2
-2
@@ -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 }
|
||||
+1
-8
@@ -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
|
||||
+3
-3
@@ -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) }
|
||||
Reference in New Issue
Block a user