Refactor static links to ensure users have to go through url_for

This ensures links will be translated if they are part of the website
This commit is contained in:
Oliver Günther
2025-09-09 08:51:14 +02:00
parent 996ce97387
commit c84dd850fa
37 changed files with 323 additions and 141 deletions
@@ -22,7 +22,7 @@
frameborder: "0",
height: "400",
width: "100%",
src: OpenProject::Static::Links.links[:enterprise_welcome_video][:href],
src: OpenProject::Static::Links.url_for(:enterprise_welcome_video),
allowfullscreen: true)
end
end
@@ -79,7 +79,7 @@ module WorkPackages
end
def learn_more_href
OpenProject::Static::Links.links[:progress_tracking_docs][:href]
OpenProject::Static::Links.url_for(:progress_tracking_docs)
end
private
+2 -2
View File
@@ -32,11 +32,11 @@ class HelpController < ApplicationController
no_authorization_required! :keyboard_shortcuts, :text_formatting
def keyboard_shortcuts
redirect_to OpenProject::Static::Links[:shortcuts][:href], allow_other_host: true
redirect_to OpenProject::Static::Links.url_for(:shortcuts), allow_other_host: true
end
def text_formatting
default_link = OpenProject::Static::Links[:text_formatting][:href]
default_link = OpenProject::Static::Links.url_for(:text_formatting)
help_link = OpenProject::Configuration.force_formatting_help_link.presence || default_link
redirect_to help_link, allow_other_host: true
+2 -1
View File
@@ -30,6 +30,7 @@
class My::LookAndFeelForm < ApplicationForm
include ApplicationHelper
form do |f|
f.select_list(
name: :theme,
@@ -65,7 +66,7 @@ class My::LookAndFeelForm < ApplicationForm
f.check_box name: :disable_keyboard_shortcuts,
label: I18n.t("activerecord.attributes.user_preference.disable_keyboard_shortcuts"),
caption: I18n.t("activerecord.attributes.user_preference.disable_keyboard_shortcuts_caption_html",
href: OpenProject::Static::Links.links[:shortcuts][:href]).html_safe
href: OpenProject::Static::Links.url_for(:shortcuts)).html_safe
f.submit(name: :submit,
label: I18n.t("activerecord.attributes.user_preference.button_update_look_and_feel"),
+7 -7
View File
@@ -31,11 +31,12 @@
module StaticLinksHelper
##
# Create a static link to the given key entry
def static_link_to(key, label: nil)
item = OpenProject::Static::Links.links.fetch key
def static_link_to(*path, label: nil)
href = OpenProject::Static::Links.url_for(*path)
label_text = label || OpenProject::Static::Links.label_for(*path)
link_to label || t(item[:label]),
item[:href],
link_to label_text,
href,
class: "openproject--static-link",
target: "_blank", rel: "noopener"
end
@@ -44,8 +45,7 @@ module StaticLinksHelper
# Link to the correct installation guides for the current selected method
def installation_guide_link
val = OpenProject::Configuration.installation_type
link = OpenProject::Static::Links.links[:"#{val}_installation"] || OpenProject::Static::Links.links[:installation_guides]
link[:href]
# Try specific installation type first, fallback to general installation guides
OpenProject::Static::Links.url_for(:"#{val}_installation") || OpenProject::Static::Links.url_for(:installation_guides)
end
end
+1 -1
View File
@@ -73,7 +73,7 @@ See COPYRIGHT and LICENSE files for more details.
if display_security_badge_graphic?
content = content_tag :object, nil, data: security_badge_url, type: "image/svg+xml"
content += link_to "",
::OpenProject::Static::Links[:security_badge_documentation][:href],
::OpenProject::Static::Links.url_for(:security_badge_documentation),
title: t(:label_what_is_this),
class: "security-badge--help-icon icon-context icon-help1",
target: "_blank"
@@ -84,7 +84,7 @@ See COPYRIGHT and LICENSE files for more details.
<p><%= t(:text_line_separated) %></p>
<p><%= t(
:setting_apiv3_cors_origins_text_html,
origin_link: ::OpenProject::Static::Links[:origin_mdn_documentation][:href]
origin_link: ::OpenProject::Static::Links.url_for(:origin_mdn_documentation)
) %></p>
</div>
</div>
@@ -69,7 +69,7 @@ See COPYRIGHT and LICENSE files for more details.
<div class="form--field">
<%= setting_select :first_week_of_year, [[day_name(1), "1"], [day_name(4), "4"]], blank: :label_language_based, container_class: "-wide" %>
<div class="form--field-instructions">
<p><%= t("settings.date_format.first_week_of_year_text_html", link: OpenProject::Static::Links[:date_format_settings_documentation][:href]) %></p>
<p><%= t("settings.date_format.first_week_of_year_text_html", link: OpenProject::Static::Links.url_for(:date_format_settings_documentation)) %></p>
</div>
</div>
@@ -96,7 +96,7 @@ See COPYRIGHT and LICENSE files for more details.
<%= t(
:text_notice_security_badge_displayed_html,
information_panel_label: t(:label_information),
more_info_url: ::OpenProject::Static::Links[:security_badge_documentation][:href],
more_info_url: ::OpenProject::Static::Links.url_for(:security_badge_documentation),
information_panel_path: info_admin_index_path
) %>
</span>
@@ -50,7 +50,7 @@ See COPYRIGHT and LICENSE files for more details.
<div class="form--field">
<%= setting_check_box :ical_enabled, size: 6 %>
<div class="form--field-instructions">
<p><%= t("settings.icalendar.enable_subscriptions_text_html", link: OpenProject::Static::Links[:ical_docs][:href]) %></p>
<p><%= t("settings.icalendar.enable_subscriptions_text_html", link: OpenProject::Static::Links.url_for(:ical_docs)) %></p>
</div>
</div>
</section>
+1 -1
View File
@@ -27,7 +27,7 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<% content_for :header_tags do %>
<%= nonced_javascript_include_tag OpenProject::Static::Links.links[:chargebee][:href],
<%= nonced_javascript_include_tag OpenProject::Static::Links.url_for(:chargebee),
"data-cb-site": OpenProject::Configuration.enterprise_chargebee_site %>
<% end %>
@@ -51,7 +51,7 @@
<%= content_tag :div, class: "security-badge--container" do %>
<%= content_tag :object, nil, data: security_badge_url, type: "image/svg+xml" %>
<%= link_to "",
::OpenProject::Static::Links[:security_badge_documentation][:href],
::OpenProject::Static::Links.url_for(:security_badge_documentation),
title: t(:label_what_is_this),
class: "security-badge--help-icon icon-context icon-help1",
target: "_blank", rel: "noopener" %>
+43 -10
View File
@@ -11,30 +11,63 @@
<li>
<%= static_link_to :forums %></li>
<li>
<%= static_link_to (EnterpriseToken.active? ? :enterprise_support : :enterprise_support_as_community) %>
<%= static_link_to(EnterpriseToken.active? ? :enterprise_support : :enterprise_support_as_community) %>
</li>
<li>
<%= link_to(
t("label_openproject_website"), "#{OpenProject::Static::Links.links[:website][:href]}/?utm_source=unknown&utm_medium=op-instance&utm_campaign=website-home-screen",
{ aria: { label: t("label_openproject_website") },
t("label_openproject_website"),
OpenProject::Static::Links.url_for(
:website,
url_params: {
utm_source: "unknown",
utm_medium: "op-instance",
utm_campaign: "website-home-screen"
}
),
{
aria: { label: t("label_openproject_website") },
target: "_blank",
title: t("label_openproject_website"), rel: "noopener" }
title: t("label_openproject_website"),
rel: "noopener"
}
) %>
</li>
<li>
<%= link_to(
t("homescreen.links.security_alerts"), "#{OpenProject::Static::Links.links[:security_alerts][:href]}/?utm_source=unknown&utm_medium=op-instance&utm_campaign=security-alerts-home-screen",
{ aria: { label: t("homescreen.links.security_alerts") },
t("homescreen.links.security_alerts"),
OpenProject::Static::Links.url_for(
:security_alerts,
url_params: {
utm_source: "unknown",
utm_medium: "op-instance",
utm_campaign: "security-alerts-home-screen"
}
),
{
aria: { label: t("homescreen.links.security_alerts") },
target: "_blank",
title: t("homescreen.links.security_alerts"), rel: "noopener" }
title: t("homescreen.links.security_alerts"),
rel: "noopener"
}
) %>
</li>
<li>
<%= link_to(
t("homescreen.links.newsletter"), "#{OpenProject::Static::Links.links[:newsletter][:href]}/?utm_source=unknown&utm_medium=op-instance&utm_campaign=newsletter-home-screen",
{ aria: { label: t("homescreen.links.newsletter") },
t("homescreen.links.newsletter"),
OpenProject::Static::Links.url_for(
:newsletter,
url_params: {
utm_source: "unknown",
utm_medium: "op-instance",
utm_campaign: "newsletter-home-screen"
}
),
{
aria: { label: t("homescreen.links.newsletter") },
target: "_blank",
title: t("homescreen.links.newsletter"), rel: "noopener" }
title: t("homescreen.links.newsletter"),
rel: "noopener"
}
) %>
</li>
<li>
+12 -5
View File
@@ -51,11 +51,18 @@ See COPYRIGHT and LICENSE files for more details.
<section class="homescreen--links">
<% @homescreen[:links].each do |link| %>
<% title = I18n.t(link[:label], scope: "homescreen.links") %>
<a class="homescreen--links--item" href="<%= link[:url] %>" target="_blank" aria-label="<%= title %>">
<%= op_icon(link[:icon]) %>
<%= title %>
</a>
<% url = link[:url] || OpenProject::Static::Links.url_for(link[:url_key]) %>
<% if url %>
<% title = I18n.t(link[:label], scope: "homescreen.links") %>
<%= link_to url,
class: "homescreen--links--item",
target: "_blank",
aria_label: title do %>
<%= op_icon(link[:icon]) %>
<%= title %>
<% end %>
<% end %>
<% end %>
</section>
<% end %>
+1 -1
View File
@@ -73,7 +73,7 @@ See COPYRIGHT and LICENSE files for more details.
<legend class="form--fieldset-legend"><%= t("ldap_auth_sources.connection_encryption") %></legend>
<p>
<%= t "ldap_auth_sources.tls_mode.section_more_info_link_html",
link: OpenProject::Static::Links[:ldap_encryption_documentation][:href] %>
link: OpenProject::Static::Links.url_for(:ldap_encryption_documentation) %>
</p>
<div class="form--field">
<%= f.radio_button :tls_mode,
@@ -40,10 +40,10 @@ See COPYRIGHT and LICENSE files for more details.
<p>
<%= t(
:mail_body_register_links_html,
webinar_link: link_to("Webinar Link", OpenProject::Static::Links.links[:webinar_videos][:href]),
webinar_link: link_to("Webinar Link", OpenProject::Static::Links.url_for(:webinar_videos)),
youtube_link: link_to("Youtube Link", OpenProject::Configuration.youtube_channel),
get_started_link: link_to("Get Started Videos Link", OpenProject::Static::Links.links[:get_started_videos][:href]),
documentation_link: link_to("Documentation Link", OpenProject::Static::Links.links[:openproject_docs][:href])
get_started_link: link_to("Get Started Videos Link", OpenProject::Static::Links.url_for(:get_started_videos)),
documentation_link: link_to("Documentation Link", OpenProject::Static::Links.url_for(:openproject_docs))
) %>
</p>
@@ -33,10 +33,10 @@ See COPYRIGHT and LICENSE files for more details.
<%= strip_tags t(
:mail_body_register_links_html,
webinar_link: OpenProject::Static::Links.links[:webinar_videos][:href],
webinar_link: OpenProject::Static::Links.url_for(:webinar_videos),
youtube_link: OpenProject::Configuration.youtube_channel,
get_started_link: OpenProject::Static::Links.links[:get_started_videos][:href],
documentation_link: OpenProject::Static::Links.links[:openproject_docs][:href]
get_started_link: OpenProject::Static::Links.url_for(:get_started_videos),
documentation_link: OpenProject::Static::Links.url_for(:openproject_docs)
)
%>
+1 -1
View File
@@ -46,7 +46,7 @@ See COPYRIGHT and LICENSE files for more details.
<span class="form--field-instructions">
<%= I18n.t(
"activerecord.attributes.user_preference.disable_keyboard_shortcuts_caption_html",
href: OpenProject::Static::Links.links[:shortcuts][:href]
href: OpenProject::Static::Links.url_for(:shortcuts)
).html_safe %>
</span>
</div>
+9 -14
View File
@@ -72,36 +72,31 @@ OpenProject::Static::Homescreen.manage :blocks do |blocks|
end
OpenProject::Static::Homescreen.manage :links do |links|
link_hash = OpenProject::Static::Links.links
links.push(
{
label: :user_guides,
icon: "icon-context icon-rename",
url: link_hash[:user_guides][:href]
url_key: :user_guides
},
{
label: :glossary,
icon: "icon-context icon-glossar",
url: link_hash[:glossary][:href]
url_key: :glossary
},
{
label: :shortcuts,
icon: "icon-context icon-shortcuts",
url: link_hash[:shortcuts][:href]
url_key: :shortcuts
},
{
label: :forums,
icon: "icon-context icon-forums",
url: link_hash[:forums][:href]
url_key: :forums
},
{
label: :impressum,
icon: "icon-context icon-info1",
url_key: :impressum
}
)
if impressum_link = link_hash[:impressum]
links.push({
label: :impressum,
url: impressum_link[:href],
icon: "icon-context icon-info1"
})
end
end
+2
View File
@@ -81,6 +81,8 @@ get_started_videos:
glossary:
href: https://www.openproject.org/docs/glossary/
label: homescreen.links.glossary
github:
href: https://github.com/opf/openproject
ical_docs:
href: https://www.openproject.org/docs/user-guide/calendar/#subscribe-to-a-calendar
installation_guides:
+2 -2
View File
@@ -94,9 +94,9 @@ class OpenProject::JournalFormatter::Cause < JournalFormatter::Base
case feature
when "progress_calculation_adjusted_from_disabled_mode",
"progress_calculation_adjusted"
{ href: OpenProject::Static::Links.links[:blog_article_progress_changes][:href] }
{ href: OpenProject::Static::Links.url_for(:blog_article_progress_changes) }
when "totals_removed_from_childless_work_packages"
{ href: OpenProject::Static::Links.links[:release_notes_14_0_1][:href] }
{ href: OpenProject::Static::Links.url_for(:release_notes_14_0_1) }
else
{}
end
+32 -20
View File
@@ -37,22 +37,28 @@ module OpenProject
end
def help_link
OpenProject::Configuration.force_help_link.presence || static_links[:user_guides]
OpenProject::Configuration.force_help_link.presence || static_links[:user_guides][:href]
end
delegate :[], to: :links
def links
@links ||= static_links.merge(dynamic_links)
def cache_key
@cache_key ||= OpenProject::Cache::CacheKey.expand(links)
end
def url_for(*items, localize_url: true)
href = links.dig(*items, :href)
def label_for(*path)
key = links.dig(*path, :label)
return if key.nil?
if localize_url && docs_url?(href)
with_locale_param(href)
I18n.t(key)
end
def url_for(*path, localize_url: true, url_params: {})
href = links.dig(*path, :href)
return if href.nil?
if localize_url && website_link?(href)
url_with_query(href, **url_params, go_to_locale: I18n.locale)
else
href
url_with_query(href, **url_params)
end
end
@@ -60,22 +66,28 @@ module OpenProject
@links.key? name
end
def docs_url?(url)
url&.start_with?(docs_url)
def website_link?(url)
url&.start_with?(website_url)
end
def docs_url
links[:openproject_docs][:href]
end
def with_locale_param(href)
url = Addressable::URI.parse(href)
url.query_values = (url.query_values || {}).merge(go_to_locale: I18n.locale)
url.to_s
def website_url
links[:website][:href]
end
private
def links
@links ||= static_links.merge(dynamic_links)
end
def url_with_query(href, **params)
return href if params.empty?
url = Addressable::URI.parse(href)
url.query_values = (url.query_values || {}).merge(params)
url.to_s
end
def dynamic_links
dynamic = {
help: {
+24 -8
View File
@@ -31,7 +31,7 @@
module Redmine::MenuManager::TopMenu::HelpMenu
def render_help_top_menu_node(item = help_menu_item)
cache_key = ["help_top_menu_node",
OpenProject::Static::Links.links,
OpenProject::Static::Links.cache_key,
I18n.locale,
OpenProject::Static::Links.help_link,
EnterpriseToken.active?]
@@ -100,7 +100,11 @@ module Redmine::MenuManager::TopMenu::HelpMenu
unless EnterpriseToken.hide_banners? && EnterpriseToken.active?
menu_group.with_item(
**link_options_for(:upsell,
href_suffix: "/?utm_source=unknown&utm_medium=op-instance&utm_campaign=ee-upsell-help-menu")
url_params: {
utm_source: "unknown",
utm_medium: "op-instance",
utm_campaign: "ee-upsell-help-menu"
})
)
end
menu_group.with_item(**link_options_for(:user_guides))
@@ -131,15 +135,27 @@ module Redmine::MenuManager::TopMenu::HelpMenu
menu_group.with_item(**link_options_for(:digital_accessibility))
menu_group.with_item(**link_options_for(
:website,
href_suffix: "/?utm_source=unknown&utm_medium=op-instance&utm_campaign=website-help-menu"
url_params: {
utm_source: "unknown",
utm_medium: "op-instance",
utm_campaign: "website-help-menu"
}
))
menu_group.with_item(**link_options_for(
:security_alerts,
href_suffix: "/?utm_source=unknown&utm_medium=op-instance&utm_campaign=security-help-menu"
url_params: {
utm_source: "unknown",
utm_medium: "op-instance",
utm_campaign: "security-help-menu"
}
))
menu_group.with_item(**link_options_for(
:newsletter,
href_suffix: "/?utm_source=unknown&utm_medium=op-instance&utm_campaign=newsletter-help-menu"
url_params: {
utm_source: "unknown",
utm_medium: "op-instance",
utm_campaign: "newsletter-help-menu"
}
))
menu_group.with_item(**link_options_for(:blog))
menu_group.with_item(**link_options_for(:release_notes))
@@ -151,11 +167,11 @@ module Redmine::MenuManager::TopMenu::HelpMenu
end
def link_options_for(key, options = {})
link = OpenProject::Static::Links.links[key]
label = I18n.t(link[:label])
href = OpenProject::Static::Links.url_for(key, url_params: options[:url_params] || {})
label = OpenProject::Static::Links.label_for(key)
{
href: "#{link[:href]}#{options[:href_suffix]}",
href: href,
label: label,
content_arguments: {
target: "_blank",
@@ -87,7 +87,7 @@ module Storages::Admin::Forms
I18n.t(
"storages.instructions.#{provider_type}.provider_configuration",
application_link_text: application_link_text_for(
::OpenProject::Static::Links[:storage_docs][:"#{provider_type}_oauth_application"][:href],
::OpenProject::Static::Links.url_for(:storage_docs, :"#{provider_type}_oauth_application"),
I18n.t("storages.instructions.#{provider_type}.application_link_text")
)
).html_safe
@@ -57,12 +57,12 @@ module Storages::Admin::Forms
end
def one_drive_integration_link(target: "_blank")
href = ::OpenProject::Static::Links[:storage_docs][:one_drive_oauth_application][:href]
href = ::OpenProject::Static::Links.url_for(:storage_docs, :one_drive_oauth_application)
render(Primer::Beta::Link.new(href:, underline: true, target:)) { I18n.t("storages.instructions.one_drive.application_link_text") }
end
def sharepoint_integration_link(target: "_blank")
href = ::OpenProject::Static::Links[:storage_docs][:sharepoint_oauth_application][:href]
href = ::OpenProject::Static::Links.url_for(:storage_docs, :sharepoint_oauth_application)
render(Primer::Beta::Link.new(href:, underline: true, target:)) { I18n.t("storages.instructions.sharepoint.application_link_text") }
end
@@ -57,7 +57,7 @@ class Storages::OpenProjectStorageModalComponent < ViewComponent::Base
end
def subtitle_timeout_text
href = OpenProject::Static::Links[:storage_docs][:health_status][:href]
href = OpenProject::Static::Links.url_for(:storage_docs, :health_status)
I18n.t(
"storages.open_project_storage_modal.timeout.subtitle",
storages_health_link: render(Primer::Beta::Link.new(href:, target: "_blank", underline: true)) do
@@ -44,7 +44,7 @@ module Storages::Admin
private
def caption
href = ::OpenProject::Static::Links[:storage_docs][:one_drive_drive_id_guide][:href]
href = ::OpenProject::Static::Links.url_for(:storage_docs, :one_drive_drive_id_guide)
I18n.t("storages.instructions.one_drive.drive_id",
drive_id_link_text: render(Primer::Beta::Link.new(href:, underline: true, target: "_blank")) do
I18n.t("storages.instructions.one_drive.documentation_link_text")
@@ -45,7 +45,7 @@ module Storages::Admin
private
def caption
href = ::OpenProject::Static::Links[:storage_docs][:one_drive_oauth_application][:href]
href = ::OpenProject::Static::Links.url_for(:storage_docs, :one_drive_oauth_application)
I18n.t("storages.instructions.one_drive.tenant_id",
application_link_text: render(Primer::Beta::Link.new(href:, underline: true, target: "_blank")) do
I18n.t("storages.instructions.one_drive.application_link_text")
@@ -18,7 +18,7 @@
<% header.with_description(test_selector: 'storage-new-page-header--description') do %>
<%=
t("storages.instructions.new_storage",
provider_link: ::OpenProject::Static::Links[:storage_docs][:"#{@storage}_setup"][:href].html_safe,
provider_link: ::OpenProject::Static::Links.url_for(:storage_docs, :"#{@storage}_setup"),
provider_name: I18n.t("storages.provider_types.#{@storage}.name")
).html_safe
%>
@@ -87,7 +87,7 @@ See COPYRIGHT and LICENSE files for more details.
<td style="color: #878787; font-size: 12px; font-weight: 400; line-height: 16px;">
<%= I18n.t("mail.storages.health.unhealthy.troubleshooting.text") %>
<a href="<%= ::OpenProject::Static::Links[:storage_docs][:troubleshooting][:href] %>"><%= I18n.t("mail.storages.health.unhealthy.troubleshooting.link_text") %></a>.
<a href="<%= ::OpenProject::Static::Links.url_for(:storage_docs, :troubleshooting) %>"><%= I18n.t("mail.storages.health.unhealthy.troubleshooting.link_text") %></a>.
</td>
<%= placeholder_cell("12px", vertical: true) %>
@@ -22,9 +22,9 @@
<p>
<%= t("two_factor_authentication.settings.text_configuration") %>
<br>
<% configuration_link = OpenProject::Static::Links.links.fetch :configuration_guide %>
<% configuration_link = OpenProject::Static::Links.url_for :configuration_guide %>
<%= link_to t("two_factor_authentication.settings.text_configuration_guide"),
configuration_link[:href],
configuration_link,
target: "_blank" %>
</p>
<%= render(AttributeGroups::AttributeGroupComponent.new) do |component|
@@ -2,7 +2,7 @@
<p>
<%= t "webhooks.outgoing.form.introduction" %>
<br>
<%= link_to t("webhooks.outgoing.form.apiv3_doc_url"), OpenProject::Static::Links.links[:api_docs][:href] %>
<%= link_to t("webhooks.outgoing.form.apiv3_doc_url"), OpenProject::Static::Links.url_for(:api_docs) %>
</p>
<div class="form--field -required">
@@ -257,7 +257,7 @@ RSpec.describe EnterpriseEdition::BannerComponent, type: :component do
expect(component).to have_text(expected_title)
expect(component).to have_text(expected_description)
expect(component).to have_link("More information", href: "https://www.openproject.org/enterprise-edition")
expect(component).to have_link("More information", href: "https://www.openproject.org/enterprise-edition?go_to_locale=mo")
end
end
@@ -74,7 +74,7 @@ RSpec.shared_examples_for "progress modal help links" do
expect(page)
.to have_link("Learn more",
href: OpenProject::Static::Links.links[:progress_tracking_docs][:href])
href: OpenProject::Static::Links.url_for(:progress_tracking_docs))
end
end
end
@@ -450,7 +450,7 @@ RSpec.describe OpenProject::JournalFormatter::Cause do
end
it do
href = OpenProject::Static::Links.links[:blog_article_progress_changes][:href]
href = OpenProject::Static::Links.url_for(:blog_article_progress_changes)
expect(cause).to render_html_variant(
"<strong>OpenProject system update:</strong> Progress calculation automatically " \
"<a href=\"#{href}\" target=\"_blank\">set to work-based mode and adjusted with version update</a>."
@@ -474,7 +474,7 @@ RSpec.describe OpenProject::JournalFormatter::Cause do
end
it do
href = OpenProject::Static::Links.links[:blog_article_progress_changes][:href]
href = OpenProject::Static::Links.url_for(:blog_article_progress_changes)
expect(cause).to render_html_variant(
"<strong>OpenProject system update:</strong> Progress calculation automatically " \
"<a href=\"#{href}\" target=\"_blank\">adjusted with version update</a>."
@@ -509,7 +509,7 @@ RSpec.describe OpenProject::JournalFormatter::Cause do
end
it do
href = OpenProject::Static::Links.links[:release_notes_14_0_1][:href]
href = OpenProject::Static::Links.url_for(:release_notes_14_0_1)
expect(cause).to render_html_variant(
"<strong>OpenProject system update:</strong> Work and progress totals " \
"automatically removed for non-parent work packages with " \
+153 -37
View File
@@ -61,18 +61,123 @@ RSpec.describe OpenProject::Static::Links do
end
end
context "with non-docs URLs" do
context "with website URL" do
let(:args) { %i[website] }
it "does not add locale parameter to non-docs URLs" do
expect(subject).to eq("https://www.openproject.org")
it "adds locale parameter to website URL" do
expect(subject).to eq("https://www.openproject.org?go_to_locale=en")
end
end
context "with other URLs" do
let(:args) { %i[github] }
it "does not add a parameter" do
expect(subject).to eq("https://github.com/opf/openproject")
expect(subject).not_to include("go_to_locale=")
end
end
context "with additional URL parameters" do
let(:args) { %i[website] }
it "adds custom URL parameters" do
result = described_class.url_for(*args, url_params: { utm_source: "test", utm_medium: "spec" })
expect(result).to include("utm_source=test")
expect(result).to include("utm_medium=spec")
end
end
context "with non-existent path" do
let(:args) { %i[non_existent_key] }
it "returns nil for non-existent paths" do
expect(subject).to be_nil
end
end
context "with localize_url disabled" do
let(:args) { %i[enterprise_features board_view] }
it "does not add locale parameter when localize_url is false" do
result = described_class.url_for(*args, localize_url: false)
expect(result).not_to include("go_to_locale=")
expect(result).to eq("https://www.openproject.org/docs/user-guide/agile-boards/#action-boards-enterprise-add-on")
end
end
end
describe ".docs_url?" do
subject { described_class.docs_url?(url) }
describe ".label_for" do
subject { described_class.label_for(*args) }
let(:args) { %i[website] }
it "returns the translated label for the given path" do
expect(subject).to eq(I18n.t("label_openproject_website"))
end
context "with single key" do
let(:args) { %i[shortcuts] }
it "returns the translated label for a single key" do
expect(subject).to eq(I18n.t("homescreen.links.shortcuts"))
end
end
context "with non-existent path" do
let(:args) { %i[non_existent_key] }
it "returns nil for non-existent paths" do
expect(subject).to be_nil
end
end
end
describe ".cache_key" do
subject { described_class.cache_key }
it "returns a cache key based on the links" do
expect(subject).to be_a(String)
expect(subject).not_to be_empty
end
it "returns the same key for multiple calls" do
first_call = described_class.cache_key
second_call = described_class.cache_key
expect(first_call).to eq(second_call)
end
end
describe ".has?" do
subject { described_class.has?(key) }
context "with existing key" do
let(:key) { :website }
it "returns true for existing keys" do
expect(subject).to be true
end
end
context "with non-existing key" do
let(:key) { :non_existent_key }
it "returns false for non-existing keys" do
expect(subject).to be false
end
end
end
describe ".website_url" do
subject { described_class.website_url }
it "returns the website URL" do
expect(subject).to eq("https://www.openproject.org")
end
end
describe ".website_link?" do
subject { described_class.website_link?(url) }
context "with docs URLs" do
let(:url) { "https://www.openproject.org/docs/user-guide/agile-boards/" }
@@ -83,56 +188,67 @@ RSpec.describe OpenProject::Static::Links do
end
context "with non-docs URLs" do
let(:url) { "https://www.openproject.org/enterprise-edition" }
let(:url) { "https://foo.example.com" }
it "returns false for URLs that do not start with the docs base URL" do
expect(subject).to be false
end
end
end
describe ".with_locale_param" do
subject { described_class.with_locale_param(href) }
context "with nil URL" do
let(:url) { nil }
let(:href) { "https://www.openproject.org/docs/system-admin-guide/authentication/openid-providers/" }
before do
allow(I18n).to receive(:locale).and_return(:en)
end
it "adds go_to_locale parameter to the URL" do
expect(subject).to include("go_to_locale=en")
end
it "preserves the original URL structure" do
expect(subject).to start_with(href)
end
context "with URL that already has query parameters" do
let(:href) { "https://www.openproject.org/docs/user-guide/agile-boards/?section=boards" }
it "adds go_to_locale parameter while preserving existing parameters" do
expect(subject).to include("section=boards")
expect(subject).to include("go_to_locale=en")
it "returns false for nil URLs" do
expect(subject).to be_falsy
end
end
end
context "with different locale" do
describe ".help_link_overridden?" do
subject { described_class.help_link_overridden? }
context "when help link is not overridden" do
before do
allow(I18n).to receive(:locale).and_return(:de)
allow(OpenProject::Configuration).to receive(:force_help_link).and_return(nil)
end
it "uses the current I18n locale" do
expect(subject).to include("go_to_locale=de")
it "returns false" do
expect(subject).to be false
end
end
context "when help link is overridden" do
before do
allow(OpenProject::Configuration).to receive(:force_help_link).and_return("https://custom.help.com")
end
it "returns true" do
expect(subject).to be true
end
end
end
describe ".docs_url" do
subject { described_class.docs_url }
describe ".help_link" do
subject { described_class.help_link }
it "returns the base docs URL" do
expect(subject).to eq("https://www.openproject.org/docs/")
context "when help link is not overridden" do
before do
allow(OpenProject::Configuration).to receive(:force_help_link).and_return(nil)
end
it "returns the default user guides link" do
expect(subject).to eq("https://www.openproject.org/docs/user-guide/")
end
end
context "when help link is overridden" do
before do
allow(OpenProject::Configuration).to receive(:force_help_link).and_return("https://custom.help.com")
end
it "returns the overridden help link" do
expect(subject).to eq("https://custom.help.com")
end
end
end
end
+2 -2
View File
@@ -30,7 +30,7 @@
# rubocop:disable RSpec/ContextWording
RSpec.shared_context "support links" do
let(:support_link_as_community) { "https://www.openproject.org/pricing/#support" }
let(:support_link_as_enterprise) { "https://www.openproject.org/docs/enterprise-guide/support/" }
let(:support_link_as_community) { "https://www.openproject.org/pricing/?go_to_locale=en#support" }
let(:support_link_as_enterprise) { "https://www.openproject.org/docs/enterprise-guide/support/?go_to_locale=en" }
end
# rubocop:enable RSpec/ContextWording