[74354] Add a simple login XWiki button (#22924)

Add XWiki login button
This commit is contained in:
Yauheni Suhakou
2026-05-05 16:22:10 +02:00
committed by GitHub
parent 8edab11953
commit 1d67d27472
13 changed files with 298 additions and 18 deletions
+2 -2
View File
@@ -402,7 +402,7 @@ GEM
smart_properties
bigdecimal (4.1.2)
bindata (2.5.1)
bootsnap (1.24.1)
bootsnap (1.24.3)
msgpack (~> 1.2)
brakeman (8.0.4)
racc
@@ -1843,7 +1843,7 @@ CHECKSUMS
better_html (2.2.0) sha256=e68ab66ab09696b708333bbf35e8aa3c107500ba7892f528e2111624bdd8cf76
bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd
bindata (2.5.1) sha256=53186a1ec2da943d4cb413583d680644eb810aacbf8902497aac8f191fad9e58
bootsnap (1.24.1) sha256=d7faea1dc24aa5b22dacc049c9236b64ebf60b14dd49c615e15d8402375d39ef
bootsnap (1.24.3) sha256=f7fa3d20597e2f0aa52b0a1aba83fb54d4f79e9c2e210ec4fa1e8895514dcad8
brakeman (8.0.4) sha256=7bf921fa9638544835df9aa7b3e720a9a72c0267f34f92135955edd80d4dcf6f
browser (6.2.0) sha256=281d5295788825c9396427c292c2d2be0a5c91875c93c390fde6e5d61a5ace2d
budgets (1.0.0)
@@ -30,11 +30,11 @@ See COPYRIGHT and LICENSE files for more details.
<%= render(Primer::OpenProject::DangerDialog.new(title: t(".title"),
form_arguments:,
size: :large)) do |dialog| %>
<% dialog.with_confirmation_message do |message|
message.with_heading(tag: :h2) { t(".title") }
end %>
<% dialog.with_additional_details do %>
<%= t(".warning_html", wiki_provider: content_tag(:strong, @wiki_provider.name)) %>
<% end %>
<% dialog.with_confirmation_check_box_content(t(:text_permanent_delete_confirmation_checkbox_label)) %>
<% dialog.with_confirmation_message do |message|
message.with_heading(tag: :h2) { t(".heading") }
message.with_description_content(
t(".warning_html", wiki_provider: content_tag(:strong, @wiki_provider.name))
)
end %>
<% dialog.with_confirmation_check_box_content(t(:text_permanent_delete_confirmation_checkbox_label)) %>
<% end %>
@@ -0,0 +1,39 @@
<%#-- 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::Beta::Blankslate.new(border: false)) do |blankslate|
blankslate.with_heading(tag: :h2).with_content(t(".heading", provider: provider.name))
blankslate.with_description { t(".description", provider: provider.name) }
blankslate.with_primary_action(href: login_url, scheme: :secondary, data: { "turbo-frame": "_top" }) do |button|
button.with_trailing_visual_icon(icon: :"link-external")
t(".connect_button", provider: provider.name)
end
end
%>
@@ -0,0 +1,51 @@
# 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 OAuthLoginComponent < ApplicationComponent
include ApplicationHelper
include OpPrimer::ComponentHelpers
alias_method :provider, :model
def initialize(model = nil, return_url:, **)
super(model, **)
@return_url = return_url
end
def login_url
oauth_clients_ensure_connection_url(
oauth_client_id: provider.oauth_client.client_id,
storage_id: provider.id,
destination_url: @return_url
)
end
end
end
@@ -50,17 +50,24 @@ See COPYRIGHT and LICENSE files for more details.
end
end
if page_link_infos.empty?
if !user_connected?
box.with_row do
render(Wikis::OAuthLoginComponent.new(
provider,
return_url: work_package_url(@work_package, tab: :wikis)
))
end
elsif page_link_infos.empty?
box.with_row do
render(Primer::Beta::Blankslate.new(border: false)) do |blankslate|
blankslate.with_heading(tag: :h2).with_content(t(".empty_heading"))
blankslate.with_description { t(".empty_text") }
end
end
end
page_link_infos.each do |info|
box.with_row { render(Wikis::PageLinkComponent.new(info, actions: [:remove])) }
else
page_link_infos.each do |info|
box.with_row { render(Wikis::PageLinkComponent.new(info, actions: [:remove])) }
end
end
end
%>
@@ -44,6 +44,10 @@ module Wikis
@page_link_infos ||= page_link_service.relation_page_link_infos_for(provider:, linkable: @work_package)
end
def user_connected?
provider.user_connected?(User.current)
end
private
def page_link_service
@@ -34,6 +34,8 @@ module Wikis
def registry_prefix = "internal"
end
def user_connected?(_user) = true
def name
model_name.human
end
@@ -48,6 +48,7 @@ module Wikis
before_create :generate_universal_identifier
def to_s = self.class.registry_prefix
def user_connected?(_user) = raise SubclassResponsibilityError
class << self
def registry_prefix = raise SubclassResponsibilityError
@@ -48,6 +48,12 @@ module Wikis
def generate_client_id = SecureRandom.uuid
end
def user_connected?(user)
return true if oauth_client.blank?
OAuthClientToken.for_user_and_client(user, oauth_client).exists?
end
def extract_origin_user_id(token)
resolve("queries.user").call(Wikis::Adapters::Input::UserQuery.new(access_token: token.access_token))
end
@@ -54,7 +54,7 @@ See COPYRIGHT and LICENSE files for more details.
destination_url: edit_admin_settings_wiki_provider_url(@wiki_provider)
)
) do |button|
button.with_leading_visual_icon(icon: :plug)
button.with_trailing_visual_icon(icon: :"link-external")
t("wikis.buttons.connect_account", provider: @wiki_provider.name)
end %>
<% end %>
+3 -3
View File
@@ -40,10 +40,13 @@ en:
empty_heading: No related pages
empty_text: Manually add links to other related wiki pages.
oauth_login_component:
heading: Not connected to %{provider}
description: Log in to %{provider} to view and manage related wiki pages from this OpenProject instance.
connect_button: Connect %{provider} account
admin:
destroy_confirmation_dialog_component:
title: Delete wiki provider
heading: Delete wiki provider?
warning_html: "You are about to delete %{wiki_provider}. This action is irreversible."
forms:
general_info_form_component:
@@ -91,8 +94,5 @@ en:
openproject_oauth_description: Allow XWiki to access OpenProject data using an OAuth.
xwiki_oauth: XWiki OAuth
xwiki_oauth_description: Allow OpenProject to access XWiki data using an OAuth.
delete:
title: Delete wiki provider
warning_html: "You are about to delete %{wiki_provider}. This action is irreversible."
macro:
page_not_found: Linked wiki page no longer available
@@ -0,0 +1,60 @@
# 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.
#++
require "spec_helper"
require_module_spec_helper
RSpec.describe Wikis::OAuthLoginComponent, type: :component do
let(:work_package) { build_stubbed(:work_package) }
let(:provider) { create(:xwiki_provider) }
let(:oauth_client) { create(:oauth_client, integration: provider) }
let(:return_url) { "https://openproject.example.com/work_packages/#{work_package.id}?tab=wikis" }
before do
allow(provider).to receive(:oauth_client).and_return(oauth_client)
render_inline(described_class.new(provider, return_url:, work_package:))
end
it "renders the heading" do
expect(page).to have_text(I18n.t("wikis.oauth_login_component.heading", provider: provider.name))
end
it "renders the description" do
expect(page).to have_text(I18n.t("wikis.oauth_login_component.description", provider: provider.name))
end
it "renders the connect button with the return url" do
link = page.find_link(I18n.t("wikis.oauth_login_component.connect_button", provider: provider.name))
expect(link[:href]).to match(/ensure_connection/)
expect(link[:href]).to include(CGI.escape(return_url))
expect(link[:"data-turbo-frame"]).to eq("_top")
end
end
@@ -0,0 +1,110 @@
# 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.
#++
require "spec_helper"
require_module_spec_helper
RSpec.describe Wikis::RelationPageLinksComponent, type: :component do
let(:user) { create(:user) }
let(:work_package) { build_stubbed(:work_package) }
let(:provider) { create(:xwiki_provider) }
let(:oauth_client) { create(:oauth_client, integration: provider) }
let(:page_link_service) { instance_double(Wikis::PageLinkService, relation_page_link_infos_for: []) }
subject(:render_component) { render_inline(described_class.new(provider, work_package:)) }
before do
login_as(user)
allow(Wikis::PageLinkService).to receive(:new).and_return(page_link_service)
end
context "when the provider has no oauth client configured" do
before do
allow(provider).to receive(:oauth_client).and_return(nil)
render_component
end
it { expect(page).to have_text(I18n.t("wikis.relation_page_links_component.empty_heading")) }
it { expect(page).to have_no_text(I18n.t("wikis.oauth_login_component.heading", provider: provider.name)) }
end
context "when the provider does not support OAuth" do
let(:provider) { create(:internal_wiki_provider) }
before { subject }
it { expect(page).to have_text(I18n.t("wikis.relation_page_links_component.empty_heading")) }
it { expect(page).to have_text(I18n.t("wikis.relation_page_links_component.empty_text")) }
it { expect(page).to have_no_text(I18n.t("wikis.oauth_login_component.heading", provider: provider.name)) }
end
context "when the provider has an oauth client but the user has no token" do
before do
allow(provider).to receive(:oauth_client).and_return(oauth_client)
render_component
end
it { expect(page).to have_text(I18n.t("wikis.oauth_login_component.heading", provider: provider.name)) }
end
context "when the user has a token for the provider" do
before do
allow(provider).to receive(:oauth_client).and_return(oauth_client)
create(:oauth_client_token, oauth_client:, user:)
render_component
end
it { expect(page).to have_text(I18n.t("wikis.relation_page_links_component.empty_heading")) }
it { expect(page).to have_no_text(I18n.t("wikis.oauth_login_component.heading", provider: provider.name)) }
end
context "when the user has a token and there are page links" do
let(:page_info) do
Wikis::Adapters::Results::PageInfo.new(
identifier: "MyPage",
provider:,
title: "My Wiki Page",
href: "https://wiki.example.com/MyPage"
)
end
before do
allow(provider).to receive(:user_connected?).and_return(true)
allow(page_link_service).to receive(:relation_page_link_infos_for)
.and_return([Dry::Monads::Success(page_info)])
render_component
end
it { expect(page).to have_text("My Wiki Page") }
it { expect(page).to have_no_text(I18n.t("wikis.relation_page_links_component.empty_heading")) }
it { expect(page).to have_no_text(I18n.t("wikis.oauth_login_component.heading", provider: provider.name)) }
end
end