[#70166] Fix accessibility errors found by ERB Lint (#21503)

* Fix GitHub/NoTitleAttribute, LinkHasHref errors

- Replaces `title` attribute with `aria-label` for interactive elements.
- Removes `title` from non-interactive elements.
- Converts `<a>` tags without proper `href` to `<button>` elements,
  using Primer `Button`/`IconButton` where possible.

# Conflicts:
#	app/views/custom_fields/_custom_options.html.erb
#	spec/features/admin/custom_fields/shared_custom_field_expectations.rb
#	spec/features/admin/custom_fields/work_packages/list_spec.rb

* Fix Autocomplete missing errors

* Fix GitHub/NoPositiveTabIndex errors

Removes all positive `tabindex` values.

* Fix Rails/LinkToBlank errors

* Replace toast with Primer Banner on LDAP form

* Add frozen_string_literal

* Ignore erb lint for deprecated files

* Fix linting errors in repository module

* Fix linting errors in budgets and custom actions

* Fix linting errors in member form and 2fa

* Fix linting errors in mcost types and wiki help and storages

* Fix linting errors in multi select filters, ifc viewer, and unsupported browser banner

* Fix failing spec

* Use Primer banner instead of op-toast where ever it is possible

* Use octicon instead of op_icon

* Fix failing tests

* Use no-decoration-on-hover for button links and change the button with only an icon to primer icon button

* Keep webhook response modal activation selector class-based

* use icon button for edit of hourly rate

---------

Co-authored-by: Behrokh Satarnejad <b.satarnejad@openproject.com>
This commit is contained in:
Alexander Brandon Coles
2026-05-07 09:31:10 +01:00
committed by GitHub
parent e378301f3c
commit e8767481e9
41 changed files with 266 additions and 208 deletions
+1
View File
@@ -5,6 +5,7 @@ inherit_gem:
- config/accessibility.yml
exclude:
- '**/frontend/**/*'
- 'lookbook/previews/open_project/deprecated/**/*'
- '**/node_modules/**/*'
- '**/vendor/**/*'
linters:
@@ -112,7 +112,7 @@ See COPYRIGHT and LICENSE files for more details.
<% end %>
<li class="simple-filters--filter">
<label class='simple-filters--filter-name' for='name'><%= User.human_attribute_name :name %>:</label>
<%= text_field_tag "name", params[:name], class: "simple-filters--filter-value" %>
<%= text_field_tag "name", params[:name], class: "simple-filters--filter-value", autocomplete: "off" %>
</li>
<li class="simple-filters--controls">
<%= submit_tag t(:button_apply), class: "button -primary -small", name: nil %>
+14 -9
View File
@@ -58,8 +58,7 @@
<section class="hide-section"
data-hide-sections-target="section"
data-name="<%= action.key %>"
<%= tag.attributes(data: { section_name: action.key }, hidden: active_section_keys.include?(action.key) ? nil : true) %>
>
<%= tag.attributes(data: { section_name: action.key }, hidden: active_section_keys.exclude?(action.key)) %>>
<div class="form--field">
<%= styled_label_tag("custom_action_actions_#{action.key}", action.human_name, class: "-top") %>
@@ -157,12 +156,18 @@
step: 1 %>
</div>
<% end %>
<button type="button"
class="spot-link"
title="<%= t(:button_close) %>"
data-action="click->hide-sections#hide">
<%= op_icon("icon-close icon-small") %>
</button>
<%=
render(
Primer::Beta::IconButton.new(
icon: :x,
size: :small,
scheme: :invisible,
type: :button,
aria: { label: t(:button_close) },
data: { action: "click->hide-sections#hide" }
)
)
%>
</div>
</section>
<% end %>
@@ -172,7 +177,7 @@
<div class="form--field">
<label class="form--label" for="add-custom-action-select">
<%= op_icon("icon-add icon4") %>
<%= render(Primer::Beta::Octicon.new(icon: :plus, size: :small)) %>
<%= I18n.t(:"custom_actions.actions.add") %>
</label>
<span class="form--field-container">
@@ -1,4 +1,4 @@
<%#-- copyright
<%# -- copyright
OpenProject is an open source project management software.
Copyright (C) the OpenProject GmbH
@@ -25,7 +25,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>
++# %>
<% content_controller "admin--custom-fields",
"admin--custom-fields-multi-select-value": @custom_field.multi_value? %>
@@ -103,39 +103,32 @@ See COPYRIGHT and LICENSE files for more details.
no_label: true %>
</td>
<td>
<span class="reorder-icons">
<a
aria-label="<%= t(:label_sort_highest) %>"
rel="nofollow"
href="#"
class="sort-up-custom-option"
data-action="admin--custom-fields#moveRowToTheTop">
<%= op_icon("icon-context icon-sort-up icon-small") %>
</a>
<a
aria-label="<%= t(:label_sort_higher) %>"
rel="nofollow"
href="#"
class="move-up-custom-option"
data-action="admin--custom-fields#moveRowUp">
<%= op_icon("icon-context icon-arrow-up2 icon-small") %>
</a>
<a
aria-label="<%= t(:label_sort_lower) %>"
rel="nofollow" href="#"
class="move-down-custom-option"
data-action="admin--custom-fields#moveRowDown">
<%= op_icon("icon-context icon-arrow-down2 icon-small") %>
</a>
<a
aria-label="<%= t(:label_sort_lowest) %>"
rel="nofollow"
href="#"
class="sort-down-custom-option"
data-action="admin--custom-fields#moveRowToTheBottom">
<%= op_icon("icon-context icon-sort-down icon-small") %>
</a>
</span>
<span class="reorder-icons">
<%=
render(Primer::Beta::ButtonGroup.new(scheme: :invisible, size: :small)) do |component|
component.with_button(
icon: :"move-to-top",
"aria-label": t(:label_sort_highest),
data: { action: "admin--custom-fields#moveRowToTheTop" }
)
component.with_button(
icon: :"chevron-up",
"aria-label": t(:label_sort_higher),
data: { action: "admin--custom-fields#moveRowUp" }
)
component.with_button(
icon: :"chevron-down",
"aria-label": t(:label_sort_lower),
data: { action: "admin--custom-fields#moveRowDown" }
)
component.with_button(
icon: :"move-to-bottom",
"aria-label": t(:label_sort_lowest),
data: { action: "admin--custom-fields#moveRowToTheBottom" }
)
end
%>
</span>
</td>
<td>
<%= link_to "",
@@ -146,7 +139,7 @@ See COPYRIGHT and LICENSE files for more details.
turbo_confirm: t(:"custom_fields.confirm_destroy_option")
},
class: "icon icon-delete delete-custom-option",
title: t(:button_delete) %>
"aria-label": t(:button_delete) %>
</td>
</tr>
<% end %>
+1 -1
View File
@@ -22,7 +22,7 @@
tabindex="0"
data-action="click->filter--filters-form#toggleMultiSelect"
data-filter--filters-form-filter-name-param="<%= filter.name %>">
<span class="icon-context icon-button <%= multi_value ? "icon-minus2" : "icon-add" %> icon4" title="<%= t(:label_enable_multi_select) %>">
<span class="icon-context icon-button <%= multi_value ? "icon-minus2" : "icon-add" %> icon4">
<span class="sr-only"><%= t(:label_enable_multi_select) %></span>
</span>
</a>
+17 -22
View File
@@ -1,4 +1,4 @@
<%#-- copyright
<%# -- copyright
OpenProject is an open source project management software.
Copyright (C) the OpenProject GmbH
@@ -25,7 +25,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>
++# %>
<%#
needs locals:
@@ -35,29 +35,24 @@ See COPYRIGHT and LICENSE files for more details.
<%= error_messages_for :ldap_auth_source %>
<% if @ldap_auth_source.new_record? %>
<div class="op-toast -info">
<a title="close" class="op-toast--close icon-context icon-close"></a>
<div class="op-toast--content">
<%=
link_translate(
"ldap_auth_sources.technical_warning",
links: {
docs_url: %i[sysadmin_docs ldap]
}
)
%>
</div>
</div>
<%=
render(Primer::Alpha::Banner.new(scheme: :warning, mb: 2, dismiss_scheme: :hide)) do
link_translate(
"ldap_auth_sources.technical_warning",
links: {
docs_url: %i[sysadmin_docs ldap]
}
)
end
%>
<% end %>
<% if @ldap_auth_source.seeded_from_env? %>
<div class="op-toast -warning">
<div class="op-toast--content">
<%= t(:label_seeded_from_env_warning) %>
<br>
<%= link_to t("ldap_auth_sources.back_to_index"), { action: :index } %>
</div>
</div>
<%= render(Primer::Alpha::Banner.new(scheme: :warning, icon: :alert, mb: 2)) do %>
<%= t(:label_seeded_from_env_warning) %>
<br>
<%= link_to t("ldap_auth_sources.back_to_index"), { action: :index } %>
<% end %>
<% end %>
<%= content_tag :fieldset, class: "form--fieldset", disabled: @ldap_auth_source.seeded_from_env? do %>
+32 -15
View File
@@ -39,9 +39,19 @@ See COPYRIGHT and LICENSE files for more details.
"user-limit-member-autocompleter-value": true
}
) do |f| %>
<a title="<%= t("js.close_form_title") %>"
class="hide-member-form-button form--close icon-context icon-close"
data-action="members-form#hideAddMemberForm"></a>
<%=
render(
Primer::Beta::IconButton.new(
icon: :x,
scheme: :invisible,
classes: "hide-member-form-button form--close",
tooltip_direction: :se,
aria: { label: t("js.close_form_title") },
data: { action: "members-form#hideAddMemberForm" }
)
)
%>
<div id="new-member-message"></div>
<div class="grid-block">
<div class="grid-content medium-5 small-12 collapse -flex">
@@ -52,7 +62,7 @@ See COPYRIGHT and LICENSE files for more details.
user_id_title = I18n.t(:label_principal_search)
if current_user.admin?
user_id_title += I18n.t(:label_principal_invite_via_email)
user_id_title = "#{user_id_title}#{I18n.t(:label_principal_invite_via_email)}"
end
%>
<%= styled_label_tag :member_user_ids, user_id_title %>
@@ -61,7 +71,7 @@ See COPYRIGHT and LICENSE files for more details.
inputs: {
inputName: "member[user_ids]",
inputBindValue: "id",
url: autocomplete_for_member_project_members_path + ".json",
url: "#{autocomplete_for_member_project_members_path}.json",
multiple: true
} %>
</div>
@@ -99,16 +109,23 @@ See COPYRIGHT and LICENSE files for more details.
</div>
<% if OpenProject::Enterprise.user_limit.present? %>
<div class="op-toast -warning icon-warning d-none"
data-user-limit-target="limitWarning">
<div class="op-toast--content">
<p><%=
link_translate(
"warning_user_limit_reached#{'_admin' if current_user.admin?}",
links: { upgrade_url: OpenProject::Enterprise.upgrade_url }
) %>
</div>
</div>
<%=
render(
Primer::Alpha::Banner.new(
scheme: :warning,
icon: :alert,
classes: "d-none",
data: { "user-limit-target": "limitWarning" }
)
) do
%>
<%=
link_translate(
"warning_user_limit_reached#{'_admin' if current_user.admin?}",
links: { upgrade_url: OpenProject::Enterprise.upgrade_url }
)
%>
<% end %>
<% end %>
<% end %>
@@ -49,8 +49,7 @@ See COPYRIGHT and LICENSE files for more details.
<button
class="button button_no-margin spot-action-bar--action spot-modal--cancel-button"
data-tour-selector="modal-close-button"
dynamic-content-modal-close-button
title=<%= t(:button_close) %>><%= t(:button_close) %></button>
dynamic-content-modal-close-button><%= t(:button_close) %></button>
<%= styled_button_tag t(:button_save), class: "button_no-margin -primary -with-icon icon-checkmark spot-action-bar--action" %>
</div>
</div>
@@ -27,7 +27,7 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<div class="form--field">
<label class="form--label" title="<%= label %>"><%= label %></label>
<label class="form--label"><%= label %></label>
<span class="form--field-container">
<td align="center">
<%= number_to_human_size(value, precision: 2) %></td>
@@ -30,9 +30,10 @@ See COPYRIGHT and LICENSE files for more details.
<div id="repository--checkout-instructions"
class="op-toast -info persistent-toggle--toaster"
hidden>
<a title="{{ ::I18n.t('js.close_popup_title') }}"
class="op-toast--close icon-context icon-close">
</a>
<button type="button"
aria-label="{{ ::I18n.t('js.close_popup_title') }}"
class="op-toast--close icon-context icon-close button--link">
</button>
<div class="op-toast--content">
<p>
<%= simple_format instructions.instructions %>
@@ -45,12 +46,7 @@ See COPYRIGHT and LICENSE files for more details.
</div>
</div>
<% elsif @instructions.supported_but_not_enabled? %>
<div class="op-toast -warning">
<a title="{{ ::I18n.t('js.close_popup_title') }}"
class="op-toast--close icon-context icon-close">
</a>
<div class="op-toast--content">
<p><%= t("repositories.checkout.not_available") %></p>
</div>
</div>
<%= render(Primer::Alpha::Banner.new(scheme: :warning, icon: :alert, dismiss_scheme: :hide)) do %>
<%= t("repositories.checkout.not_available") %>
<% end %>
<% end %>
+7 -7
View File
@@ -27,9 +27,9 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<%= render(Repositories::Revision::PageHeaderComponent.new(changeset: @changeset, repository: @repository, project: @project)) %>
<%= render(Repositories::Revision::PageHeaderComponent.new(changeset: @changeset, repository: @repository, project: @project)) %>
<%= form_tag({ controller: "/repositories", action: "revision", project_id: @project }, method: :get) do %>
<%= text_field_tag :rev, @rev, placeholder: t(:label_revision) %>
<%= text_field_tag :rev, @rev, placeholder: t(:label_revision), autocomplete: "off" %>
<% end %>
<p><% if @changeset.scmid %>ID: <%= h(@changeset.scmid) %><br>
<% end %>
@@ -46,11 +46,11 @@ See COPYRIGHT and LICENSE files for more details.
<% if User.current.allowed_in_project?(:browse_repository, @project) %>
<h3><%= t(:label_attachment_plural) %></h3>
<ul id="changes-legend">
<li class="change change-A icon icon-add" title=<%= t(:label_added) %>><%= t(:label_added) %></li>
<li class="change change-M icon icon-arrow-left-right" title=<%= t(:label_modified) %>><%= t(:label_modified) %></li>
<li class="change change-C icon icon-copy" title=<%= t(:label_copied) %>><%= t(:label_copied) %></li>
<li class="change change-R icon icon-rename" title=<%= t(:label_renamed) %>><%= t(:label_renamed) %></li>
<li class="change change-D icon icon-delete" title=<%= t(:label_deleted) %>><%= t(:label_deleted) %></li>
<li class="change change-A icon icon-add" aria-label="<%= t(:label_added) %>"><%= t(:label_added) %></li>
<li class="change change-M icon icon-arrow-left-right" aria-label="<%= t(:label_modified) %>"><%= t(:label_modified) %></li>
<li class="change change-C icon icon-copy" aria-label="<%= t(:label_copied) %>"><%= t(:label_copied) %></li>
<li class="change change-R icon icon-rename" aria-label="<%= t(:label_renamed) %>"><%= t(:label_renamed) %></li>
<li class="change change-D icon icon-delete" aria-label="<%= t(:label_deleted) %>"><%= t(:label_deleted) %></li>
</ul>
<p><%= link_to(t(:label_view_diff), action: "diff", project_id: @project, repo_path: nil, rev: @changeset.identifier) if @changeset.file_changes.any? %></p>
<div class="changeset-changes">
@@ -1,5 +1,7 @@
<div id="unsupported-browser-warning" class="warning-bar--item">
<span title="<%= t("unsupported_browser.close_warning") %>" class="icon3 icon-warning warning-bar--disable-on-hover"></span>
<button type="button"
aria-label="<%= t("unsupported_browser.close_warning") %>"
class="icon3 icon-warning warning-bar--disable-on-hover button--link"></button>
<p>
<strong><%= t("unsupported_browser.title") %></strong>
@@ -27,8 +29,8 @@
}
// Click handler to hide
var span = message.querySelector('.warning-bar--disable-on-hover');
span.onclick = function() {
var closeButton = message.querySelector('.warning-bar--disable-on-hover');
closeButton.onclick = function() {
message.style.display = 'none';
try {
window.localStorage.setItem('unsupported-browser-warning-ignore', '1');
@@ -66,7 +66,7 @@ a.icon, a.icon-context
a.icon:hover, a.icon-context:hover
text-decoration: none
#content table th a.no-decoration-on-hover:hover, a.no-decoration-on-hover:hover
#content table th a.no-decoration-on-hover:hover, a.no-decoration-on-hover:hover, button.no-decoration-on-hover:hover
text-decoration: none
.skip-navigation-link
@@ -1,5 +1,5 @@
<fieldset class="form--fieldset">
<legend class="form--fieldset-legend" title="<%= t "avatars.label_gravatar" %>"><%= t "avatars.label_gravatar" %></legend>
<legend class="form--fieldset-legend"><%= t "avatars.label_gravatar" %></legend>
<p>
<%= t "avatars.text_your_current_gravatar" %>
</p>
@@ -1,5 +1,5 @@
<fieldset class="form--fieldset">
<legend class="form--fieldset-legend" title="<%= t "avatars.label_local_avatar" %>"><%= t "avatars.label_local_avatar" %></legend>
<legend class="form--fieldset-legend"><%= t "avatars.label_local_avatar" %></legend>
<p>
<%= t "avatars.text_your_local_avatar" %>
<% if @manager.gravatar_enabled? %>
@@ -1,7 +1,7 @@
<% manager = ::OpenProject::Avatars::AvatarManager %>
<fieldset class="form--fieldset">
<legend class="form--fieldset-legend" title="<%= t "avatars.label_gravatar" %>"><%= t "avatars.label_gravatar" %></legend>
<legend class="form--fieldset-legend"><%= t "avatars.label_gravatar" %></legend>
<div class="form--field">
<%= styled_label_tag "settings-enable-gravatars", t("avatars.settings.enable_gravatars") %>
@@ -13,7 +13,7 @@
</fieldset>
<fieldset class="form--fieldset">
<legend class="form--fieldset-legend" title="<%= t "avatars.label_local_avatar" %>"><%= t "avatars.label_local_avatar" %></legend>
<legend class="form--fieldset-legend"><%= t "avatars.label_local_avatar" %></legend>
<div class="form--field">
<%= styled_label_tag "settings-enable-local-avatars", t("avatars.settings.enable_local_avatars") %>
<%= hidden_field_tag "settings[enable_local_avatars]", 0 %>
@@ -1,11 +1,8 @@
<div class="op-toast -info">
<a href="#" title="close" class="op-toast--close icon-context icon-close"></a>
<div class="op-toast--content">
<p><%= t("ifc_models.processing_notice.processing_default") %></p>
<ul>
<% unconverted.each do |model| %>
<li><%= model.title %></li>
<% end %>
</ul>
</div>
</div>
<%= render(Primer::Alpha::Banner.new(scheme: :default, icon: :info, dismiss_scheme: :hide)) do %>
<p><%= t("ifc_models.processing_notice.processing_default") %></p>
<ul>
<% unconverted.each do |model| %>
<li><%= model.title %></li>
<% end %>
</ul>
<% end %>
@@ -98,9 +98,7 @@ RSpec.describe "show default model", :js, with_config: { edition: "bim" } do
end
it "renders a notification" do
show_default_page
.expect_toast(type: :info,
message: I18n.t(:"ifc_models.processing_notice.processing_default"))
expect(page).to have_css(".Banner", text: I18n.t(:"ifc_models.processing_notice.processing_default"))
end
end
end
@@ -93,11 +93,14 @@ See COPYRIGHT and LICENSE files for more details.
<% end %>
<% cost_value = labor_budget_item.amount || labor_budget_item.calculated_costs(@budget.fixed_date, @budget.project_id) %>
<a id="<%= "#{id_prefix}_costs" %>" class="costs--edit-planned-costs-btn icon-context icon-edit" title="<%= t(:help_click_to_edit) %>">
<button type="button"
id="<%= "#{id_prefix}_costs" %>"
class="costs--edit-planned-costs-btn icon-context icon-edit button--link no-decoration-on-hover"
aria-label="<%= t(:help_click_to_edit) %>">
<% if labor_budget_item.costs_visible_by?(User.current) %>
<%= number_to_currency(cost_value) %>
<% end %>
</a>
</button>
<%= render partial: "/budgets/items/budget_override_cost_form",
locals: {
field_name: "#{name_prefix}[amount]",
@@ -95,9 +95,12 @@ See COPYRIGHT and LICENSE files for more details.
<% end %>
<% cost_value = material_budget_item.amount || material_budget_item.calculated_costs(@budget.fixed_date) %>
<a id="<%= id_prefix %>_costs" class="costs--edit-planned-costs-btn icon-context icon-edit" role="button" title="<%= t(:help_click_to_edit) %>">
<button type="button"
id="<%= id_prefix %>_costs"
class="costs--edit-planned-costs-btn icon-context icon-edit button--link no-decoration-on-hover"
aria-label="<%= t(:help_click_to_edit) %>">
<%= number_to_currency(cost_value) %>
</a>
</button>
<%= render partial: "/budgets/items/budget_override_cost_form",
locals: {
field_name: "#{name_prefix}[amount]",
@@ -76,11 +76,18 @@ See COPYRIGHT and LICENSE files for more details.
</span>
</td>
<td class="buttons">
<a href="#"
class="delete-row-button no-decoration-on-hover"
data-action="subform#deleteRow">
<%= op_icon("icon-context icon-delete", title: t(:button_delete)) %>
</a>
<%=
render(
Primer::Beta::IconButton.new(
icon: :trash,
scheme: :invisible,
type: :button,
classes: "delete-row-button",
aria: { label: t(:button_delete) },
data: { action: "subform#deleteRow" }
)
)
%>
</td>
<% end %>
<% end %>
@@ -122,13 +122,19 @@ See COPYRIGHT and LICENSE files for more details.
</div>
<div class="wp-inline-create-button">
<label class="sr-only" for="add_rate_date"> <%= t(:description_date_for_new_rate) %></label>
<a id="add_rate_date"
href="#"
class="add-row-button wp-inline-create--add-link"
title="<%= t(:button_add_rate) %>"
data-action="subform#addRow">
<%= op_icon("icon icon-add") %>
</a>
<%=
render(
Primer::Beta::IconButton.new(
icon: :plus,
id: "add_rate_date",
scheme: :invisible,
type: :button,
classes: "add-row-button wp-inline-create--add-link",
aria: { label: t(:button_add_rate) },
data: { action: "subform#addRow" }
)
)
%>
</div>
<%= styled_button_tag t(:button_save), class: "-with-icon icon-checkmark" %>
<% end %>
@@ -130,13 +130,12 @@ See COPYRIGHT and LICENSE files for more details.
<label for="cost_entry_costs_edit" class="form--label"><%= CostEntry.human_attribute_name(:costs) %></label>
<% if User.current.allowed_in_project?(:view_cost_rates, @cost_entry.project) %>
<span class="form--field-container">
<a href="#"
id="cost_entry_costs"
class="costs--edit-planned-costs-btn icon-context icon-edit"
role="button"
title="<%= t(:help_click_to_edit) %>">
<%= number_to_currency(@cost_entry.real_costs) %>
</a>
<button type="button"
id="cost_entry_costs"
class="costs--edit-planned-costs-btn icon-context icon-edit button--link no-decoration-on-hover"
aria-label="<%= t(:help_click_to_edit) %>">
<%= number_to_currency(@cost_entry.real_costs) %>
</button>
<%= render partial: "/budgets/items/budget_override_cost_form",
locals: {
field_name: "cost_entry[overridden_costs]",
@@ -67,9 +67,18 @@ See COPYRIGHT and LICENSE files for more details.
</span>
</td>
<td class="buttons">
<a href="#" class="delete-row-button no-decoration-on-hover" data-action="subform#deleteRow">
<%= op_icon("icon-context icon-delete", title: t(:button_delete)) %>
</a>
<%=
render(
Primer::Beta::IconButton.new(
icon: :trash,
scheme: :invisible,
type: :button,
classes: "delete-row-button",
aria: { label: t(:button_delete) },
data: { action: "subform#deleteRow" }
)
)
%>
</td>
<% end %>
<% end %>
@@ -105,10 +105,19 @@ See COPYRIGHT and LICENSE files for more details.
</div>
</div>
<div class="wp-inline-create-button">
<a href="#" class="add-row-button wp-inline-create--add-link" data-action="subform#addRow">
<%= op_icon("icon icon-add") %>
<%= t(:button_add_rate) %>
</a>
<%=
render(
Primer::Beta::Button.new(
scheme: :link,
type: :button,
classes: "add-row-button wp-inline-create--add-link",
data: { action: "subform#addRow" }
)
) do |button|
button.with_leading_visual_icon(icon: :plus)
t(:button_add_rate)
end
%>
</div>
<div class="generic-table--action-buttons">
<%= styled_button_tag t(:button_save), class: "-with-icon icon-checkmark" %>
@@ -1,3 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -64,7 +66,7 @@ RSpec.describe "hourly rates on user edit", :js do
before do
click_link "Update" # go to update view for rates
SeleniumHubWaiter.wait
find(".icon-delete").click # delete last existing rate
find(".delete-row-button").click # delete last existing rate
click_on "Save" # save change
end
@@ -88,13 +90,14 @@ RSpec.describe "hourly rates on user edit", :js do
# Expect the german locale output
expect(page).to have_field("user[existing_rate_attributes][#{rate.id}][rate]", with: "1,00")
click_link "Satz hinzufügen"
click_button "Satz hinzufügen"
fill_in "user_new_rate_attributes_1_valid_from", with: (Time.zone.today + 1.day).iso8601
find("input#user_new_rate_attributes_1_valid_from").send_keys :escape
fill_in "user_new_rate_attributes_1_rate", with: "5,12"
click_button "Speichern"
expect_flash(type: :notice)
view_rates
@@ -67,21 +67,21 @@ See COPYRIGHT and LICENSE files for more details.
<p>OpenProject allows hyperlinking between issues, changesets and wiki pages from anywhere wiki formatting is used.</p>
<ul>
<li><strong>#124</strong> displays a link to an issue: <del><a href="#" class="issue" title="bulk edit doesn't change the category or version properties (Closed)">#124</a></del> (link is striked-through if the issue is closed)</li>
<li><strong>##124</strong> displays a link to an issue with context information: <a href="/issues/12" class="issue status-1 priority-2 overdue created-by-me" title="Issue subject (New)">#12 New: Issue subject</a> 2012-05-14 - 2012-05-23 (User Name - assigned to)</li>
<li><strong>#124</strong> displays a link to an issue: <del><a href="/issues/124" class="issue">#124</a></del> (link is striked-through if the issue is closed)</li>
<li><strong>##124</strong> displays a link to an issue with context information: <a href="/issues/12" class="issue status-1 priority-2 overdue created-by-me">#12 New: Issue subject</a> 2012-05-14 - 2012-05-23 (User Name - assigned to)</li>
<li><strong>###124</strong> displays a link to an issue with context information and an excerpt (first 3 lines) of the description</li>
<li><strong>r758</strong> displays a link to a changeset: <a href="#" class="changeset" title="Search engine now only searches objects the user is allowed to view.">r758</a></li>
<li><strong>commit:c6f4d0fd</strong> displays a link to a changeset with a non-numeric hash: <a href="#" class="changeset">c6f4d0fd</a></li>
<li><strong>sandbox:r758</strong> displays a link to a changeset of another project: <a href="#" class="changeset" title="Search engine now only searches objects the user is allowed to view.">sandbox:r758</a></li>
<li><strong>sandbox:c6f4d0fd</strong> displays a link to a changeset with a non-numeric hash: <a href="#" class="changeset">sandbox:c6f4d0fd</a></li>
<li><strong>r758</strong> displays a link to a changeset: <a href="/repositories/revision/758" class="changeset">r758</a></li>
<li><strong>commit:c6f4d0fd</strong> displays a link to a changeset with a non-numeric hash: <a href="/repositories/revision/c6f4d0fd" class="changeset">c6f4d0fd</a></li>
<li><strong>sandbox:r758</strong> displays a link to a changeset of another project: <a href="/projects/sandbox/repository/revision/758" class="changeset">sandbox:r758</a></li>
<li><strong>sandbox:c6f4d0fd</strong> displays a link to a changeset with a non-numeric hash: <a href="/projects/sandbox/repository/revision/c6f4d0fd" class="changeset">sandbox:c6f4d0fd</a></li>
</ul>
<p>Wiki links:</p>
<ul>
<li><strong>[[Guide]]</strong> displays a link to the page named 'Guide': <a href="#" class="wiki-page">Guide</a></li>
<li><strong>[[Guide#further-reading]]</strong> takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: <a href="#" class="wiki-page">Guide</a></li>
<li><strong>[[Guide|User manual]]</strong> displays a link to the same page but with a different text: <a href="#" class="wiki-page">User manual</a></li>
<li><strong>[[Guide]]</strong> displays a link to the page named 'Guide': <a href="/projects/demo/wiki/Guide" class="wiki-page">Guide</a></li>
<li><strong>[[Guide#further-reading]]</strong> takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: <a href="/projects/demo/wiki/Guide#further-reading" class="wiki-page">Guide</a></li>
<li><strong>[[Guide|User manual]]</strong> displays a link to the same page but with a different text: <a href="/projects/demo/wiki/Guide" class="wiki-page">User manual</a></li>
</ul>
<p>You can also link to pages of an other project wiki:</p>
@@ -91,7 +91,7 @@ See COPYRIGHT and LICENSE files for more details.
<li><strong>[[sandbox:]]</strong> displays a link to the Sandbox wiki main page</li>
</ul>
<p>Wiki links are displayed in red if the page doesn't exist yet, eg: <a href="#" class="wiki-page new">Nonexistent page</a>.</p>
<p>Wiki links are displayed in red if the page doesn't exist yet, eg: <a href="/projects/demo/wiki/Nonexistent_page" class="wiki-page new">Nonexistent page</a>.</p>
<p>Links to other resources:</p>
@@ -6,18 +6,19 @@
id: "submit_backup_code",
data: {
turbo: false
}) do %>
}
) do %>
<h2><%= t "two_factor_authentication.login.enter_backup_code_title" %></h2>
<p><%= I18n.t("two_factor_authentication.login.enter_backup_code_text") %></p>
<hr class="form--separator">
<div class="form--field -required -wide-label">
<%= styled_label_tag "backup_code", t("two_factor_authentication.backup_codes.singular") %>
<div class="form--field-container">
<%= styled_text_field_tag "backup_code", nil, required: true, autocomplete: "off", size: 20, maxlength: 20, tabindex: 1, autofocus: true %>
<%= styled_text_field_tag "backup_code", nil, required: true, autocomplete: "off", size: 20, maxlength: 20, autofocus: true %>
</div>
</div>
<div class="login-form--footer">
<input type="submit" name="login" value="<%= t(:button_submit) %>" class="button -primary button_no-margin" tabindex="2">
<input type="submit" name="login" value="<%= t(:button_submit) %>" class="button -primary button_no-margin">
</div>
<% end %>
</div>
@@ -1,5 +1,5 @@
<% resend_supported = @strategy.mobile_token? %>
<% has_other_devices = @active_devices.count > 1 %>
<% has_other_devices = @active_devices.many? %>
<% has_backup_codes = @authenticated_user.otp_backup_codes.exists? %>
<% html_title t(:field_otp) %>
<div id="login-form"
@@ -25,7 +25,7 @@
<div class="form--field -wide-label">
<%= styled_label_tag "otp", t(:field_otp) %>
<div class="form--field-container">
<%= styled_text_field_tag "otp", nil, autocomplete: "off", size: 6, maxlength: 6, tabindex: 1, autofocus: true %>
<%= styled_text_field_tag "otp", nil, autocomplete: "off", size: 6, maxlength: 6, autofocus: true %>
</div>
</div>
<% else %>
@@ -45,7 +45,7 @@
</div>
<% end %>
<div class="login-form--footer">
<input type="submit" name="login" value="<%= t(:button_login) %>" class="button -primary button_no-margin" tabindex="2">
<input type="submit" name="login" value="<%= t(:button_login) %>" class="button -primary button_no-margin">
<% if resend_supported || has_other_devices || has_backup_codes %>
<div class="login-options-container">
<div class="login-links">
@@ -53,7 +53,6 @@
t(:text_otp_not_receive),
"#",
id: "toggle_resend_form",
tabindex: 3,
data: { action: "two-factor-authentication#toggleResendOptions" },
class: "login-form--footer-link"
) %>
@@ -74,7 +73,8 @@
id: "resend_otp",
data: {
turbo: false
}) do %>
}
) do %>
<%= hidden_field_tag "use_device", @service.device.id %>
<hr>
<div class="resend-header"><%= t(:text_send_otp_again) %></div>
@@ -25,7 +25,8 @@
<% configuration_link = OpenProject::Static::Links.url_for :configuration_guide %>
<%= link_to t("two_factor_authentication.settings.text_configuration_guide"),
configuration_link,
target: "_blank" %>
target: "_blank",
rel: "noopener" %>
</p>
<%= render(AttributeGroups::AttributeGroupComponent.new) do |component|
if configuration["active_strategies"].empty?
@@ -1,13 +1,15 @@
<% html_title(t(:label_my_account), t("two_factor_authentication.devices.confirm_device")) -%>
<%= styled_form_tag(confirm_path,
method: :post,
id: "login-form",
class: "form -bordered",
autocomplete: "off",
data: {
turbo: false
}) do %>
<%= styled_form_tag(
confirm_path,
method: :post,
id: "login-form",
class: "form -bordered",
autocomplete: "off",
data: {
turbo: false
}
) do %>
<h2><%= t("two_factor_authentication.devices.confirm_device") %></h2>
<p><%= t("two_factor_authentication.devices.text_confirm_to_complete_html", identifier: @device.identifier) %></p>
<hr class="form--separator">
@@ -15,10 +17,10 @@
<div class="form--field -required -wide-label">
<%= styled_label_tag "otp", t(:field_otp) %>
<div class="form--field-container">
<%= styled_text_field_tag "otp", nil, required: true, autocomplete: "off", size: 6, maxlength: 6, tabindex: 1, autofocus: true %>
<%= styled_text_field_tag "otp", nil, required: true, autocomplete: "off", size: 6, maxlength: 6, autofocus: true %>
</div>
</div>
<div class="login-form--footer">
<input type="submit" name="login" value="<%= t "button_continue" %>" class="button -primary button_no-margin" tabindex="2">
<input type="submit" name="login" value="<%= t "button_continue" %>" class="button -primary button_no-margin">
</div>
<% end %>
@@ -1,5 +1,5 @@
<fieldset class="form--fieldset">
<legend class="form--fieldset-legend" title="<%= @device.name %>"><%= @device.name %></legend>
<legend class="form--fieldset-legend"><%= @device.name %></legend>
<p><%= t("two_factor_authentication.devices.sms.description") %></p>
@@ -22,7 +22,7 @@
<% available_channels = @device.class.available_channels_in_strategy %>
<% if available_channels.length > 1 %>
<fieldset class="form--fieldset">
<legend class="form--fieldset-legend" title="<%= t(:label_otp_channel) %>"><%= t(:label_otp_channel) %></legend>
<legend class="form--fieldset-legend"><%= t(:label_otp_channel) %></legend>
<% available_channels.each do |channel| %>
<div class="form--field">
@@ -1,5 +1,5 @@
<fieldset class="form--fieldset">
<legend class="form--fieldset-legend" title="<%= @device.name %>"><%= @device.name %></legend>
<legend class="form--fieldset-legend"><%= @device.name %></legend>
<p><%= t("two_factor_authentication.devices.totp.description") %></p>
@@ -1,5 +1,5 @@
<fieldset class="form--fieldset">
<legend class="form--fieldset-legend" title="<%= @device.name %>"><%= @device.name %></legend>
<legend class="form--fieldset-legend"><%= @device.name %></legend>
<p><%= t("two_factor_authentication.devices.webauthn.description") %></p>
<div class="form--field -required">
<%= f.text_field :identifier, required: true, container_class: "-middle" %>
@@ -2,13 +2,22 @@
data-augmented-model-wrapper
data-activation-selector=".log-response-<%= id %>-modal--activation-link"
data-modal-class-name="webhooks--response-body-modal">
<a class="log-response-<%= id %>-modal--activation-link" title="<%= title %>">
<%= helpers.op_icon("icon-info1") %>
<%= t(:button_show) %>
</a>
<%=
render(
Primer::Beta::Button.new(
scheme: :link,
classes: "log-response-#{id}-modal--activation-link",
aria: { label: response_body_title }
)
) do |link|
link.with_leading_visual_icon(icon: :info)
t(:button_show)
end
%>
<div class="modal-delivery-element">
<div class="spot-modal--header">
<h1 class="spot-subheader-big"> <%= title %> </h1>
<h1 class="spot-subheader-big"> <%= response_body_title %> </h1>
</div>
<div class="spot-divider"></div>
<div class="spot-modal--body spot-container">
@@ -22,7 +31,7 @@
<div class="spot-action-bar--right">
<button
class="button button_no-margin spot-action-bar--action spot-modal--cancel-button"
title="<%= t(:button_close) %>"
aria-label="<%= t(:button_close) %>"
dynamic-content-modal-close-button>
<%= t(:button_close) %>
</button>
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module ::Webhooks
module Outgoing
module Deliveries
@@ -6,8 +8,8 @@ module ::Webhooks
property :response_headers
property :response_body
def title
model.class.human_attribute_name("response_body")
def response_body_title
model.class.human_attribute_name(:response_body)
end
end
end
@@ -36,7 +36,7 @@
class="form--fieldset"
id="webhooks-selected-events"
data-controller="checkable">
<legend class="form--fieldset-legend" title="<%= t "webhooks.outgoing.form.events.title" %>">
<legend class="form--fieldset-legend">
<%= t "webhooks.outgoing.form.events.title" %>
</legend>
<div class="form--toolbar">
@@ -71,7 +71,7 @@
</fieldset>
<fieldset class="form--fieldset">
<legend class="form--fieldset-legend" title="<%= "webhooks.outgoing.form.project_ids.title" %>">
<legend class="form--fieldset-legend">
<%= t "webhooks.outgoing.form.project_ids.title" %>
</legend>
<p><%= t("webhooks.outgoing.form.project_ids.description") %></p>
@@ -1,3 +1,5 @@
# frozen_string_literal: true
require "spec_helper"
RSpec.describe "Manage webhooks through UI", :js, :selenium do
@@ -98,7 +100,7 @@ RSpec.describe "Manage webhooks through UI", :js, :selenium do
# Open modal
SeleniumHubWaiter.wait
find("td.response_body a", text: "Show").click
find("td.response_body").click_on "Show"
page.within(".spot-modal") do
expect(page).to have_css(".webhooks--response-headers strong", text: "test")
@@ -121,7 +123,7 @@ RSpec.describe "Manage webhooks through UI", :js, :selenium do
within(row_element) do
id = find("td.id").text.to_i
matching_log = [log, log2, log3].find { |l| l.id == id }
find("td.response_body a", text: "Show").click
find("td.response_body").click_on "Show"
end
page.within(".spot-modal") do
@@ -80,7 +80,7 @@ RSpec.shared_examples_for "list custom fields" do |type|
within all(".custom-option-row").last do
find(".custom-option-value input").set "Solaris"
click_link accessible_name: "Move to top"
click_on accessible_name: "Move to top"
end
click_on "Save"
@@ -103,7 +103,7 @@ RSpec.describe "work package list custom fields", :js do
check("custom_field_custom_options_attributes_0_default_value")
check("custom_field_custom_options_attributes_2_default_value")
within first(".custom-option-row") do
click_link accessible_name: "Move to bottom"
click_on accessible_name: "Move to bottom"
end
click_on "Save"
@@ -59,8 +59,7 @@ module Pages
def remove_action(name)
within "#custom-actions-form--active-actions" do
find(".form--field", text: name)
.find(".icon-close")
.click
.click_on accessible_name: "Close"
end
end