From bf576952c53a25ef333133325ab6522ef685b6f0 Mon Sep 17 00:00:00 2001 From: Jan Sandbrink Date: Mon, 22 Jun 2026 16:48:06 +0200 Subject: [PATCH] Defer loading of page link inline macro The main motivation for this is to not include user-specific information in the HTML-rendered formatted text of API responses, because we cache those responses across requests for different users. This works around the caching issue by ensuring the relevant information is not part of the cache. An alternative approach would be to ensure the caching is safe across different users. --- ..._inline_page_link_macro_component.html.erb | 37 ++++++++++++ ...ferred_inline_page_link_macro_component.rb | 57 +++++++++++++++++++ .../text_formatting/wiki_link_matcher.rb | 18 +----- modules/wikis/config/locales/en.yml | 2 + 4 files changed, 99 insertions(+), 15 deletions(-) create mode 100644 modules/wikis/app/components/wikis/deferred_inline_page_link_macro_component.html.erb create mode 100644 modules/wikis/app/components/wikis/deferred_inline_page_link_macro_component.rb diff --git a/modules/wikis/app/components/wikis/deferred_inline_page_link_macro_component.html.erb b/modules/wikis/app/components/wikis/deferred_inline_page_link_macro_component.html.erb new file mode 100644 index 000000000000..c79be6ac8944 --- /dev/null +++ b/modules/wikis/app/components/wikis/deferred_inline_page_link_macro_component.html.erb @@ -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. + +++#%> + +<%= content_tag("turbo-frame", id: frame_id, src: frame_src, data: { provider_id:, page_identifier: identifier, type: "wiki-page-link" }) do %> + <%= render(OpPrimer::InlineMacroComponent.new) do |component| %> + <% component.with_leading_visual_icon(icon: :"op-file-doc") %> + + <%= render Primer::Beta::Spinner.new(size: :small, display: :flex, mr: 2) %> + <%= render(Primer::Beta::Text.new(color: :muted)) { t(".loading") } %> + <% end %> +<% end %> diff --git a/modules/wikis/app/components/wikis/deferred_inline_page_link_macro_component.rb b/modules/wikis/app/components/wikis/deferred_inline_page_link_macro_component.rb new file mode 100644 index 000000000000..b57dc65d7634 --- /dev/null +++ b/modules/wikis/app/components/wikis/deferred_inline_page_link_macro_component.rb @@ -0,0 +1,57 @@ +# 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 DeferredInlinePageLinkMacroComponent < ApplicationComponent + include ApplicationHelper + include OpPrimer::ComponentHelpers + + attr_reader :identifier, :provider_id + + def initialize(identifier:, provider_id:, **) + super(nil, **) + + @identifier = identifier + @provider_id = provider_id + end + + def frame_id + @frame_id ||= SecureRandom.uuid + end + + def frame_src + url_helpers.load_wiki_page_link_macro_path( + provider_id:, + page_identifier: identifier, + turbo_frame_id: frame_id + ) + end + end +end diff --git a/modules/wikis/app/services/wikis/text_formatting/wiki_link_matcher.rb b/modules/wikis/app/services/wikis/text_formatting/wiki_link_matcher.rb index bba8b758c60e..6099e1b3032e 100644 --- a/modules/wikis/app/services/wikis/text_formatting/wiki_link_matcher.rb +++ b/modules/wikis/app/services/wikis/text_formatting/wiki_link_matcher.rb @@ -52,6 +52,8 @@ def process_match(match, _matched_string, _context) end end + attr_reader :provider_id, :identifier + def initialize(provider_id:, identifier:) super() @@ -60,23 +62,9 @@ def initialize(provider_id:, identifier:) end def process - provider = Provider.find_by(id: @provider_id) view_context = ApplicationController.new.view_context - page_info_result = resolve_page(provider, @identifier) - - InlinePageLinkMacroComponent.new(page_info_result).render_in(view_context) - end - private - - def resolve_page(provider, identifier) - return Failure() if provider.nil? - - Adapters::Input::PageInfo.build(identifier:).bind do |input_data| - provider.auth_strategy_for(User.current).bind do |auth_strategy| - provider.resolve("queries.page_info").call(input_data:, auth_strategy:) - end - end + DeferredInlinePageLinkMacroComponent.new(provider_id:, identifier:).render_in(view_context) end end end diff --git a/modules/wikis/config/locales/en.yml b/modules/wikis/config/locales/en.yml index ac50c3383542..6b13832a8a71 100644 --- a/modules/wikis/config/locales/en.yml +++ b/modules/wikis/config/locales/en.yml @@ -131,6 +131,8 @@ en: wiki_page: Wiki page create_new_wiki_page_dialog: title: Create new wiki page + deferred_inline_page_link_macro_component: + loading: Loading… delete_relation_page_link_confirmation_dialog: heading: Delete related wiki page link? title: Delete related wiki page link