From e63952fab6b2994e4e5aadc48f1c970b69159b62 Mon Sep 17 00:00:00 2001 From: Eric Schubert Date: Fri, 22 May 2026 11:11:34 +0200 Subject: [PATCH 1/2] [#75165] show error states in the page link component - https://community.openproject.org/work_packages/75165 - add component test to page link component --- .../inline_page_link_macro_component.html.erb | 2 +- .../wikis/page_link_component.html.erb | 8 +- .../components/wikis/page_link_component.rb | 24 +++- modules/wikis/config/locales/en.yml | 9 +- .../wikis/page_link_component_spec.rb | 122 ++++++++++++++++++ 5 files changed, 154 insertions(+), 11 deletions(-) create mode 100644 modules/wikis/spec/components/wikis/page_link_component_spec.rb diff --git a/modules/wikis/app/components/wikis/inline_page_link_macro_component.html.erb b/modules/wikis/app/components/wikis/inline_page_link_macro_component.html.erb index d80e5b68249..21f0afa0d71 100644 --- a/modules/wikis/app/components/wikis/inline_page_link_macro_component.html.erb +++ b/modules/wikis/app/components/wikis/inline_page_link_macro_component.html.erb @@ -36,7 +36,7 @@ See COPYRIGHT and LICENSE files for more details. render(Primer::Beta::Link.new(href: page_info.href)) { page_info.title } end, ->(_error) do - render(Primer::Beta::Text.new(color: :muted)) { I18n.t("wikis.macro.page_not_found") } + render(Primer::Beta::Text.new(color: :muted)) { I18n.t("wikis.page_links.errors.page_not_found") } end ) end diff --git a/modules/wikis/app/components/wikis/page_link_component.html.erb b/modules/wikis/app/components/wikis/page_link_component.html.erb index 9a77c4af97b..4da6a52b5ba 100644 --- a/modules/wikis/app/components/wikis/page_link_component.html.erb +++ b/modules/wikis/app/components/wikis/page_link_component.html.erb @@ -32,13 +32,17 @@ See COPYRIGHT and LICENSE files for more details. <%= render(Primer::Alpha::StackItem.new(grow: true, classes: "ellipsis")) do - render(Primer::Beta::Link.new(href: page_href, scheme: :primary)) { page_title } + if error? + render(Primer::Beta::Text.new(color: :muted)) { page_title } + else + render(Primer::Beta::Link.new(href: page_href, scheme: :primary)) { page_title } + end end %> <%= if show_action_menu? - render(Primer::Alpha::ActionMenu.new) do |menu| + render(Primer::Alpha::ActionMenu.new(test_selector: "wiki-page-link-action-menu")) do |menu| menu.with_show_button(icon: :"kebab-horizontal", "aria-label": t(:label_more), scheme: :invisible) menu_items(menu) end diff --git a/modules/wikis/app/components/wikis/page_link_component.rb b/modules/wikis/app/components/wikis/page_link_component.rb index a94c3ffa76d..5058b2167da 100644 --- a/modules/wikis/app/components/wikis/page_link_component.rb +++ b/modules/wikis/app/components/wikis/page_link_component.rb @@ -45,17 +45,31 @@ module Wikis end def page_title - # TODO: Define behaviour for errors - page_info_result.either(->(pi) { pi.title }, ->(_) { "Nothing to see here" }) + page_info_result.either( + ->(pi) { pi.title }, + ->(error) do + case error.code + when :not_found + I18n.t("wikis.page_links.errors.page_not_found") + when :forbidden + I18n.t("wikis.page_links.errors.page_access_forbidden") + else + I18n.t("wikis.page_links.errors.unknown") + end + end + ) end def page_href - # TODO: Define behaviour for errors - page_info_result.either(->(pi) { pi.href }, ->(_) { "#" }) + page_info_result.value!.href + end + + def error? + page_info_result.failure? end def show_action_menu? - page_info_result.success? && actions.any? + actions.any? end def menu_items(menu) diff --git a/modules/wikis/config/locales/en.yml b/modules/wikis/config/locales/en.yml index 6584f37749b..9bf1288fea1 100644 --- a/modules/wikis/config/locales/en.yml +++ b/modules/wikis/config/locales/en.yml @@ -14,7 +14,7 @@ en: universal_identifier: Universal identifier url: Instance URL wiki_audience: XWiki Audience - errors: {} + errors: { } models: wikis/inline_page_link: one: Inline page link @@ -57,6 +57,11 @@ en: work_package_wikis_tab_component: inline_page_links: Inline page links referencing_pages: Referenced in + page_links: + errors: + page_not_found: Linked wiki page no longer available + page_access_forbidden: You do not have permission to access this wiki page + unknown: An unknown error occurred page_link_component: remove: Remove page link relation_page_links_component: @@ -135,5 +140,3 @@ 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. - macro: - page_not_found: Linked wiki page no longer available diff --git a/modules/wikis/spec/components/wikis/page_link_component_spec.rb b/modules/wikis/spec/components/wikis/page_link_component_spec.rb new file mode 100644 index 00000000000..e878c22e349 --- /dev/null +++ b/modules/wikis/spec/components/wikis/page_link_component_spec.rb @@ -0,0 +1,122 @@ +# 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::PageLinkComponent, type: :component do + let(:project) { create(:project) } + let(:work_package) { create(:work_package, project:) } + let(:provider) { create(:internal_wiki_provider) } + let(:page_link) { create(:relation_wiki_page_link, linkable: work_package, provider:) } + let(:page_info) do + Wikis::Adapters::Results::PageInfo.new( + identifier: page_link.identifier, + title: "Stormtrooper training", + provider:, + href: "https://wiki.death.star/Home/stormtrooper_training" + ) + end + let(:page_info_result) { Success(page_info) } + let(:permissions) { [] } + let(:actions) { [] } + + current_user { create(:user, member_with_permissions: { project => permissions }) } + + subject(:render_component) { render_inline(described_class.new(page_info_result, actions:, page_link:)) } + + before { render_component } + + it "renders the page link successful" do + expect(page).to have_link(text: page_info.title, href: page_info.href) + end + + context "when the page link has the remove action" do + let(:actions) { [:remove] } + + context "when the user has no permission to manage wiki page links" do + it "does not render the action menu" do + expect(page).not_to have_test_selector("wiki-page-link-action-menu") + end + end + + context "when the user has the permission to manage wiki page links" do + let(:permissions) { [:manage_wiki_page_links] } + + it "shows the remove page link action in the action menu" do + expect(page).to have_test_selector("wiki-page-link-action-menu") + end + end + end + + context "when the page link has no actions" do + it "does not render the action menu" do + expect(page).not_to have_test_selector("wiki-page-link-action-menu") + end + end + + context "if there are errors retrieving the page info" do + let(:page_info_result) do + Failure( + Wikis::Adapters::Results::Error.new( + source: Wikis::Adapters::Providers::Internal::Queries::PageInfo, + code: error_code + ) + ) + end + + context "if the page was not found" do + let(:error_code) { :not_found } + + it "renders the page link successful" do + expect(page).not_to have_link + expect(page).to have_text(I18n.t("wikis.page_links.errors.page_not_found")) + end + end + + context "if the page access is forbidden" do + let(:error_code) { :forbidden } + + it "renders the page link successful" do + expect(page).not_to have_link + expect(page).to have_text(I18n.t("wikis.page_links.errors.page_access_forbidden")) + end + end + + context "if an unknown error occurred" do + let(:error_code) { "IT'S NO MOON" } + + it "renders the page link successful" do + expect(page).not_to have_link + expect(page).to have_text(I18n.t("wikis.page_links.errors.unknown")) + end + end + end +end From fd6d8a7eb551e695d4cddfcc4a13c0ade1171275 Mon Sep 17 00:00:00 2001 From: Eric Schubert Date: Fri, 22 May 2026 16:53:01 +0200 Subject: [PATCH 2/2] [#75165] update error state icons - use alert icon for error states - update test setup --- .../inline_page_link_macro_component.html.erb | 8 ++++---- .../wikis/inline_page_link_macro_component.rb | 11 +++++++++++ .../wikis/page_link_component.html.erb | 8 +++++++- .../components/wikis/page_link_component.rb | 8 ++++---- .../load.html.erb | 0 modules/wikis/config/locales/en.yml | 2 +- .../wikis/page_link_component_spec.rb | 18 +++++++++--------- 7 files changed, 36 insertions(+), 19 deletions(-) rename modules/wikis/app/views/wikis/{inline_page_link_macro => page_link_macro}/load.html.erb (100%) diff --git a/modules/wikis/app/components/wikis/inline_page_link_macro_component.html.erb b/modules/wikis/app/components/wikis/inline_page_link_macro_component.html.erb index 21f0afa0d71..cd02da27c17 100644 --- a/modules/wikis/app/components/wikis/inline_page_link_macro_component.html.erb +++ b/modules/wikis/app/components/wikis/inline_page_link_macro_component.html.erb @@ -29,14 +29,14 @@ See COPYRIGHT and LICENSE files for more details. <%= render(OpPrimer::InlineMacroComponent.new) do |inline_macro| - inline_macro.with_leading_visual_icon(icon: :"op-file-doc") - page_info_result.either( ->(page_info) do + inline_macro.with_leading_visual_icon(icon: :"op-file-doc") render(Primer::Beta::Link.new(href: page_info.href)) { page_info.title } end, - ->(_error) do - render(Primer::Beta::Text.new(color: :muted)) { I18n.t("wikis.page_links.errors.page_not_found") } + ->(error) do + inline_macro.with_leading_visual_icon(icon: :alert) + render(Primer::Beta::Text.new(color: :muted)) { error_text(error) } end ) end diff --git a/modules/wikis/app/components/wikis/inline_page_link_macro_component.rb b/modules/wikis/app/components/wikis/inline_page_link_macro_component.rb index 8320ffc9caf..da40729429b 100644 --- a/modules/wikis/app/components/wikis/inline_page_link_macro_component.rb +++ b/modules/wikis/app/components/wikis/inline_page_link_macro_component.rb @@ -34,5 +34,16 @@ module Wikis include OpPrimer::ComponentHelpers alias_method :page_info_result, :model + + def error_text(error) + case error + in { code: :not_found } + I18n.t("wikis.page_links.errors.page_not_found") + in { code: :forbidden } + I18n.t("wikis.page_links.errors.page_access_forbidden") + else + I18n.t("wikis.page_links.errors.unexpected") + end + end end end diff --git a/modules/wikis/app/components/wikis/page_link_component.html.erb b/modules/wikis/app/components/wikis/page_link_component.html.erb index 4da6a52b5ba..30ea3e9cb6f 100644 --- a/modules/wikis/app/components/wikis/page_link_component.html.erb +++ b/modules/wikis/app/components/wikis/page_link_component.html.erb @@ -28,7 +28,13 @@ See COPYRIGHT and LICENSE files for more details. ++#%> <%= render(Primer::Alpha::Stack.new(direction: :horizontal, gap: :condensed, align: :center)) do %> - <%= render(Primer::Beta::Octicon.new(icon: :"op-file-doc")) %> + <%= + if error? + render(Primer::Beta::Octicon.new(icon: :alert, color: :muted)) + else + render(Primer::Beta::Octicon.new(icon: :"op-file-doc")) + end + %> <%= render(Primer::Alpha::StackItem.new(grow: true, classes: "ellipsis")) do diff --git a/modules/wikis/app/components/wikis/page_link_component.rb b/modules/wikis/app/components/wikis/page_link_component.rb index 5058b2167da..9da0f465471 100644 --- a/modules/wikis/app/components/wikis/page_link_component.rb +++ b/modules/wikis/app/components/wikis/page_link_component.rb @@ -48,13 +48,13 @@ module Wikis page_info_result.either( ->(pi) { pi.title }, ->(error) do - case error.code - when :not_found + case error + in { code: :not_found } I18n.t("wikis.page_links.errors.page_not_found") - when :forbidden + in { code: :forbidden } I18n.t("wikis.page_links.errors.page_access_forbidden") else - I18n.t("wikis.page_links.errors.unknown") + I18n.t("wikis.page_links.errors.unexpected") end end ) diff --git a/modules/wikis/app/views/wikis/inline_page_link_macro/load.html.erb b/modules/wikis/app/views/wikis/page_link_macro/load.html.erb similarity index 100% rename from modules/wikis/app/views/wikis/inline_page_link_macro/load.html.erb rename to modules/wikis/app/views/wikis/page_link_macro/load.html.erb diff --git a/modules/wikis/config/locales/en.yml b/modules/wikis/config/locales/en.yml index 9bf1288fea1..9e8ca1b996a 100644 --- a/modules/wikis/config/locales/en.yml +++ b/modules/wikis/config/locales/en.yml @@ -61,7 +61,7 @@ en: errors: page_not_found: Linked wiki page no longer available page_access_forbidden: You do not have permission to access this wiki page - unknown: An unknown error occurred + unexpected: An unexpected error occurred page_link_component: remove: Remove page link relation_page_links_component: diff --git a/modules/wikis/spec/components/wikis/page_link_component_spec.rb b/modules/wikis/spec/components/wikis/page_link_component_spec.rb index e878c22e349..29cab6f89e8 100644 --- a/modules/wikis/spec/components/wikis/page_link_component_spec.rb +++ b/modules/wikis/spec/components/wikis/page_link_component_spec.rb @@ -45,7 +45,7 @@ RSpec.describe Wikis::PageLinkComponent, type: :component do ) end let(:page_info_result) { Success(page_info) } - let(:permissions) { [] } + let(:permissions) { [:manage_wiki_page_links] } let(:actions) { [] } current_user { create(:user, member_with_permissions: { project => permissions }) } @@ -54,7 +54,7 @@ RSpec.describe Wikis::PageLinkComponent, type: :component do before { render_component } - it "renders the page link successful" do + it "renders the page link successfully" do expect(page).to have_link(text: page_info.title, href: page_info.href) end @@ -62,14 +62,14 @@ RSpec.describe Wikis::PageLinkComponent, type: :component do let(:actions) { [:remove] } context "when the user has no permission to manage wiki page links" do + let(:permissions) { [] } + it "does not render the action menu" do expect(page).not_to have_test_selector("wiki-page-link-action-menu") end end context "when the user has the permission to manage wiki page links" do - let(:permissions) { [:manage_wiki_page_links] } - it "shows the remove page link action in the action menu" do expect(page).to have_test_selector("wiki-page-link-action-menu") end @@ -95,7 +95,7 @@ RSpec.describe Wikis::PageLinkComponent, type: :component do context "if the page was not found" do let(:error_code) { :not_found } - it "renders the page link successful" do + it "renders an error text" do expect(page).not_to have_link expect(page).to have_text(I18n.t("wikis.page_links.errors.page_not_found")) end @@ -104,18 +104,18 @@ RSpec.describe Wikis::PageLinkComponent, type: :component do context "if the page access is forbidden" do let(:error_code) { :forbidden } - it "renders the page link successful" do + it "renders an error text" do expect(page).not_to have_link expect(page).to have_text(I18n.t("wikis.page_links.errors.page_access_forbidden")) end end context "if an unknown error occurred" do - let(:error_code) { "IT'S NO MOON" } + let(:error_code) { :timeout } - it "renders the page link successful" do + it "renders an error text" do expect(page).not_to have_link - expect(page).to have_text(I18n.t("wikis.page_links.errors.unknown")) + expect(page).to have_text(I18n.t("wikis.page_links.errors.unexpected")) end end end