diff --git a/app/components/activities/item_component.html.erb b/app/components/activities/item_component.html.erb index 540337fc93d9..ba689f97991e 100644 --- a/app/components/activities/item_component.html.erb +++ b/app/components/activities/item_component.html.erb @@ -65,7 +65,7 @@ See COPYRIGHT and LICENSE files for more details.
  • <%= detail %>
  • <% end -%> <% if time_entry_url.present? -%> -
  • <%= link_to "Details", @event.event_url %>
  • +
  • <%= link_to I18n.t(:label_details), @event.event_url %>
  • <% end -%> <% elsif noop? -%> diff --git a/app/components/work_packages/activities_tab/index_component.rb b/app/components/work_packages/activities_tab/index_component.rb index acad2fe5006e..92f4cdad2b64 100644 --- a/app/components/work_packages/activities_tab/index_component.rb +++ b/app/components/work_packages/activities_tab/index_component.rb @@ -35,16 +35,17 @@ class IndexComponent < ApplicationComponent include OpPrimer::ComponentHelpers include OpTurbo::Streamable - def initialize(work_package:, filter: :all) + def initialize(work_package:, last_server_timestamp:, filter: :all) super @work_package = work_package @filter = filter + @last_server_timestamp = last_server_timestamp end private - attr_reader :work_package, :filter + attr_reader :work_package, :filter, :last_server_timestamp def wrapper_data_attributes { @@ -59,7 +60,8 @@ def wrapper_data_attributes "work-packages--activities-tab--index-user-id-value": User.current.id, "work-packages--activities-tab--index-work-package-id-value": work_package.id, "work-packages--activities-tab--index-polling-interval-in-ms-value": polling_interval, - "work-packages--activities-tab--index-notification-center-path-name-value": notifications_path + "work-packages--activities-tab--index-notification-center-path-name-value": notifications_path, + "work-packages--activities-tab--index-last-server-timestamp-value": last_server_timestamp } end diff --git a/app/components/work_packages/activities_tab/journals/index_component.rb b/app/components/work_packages/activities_tab/journals/index_component.rb index 582432c9440d..a5f5f9b84adf 100644 --- a/app/components/work_packages/activities_tab/journals/index_component.rb +++ b/app/components/work_packages/activities_tab/journals/index_component.rb @@ -64,7 +64,11 @@ def journal_sorting_desc? end def journals - work_package.journals.includes(:user, :notifications).reorder(version: journal_sorting) + work_package + .journals + .includes(:user, :notifications) + .reorder(version: journal_sorting) + .with_sequence_version end def journal_with_notes diff --git a/app/components/work_packages/activities_tab/journals/item_component.html.erb b/app/components/work_packages/activities_tab/journals/item_component.html.erb index 0e87fad9783d..784a730cb55c 100644 --- a/app/components/work_packages/activities_tab/journals/item_component.html.erb +++ b/app/components/work_packages/activities_tab/journals/item_component.html.erb @@ -4,7 +4,7 @@ if show_comment_container? journal_container.with_row do render(border_box_container( - id: "activity-anchor-#{journal.version}", + id: "activity-anchor-#{journal.sequence_version}", padding: :condensed, "aria-label": I18n.t("activities.work_packages.activity_tab.commented") )) do |border_box_component| @@ -52,10 +52,10 @@ data: { turbo: false, action: "click->work-packages--activities-tab--index#setAnchor:prevent", - "work-packages--activities-tab--index-id-param": journal.version + "work-packages--activities-tab--index-id-param": journal.sequence_version } )) do - "##{journal.version}" + "##{journal.sequence_version}" end end header_end_container.with_column(ml: 1, diff --git a/app/components/work_packages/activities_tab/journals/item_component.rb b/app/components/work_packages/activities_tab/journals/item_component.rb index 1d20546545bc..7439943c8680 100644 --- a/app/components/work_packages/activities_tab/journals/item_component.rb +++ b/app/components/work_packages/activities_tab/journals/item_component.rb @@ -73,7 +73,7 @@ def activity_url end def activity_anchor - "#activity-#{journal.version}" + "#activity-#{journal.sequence_version}" end def updated? diff --git a/app/components/work_packages/activities_tab/journals/item_component/details.rb b/app/components/work_packages/activities_tab/journals/item_component/details.rb index 79c8bf8830ef..7734ec774b95 100644 --- a/app/components/work_packages/activities_tab/journals/item_component/details.rb +++ b/app/components/work_packages/activities_tab/journals/item_component/details.rb @@ -58,7 +58,7 @@ def render_details_header(details_container) flex_layout: true, justify_content: :space_between, classes: "work-packages-activities-tab-journals-item-component-details--journal-details-header-container", - id: "activity-anchor-#{journal.version}" + id: "activity-anchor-#{journal.sequence_version}" ) do |header_container| render_header_start(header_container) render_header_end(header_container) @@ -192,9 +192,9 @@ def render_activity_link(container) underline: false, font_size: :small, data: { turbo: false, action: "click->work-packages--activities-tab--index#setAnchor:prevent", - "work-packages--activities-tab--index-id-param": journal.version } + "work-packages--activities-tab--index-id-param": journal.sequence_version } )) do - "##{journal.version}" + "##{journal.sequence_version}" end end end diff --git a/app/components/work_packages/activities_tab/journals/item_component/reactions.html.erb b/app/components/work_packages/activities_tab/journals/item_component/reactions.html.erb index f627e6e11532..f40c99678ddd 100644 --- a/app/components/work_packages/activities_tab/journals/item_component/reactions.html.erb +++ b/app/components/work_packages/activities_tab/journals/item_component/reactions.html.erb @@ -1,7 +1,7 @@ <%= component_wrapper do if grouped_emoji_reactions.present? - flex_layout(test_selector: "emoji-reactions") do |reactions_container| + flex_layout(test_selector: "emoji-reactions", flex_wrap: :wrap, classes: "op-emoji-reactions--gap") do |reactions_container| grouped_emoji_reactions.each do |reaction, data| reactions_container.with_column(mr: 2) do render(Primer::Beta::Button.new( @@ -12,10 +12,10 @@ test_selector: "reaction-#{reaction}", tag: :a, href: href(reaction:), - data: { - turbo_stream: true, + data: { + turbo_stream: true, turbo_method: :put, - "work-packages--activities-tab--index-target": "reactionButton", + "work-packages--activities-tab--index-target": "reactionButton" }, aria: { label: aria_label_text(reaction, data[:users]) }, disabled: current_user_cannot_react?, diff --git a/app/components/work_packages/activities_tab/journals/item_component/reactions.sass b/app/components/work_packages/activities_tab/journals/item_component/reactions.sass index e0583044c2ad..6947b7b5601d 100644 --- a/app/components/work_packages/activities_tab/journals/item_component/reactions.sass +++ b/app/components/work_packages/activities_tab/journals/item_component/reactions.sass @@ -1,3 +1,6 @@ +.op-emoji-reactions--gap + row-gap: var(--base-size-4, 4px) + .op-reactions-button:disabled cursor: default background-color: transparent diff --git a/app/components/work_packages/activities_tab/journals/item_component/show.html.erb b/app/components/work_packages/activities_tab/journals/item_component/show.html.erb index ab5104b03c1c..b14e4614fe15 100644 --- a/app/components/work_packages/activities_tab/journals/item_component/show.html.erb +++ b/app/components/work_packages/activities_tab/journals/item_component/show.html.erb @@ -3,7 +3,7 @@ if journal.notes.present? flex_layout do |journal_container| journal_container.with_row do - render(Primer::Box.new(mt: 1)) do + render(Primer::Box.new(mt: 1, classes: "op-uc-container")) do format_text(journal, :notes) end end diff --git a/app/controllers/work_packages/activities_tab_controller.rb b/app/controllers/work_packages/activities_tab_controller.rb index ab1b16aafb71..5d2d8e6c73ec 100644 --- a/app/controllers/work_packages/activities_tab_controller.rb +++ b/app/controllers/work_packages/activities_tab_controller.rb @@ -42,13 +42,16 @@ def index render( WorkPackages::ActivitiesTab::IndexComponent.new( work_package: @work_package, - filter: @filter + filter: @filter, + last_server_timestamp: get_current_server_timestamp ), layout: false ) end def update_streams + set_last_server_timestamp_to_headers + perform_update_streams_from_last_update_timestamp respond_with_turbo_streams @@ -115,6 +118,7 @@ def create call = create_journal_service_call if call.success? && call.result + set_last_server_timestamp_to_headers handle_successful_create_call(call) else handle_failed_create_call(call) # errors should be rendered in the form @@ -213,7 +217,9 @@ def find_project end def find_journal - @journal = Journal.find(params[:id]) + @journal = Journal + .with_sequence_version + .find(params[:id]) rescue ActiveRecord::RecordNotFound respond_with_error(I18n.t("label_not_found")) end @@ -284,7 +290,8 @@ def replace_whole_tab replace_via_turbo_stream( component: WorkPackages::ActivitiesTab::IndexComponent.new( work_package: @work_package, - filter: @filter + filter: @filter, + last_server_timestamp: get_current_server_timestamp ) ) end @@ -309,7 +316,9 @@ def create_journal_service_call end def generate_time_based_update_streams(last_update_timestamp) - journals = @work_package.journals + journals = @work_package + .journals + .with_sequence_version if @filter == :only_comments journals = journals.where.not(notes: "") @@ -427,4 +436,14 @@ def grouped_emoji_reactions_for_journal def allowed_to_edit?(journal) journal.editable_by?(User.current) end + + def get_current_server_timestamp + # single source of truth for the server timestamp format + Time.current.iso8601(3) + end + + def set_last_server_timestamp_to_headers + # Add server timestamp to response in order to let the client be in sync with the server + response.headers["X-Server-Timestamp"] = get_current_server_timestamp + end end diff --git a/app/models/journal.rb b/app/models/journal.rb index cbb216b1eaa4..04f66834916a 100644 --- a/app/models/journal.rb +++ b/app/models/journal.rb @@ -102,6 +102,10 @@ class Journal < ApplicationRecord has_many :notifications, dependent: :destroy + include ::Scopes::Scoped + + scopes :with_sequence_version + # Scopes to all journals excluding the initial journal - useful for change # logs like the history on issue#show scope :changing, -> { where(["version > 1"]) } diff --git a/app/models/journals/scopes/with_sequence_version.rb b/app/models/journals/scopes/with_sequence_version.rb new file mode 100644 index 000000000000..b806a377cea5 --- /dev/null +++ b/app/models/journals/scopes/with_sequence_version.rb @@ -0,0 +1,55 @@ +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2010-2024 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 Journals::Scopes + module WithSequenceVersion + extend ActiveSupport::Concern + + class_methods do + def with_sequence_version + joins( + <<~SQL.squish + JOIN LATERAL ( + SELECT + journals_rank.id, + ROW_NUMBER() OVER (ORDER BY version ASC) sequence_version + FROM + journals journals_rank + WHERE + journals_rank.journable_id = journals.journable_id + AND + journals_rank.journable_type = journals.journable_type + ) ranked + ON ranked.id = journals.id + SQL + ) + .select("*") + end + end + end +end diff --git a/app/models/queries/versions.rb b/app/models/queries/versions.rb index b6af7df75293..6e036b0bae4a 100644 --- a/app/models/queries/versions.rb +++ b/app/models/queries/versions.rb @@ -30,7 +30,6 @@ module Queries::Versions ::Queries::Register.register(VersionQuery) do filter Filters::SharingFilter - order Orders::NameOrder - order Orders::SemverNameOrder + order Orders::DefaultOrder end end diff --git a/app/models/queries/versions/orders/name_order.rb b/app/models/queries/versions/orders/default_order.rb similarity index 80% rename from app/models/queries/versions/orders/name_order.rb rename to app/models/queries/versions/orders/default_order.rb index 362b9340cfb9..b86f38ef6d52 100644 --- a/app/models/queries/versions/orders/name_order.rb +++ b/app/models/queries/versions/orders/default_order.rb @@ -26,22 +26,20 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class Queries::Versions::Orders::NameOrder < Queries::Orders::Base +class Queries::Versions::Orders::DefaultOrder < Queries::Orders::Base self.model = Version def self.key - :name + /\A(id|name|semver_name)\z/ end - private + def initialize(attribute) + if attribute == :semver_name + OpenProject::Deprecation.warn("Sorting by semver_name is deprecated, name should be used instead") - def order - ordered = Version.order(:name) - - if direction == :desc - ordered = ordered.reverse_order + super(:name) + else + super end - - ordered end end diff --git a/app/models/queries/work_packages/selects/property_select.rb b/app/models/queries/work_packages/selects/property_select.rb index 7963dfb81348..c73db63aaeff 100644 --- a/app/models/queries/work_packages/selects/property_select.rb +++ b/app/models/queries/work_packages/selects/property_select.rb @@ -53,7 +53,6 @@ def caption }, parent: { association: "ancestors_relations", - default_order: "asc", sortable: false }, status: { @@ -96,18 +95,14 @@ def caption version: { association: "version", sortable: "name", - default_order: "ASC", - null_handling: "NULLS LAST", groupable: "#{WorkPackage.table_name}.version_id" }, start_date: { - sortable: "#{WorkPackage.table_name}.start_date", - null_handling: "NULLS LAST" + sortable: "#{WorkPackage.table_name}.start_date" }, due_date: { highlightable: true, - sortable: "#{WorkPackage.table_name}.due_date", - null_handling: "NULLS LAST" + sortable: "#{WorkPackage.table_name}.due_date" }, estimated_hours: { sortable: "#{WorkPackage.table_name}.estimated_hours", diff --git a/config/locales/crowdin/js-af.yml b/config/locales/crowdin/js-af.yml index 7c4bb3fb826b..2e85ba0c0360 100644 --- a/config/locales/crowdin/js-af.yml +++ b/config/locales/crowdin/js-af.yml @@ -356,10 +356,10 @@ af: learn_about: "Learn more about the new features" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-ar.yml b/config/locales/crowdin/js-ar.yml index 376092e22dbe..2ed8f35668fb 100644 --- a/config/locales/crowdin/js-ar.yml +++ b/config/locales/crowdin/js-ar.yml @@ -356,10 +356,10 @@ ar: learn_about: "Learn more about the new features" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-az.yml b/config/locales/crowdin/js-az.yml index 71ceefddda5b..a1e627b6cf03 100644 --- a/config/locales/crowdin/js-az.yml +++ b/config/locales/crowdin/js-az.yml @@ -356,10 +356,10 @@ az: learn_about: "Learn more about the new features" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-be.yml b/config/locales/crowdin/js-be.yml index f893e98af9b5..34f8c231e8ff 100644 --- a/config/locales/crowdin/js-be.yml +++ b/config/locales/crowdin/js-be.yml @@ -356,10 +356,10 @@ be: learn_about: "Learn more about the new features" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-bg.yml b/config/locales/crowdin/js-bg.yml index cabf307b16ab..fd684a72a226 100644 --- a/config/locales/crowdin/js-bg.yml +++ b/config/locales/crowdin/js-bg.yml @@ -356,10 +356,10 @@ bg: learn_about: "Learn more about the new features" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-ca.yml b/config/locales/crowdin/js-ca.yml index 750b2ce09c3d..c150dbbe8397 100644 --- a/config/locales/crowdin/js-ca.yml +++ b/config/locales/crowdin/js-ca.yml @@ -356,10 +356,10 @@ ca: learn_about: "Més informació sobre les noves funcions" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "S'ha produït un error en carregar les dades." diff --git a/config/locales/crowdin/js-ckb-IR.yml b/config/locales/crowdin/js-ckb-IR.yml index 78e3034bb5ef..25322f5c6e10 100644 --- a/config/locales/crowdin/js-ckb-IR.yml +++ b/config/locales/crowdin/js-ckb-IR.yml @@ -356,10 +356,10 @@ ckb-IR: learn_about: "Learn more about the new features" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-cs.yml b/config/locales/crowdin/js-cs.yml index aa0f56e0ad7c..537e4567c8d1 100644 --- a/config/locales/crowdin/js-cs.yml +++ b/config/locales/crowdin/js-cs.yml @@ -355,10 +355,10 @@ cs: learn_about: "Další informace o nových funkcích" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Přihlásit kalendář k odběru" inital_setup_error_message: "Při načítání dat došlo k chybě." diff --git a/config/locales/crowdin/js-da.yml b/config/locales/crowdin/js-da.yml index 2529f6ba7fca..1f7260e04fb0 100644 --- a/config/locales/crowdin/js-da.yml +++ b/config/locales/crowdin/js-da.yml @@ -355,10 +355,10 @@ da: learn_about: "Learn more about the new features" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-de.yml b/config/locales/crowdin/js-de.yml index 3f2f090d4097..9ec6b046e1ca 100644 --- a/config/locales/crowdin/js-de.yml +++ b/config/locales/crowdin/js-de.yml @@ -355,10 +355,10 @@ de: learn_about: "Erfahren Sie mehr über die neuen Funktionen" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - Die Version bietet Ihnen verschiedene Funktionen und Verbesserungen, z.B.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Kalender abonnieren" inital_setup_error_message: "Beim Abrufen der Daten ist ein Fehler aufgetreten." diff --git a/config/locales/crowdin/js-el.yml b/config/locales/crowdin/js-el.yml index c1b3dacce095..b843a70eec32 100644 --- a/config/locales/crowdin/js-el.yml +++ b/config/locales/crowdin/js-el.yml @@ -355,10 +355,10 @@ el: learn_about: "Μάθετε περισσότερα για τις νέες λειτουργίες" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-eo.yml b/config/locales/crowdin/js-eo.yml index aa24119046f0..fab34948e671 100644 --- a/config/locales/crowdin/js-eo.yml +++ b/config/locales/crowdin/js-eo.yml @@ -356,10 +356,10 @@ eo: learn_about: "Lerni pli pri la novaj plibonigoj" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-es.yml b/config/locales/crowdin/js-es.yml index f89bf3321a5d..aba565d4b211 100644 --- a/config/locales/crowdin/js-es.yml +++ b/config/locales/crowdin/js-es.yml @@ -356,10 +356,10 @@ es: learn_about: "Más información sobre las nuevas funciones" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - La versión trae varias características y mejoras para usted, por ejemplo
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Suscribirse al calendario" inital_setup_error_message: "Se ha producido un error al obtener los datos." diff --git a/config/locales/crowdin/js-et.yml b/config/locales/crowdin/js-et.yml index 88eb01db5be2..eda78d83c57f 100644 --- a/config/locales/crowdin/js-et.yml +++ b/config/locales/crowdin/js-et.yml @@ -356,10 +356,10 @@ et: learn_about: "Learn more about the new features" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-eu.yml b/config/locales/crowdin/js-eu.yml index 421b56601834..e4277f4a90d4 100644 --- a/config/locales/crowdin/js-eu.yml +++ b/config/locales/crowdin/js-eu.yml @@ -356,10 +356,10 @@ eu: learn_about: "Learn more about the new features" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-fa.yml b/config/locales/crowdin/js-fa.yml index 03764bf2d9e0..1bd6049c6dd2 100644 --- a/config/locales/crowdin/js-fa.yml +++ b/config/locales/crowdin/js-fa.yml @@ -356,10 +356,10 @@ fa: learn_about: "Learn more about the new features" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-fi.yml b/config/locales/crowdin/js-fi.yml index f800472216b7..e7af4d41e6e1 100644 --- a/config/locales/crowdin/js-fi.yml +++ b/config/locales/crowdin/js-fi.yml @@ -356,10 +356,10 @@ fi: learn_about: "Lisätietoja uusista ominaisuuksista" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-fil.yml b/config/locales/crowdin/js-fil.yml index 462224b9f353..ec72e1f98195 100644 --- a/config/locales/crowdin/js-fil.yml +++ b/config/locales/crowdin/js-fil.yml @@ -356,10 +356,10 @@ fil: learn_about: "Learn more about the new features" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-fr.yml b/config/locales/crowdin/js-fr.yml index 4fc31f38d285..88919d5be011 100644 --- a/config/locales/crowdin/js-fr.yml +++ b/config/locales/crowdin/js-fr.yml @@ -356,10 +356,10 @@ fr: learn_about: "En savoir plus sur les nouvelles fonctionnalités" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - Cette version apporte diverses fonctionnalités et améliorations, p. ex.,
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "S'abonner au calendrier" inital_setup_error_message: "Une erreur est survenue lors de la récupération des données." diff --git a/config/locales/crowdin/js-he.yml b/config/locales/crowdin/js-he.yml index e7244059eea2..5a8d338ccb36 100644 --- a/config/locales/crowdin/js-he.yml +++ b/config/locales/crowdin/js-he.yml @@ -356,10 +356,10 @@ he: learn_about: "Learn more about the new features" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-hi.yml b/config/locales/crowdin/js-hi.yml index 2be5d39ef334..329cb1f25b1c 100644 --- a/config/locales/crowdin/js-hi.yml +++ b/config/locales/crowdin/js-hi.yml @@ -356,10 +356,10 @@ hi: learn_about: "Learn more about the new features" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-hr.yml b/config/locales/crowdin/js-hr.yml index 7a3ea94a224d..258e305c1892 100644 --- a/config/locales/crowdin/js-hr.yml +++ b/config/locales/crowdin/js-hr.yml @@ -356,10 +356,10 @@ hr: learn_about: "Saznaj više o novim značajkama" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-hu.yml b/config/locales/crowdin/js-hu.yml index 095eeeb064ea..3a3b31374ebf 100644 --- a/config/locales/crowdin/js-hu.yml +++ b/config/locales/crowdin/js-hu.yml @@ -356,10 +356,10 @@ hu: learn_about: "Tudjon meg többet az új funkciókról" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-id.yml b/config/locales/crowdin/js-id.yml index 36a7fc89aa38..1350cd5c7c89 100644 --- a/config/locales/crowdin/js-id.yml +++ b/config/locales/crowdin/js-id.yml @@ -356,10 +356,10 @@ id: learn_about: "Learn more about the new features" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-it.yml b/config/locales/crowdin/js-it.yml index 6f3b18991544..d8492653dec0 100644 --- a/config/locales/crowdin/js-it.yml +++ b/config/locales/crowdin/js-it.yml @@ -356,10 +356,10 @@ it: learn_about: "Scopri di più sulle nuove funzionalità" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - Questa versione offre diverse funzionalità e miglioramenti, come:
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Iscriviti al calendario" inital_setup_error_message: "Si è verificato un errore recuperando i dati." diff --git a/config/locales/crowdin/js-ja.yml b/config/locales/crowdin/js-ja.yml index 475ed83d243a..3ee15dedf82a 100644 --- a/config/locales/crowdin/js-ja.yml +++ b/config/locales/crowdin/js-ja.yml @@ -357,10 +357,10 @@ ja: learn_about: "新機能の詳細はこちら" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-ka.yml b/config/locales/crowdin/js-ka.yml index a121c5a2c5ee..de78ca5aa76c 100644 --- a/config/locales/crowdin/js-ka.yml +++ b/config/locales/crowdin/js-ka.yml @@ -356,10 +356,10 @@ ka: learn_about: "Learn more about the new features" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-kk.yml b/config/locales/crowdin/js-kk.yml index 8c7ca6f8f684..ef989f591977 100644 --- a/config/locales/crowdin/js-kk.yml +++ b/config/locales/crowdin/js-kk.yml @@ -356,10 +356,10 @@ kk: learn_about: "Learn more about the new features" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-ko.yml b/config/locales/crowdin/js-ko.yml index 9f4a0e22e9ad..8cf7ed3b9857 100644 --- a/config/locales/crowdin/js-ko.yml +++ b/config/locales/crowdin/js-ko.yml @@ -356,10 +356,10 @@ ko: learn_about: "새로운 기능에 대해 자세히 알아보기" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - 이 릴리스는 다음을 비롯한 다양한 기능과 개선 사항을 제공합니다.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "캘린더 구독" inital_setup_error_message: "데이터를 가져오는 중에 오류가 발생했습니다." diff --git a/config/locales/crowdin/js-lt.yml b/config/locales/crowdin/js-lt.yml index 08073a388ada..8dff71eb4d77 100644 --- a/config/locales/crowdin/js-lt.yml +++ b/config/locales/crowdin/js-lt.yml @@ -356,10 +356,10 @@ lt: learn_about: "Sužinokite daugiau apie naujas galimybes" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Prenumeruoti kalendorių" inital_setup_error_message: "Gaunant duomenis įvyko klaida." diff --git a/config/locales/crowdin/js-lv.yml b/config/locales/crowdin/js-lv.yml index a2581f482aa1..123b62fb6bb4 100644 --- a/config/locales/crowdin/js-lv.yml +++ b/config/locales/crowdin/js-lv.yml @@ -356,10 +356,10 @@ lv: learn_about: "Learn more about the new features" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-mn.yml b/config/locales/crowdin/js-mn.yml index 4b1d0ca05ce6..07730680615c 100644 --- a/config/locales/crowdin/js-mn.yml +++ b/config/locales/crowdin/js-mn.yml @@ -356,10 +356,10 @@ mn: learn_about: "Learn more about the new features" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-ms.yml b/config/locales/crowdin/js-ms.yml index 24a13665ba03..e4691fd83b1b 100644 --- a/config/locales/crowdin/js-ms.yml +++ b/config/locales/crowdin/js-ms.yml @@ -356,10 +356,10 @@ ms: learn_about: "Ketahui lebih lanjut mengenai fitur-fitur baharu." #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Langgan kalendar" inital_setup_error_message: "Ralat berlaku ketika sedang mengambil data." diff --git a/config/locales/crowdin/js-ne.yml b/config/locales/crowdin/js-ne.yml index 737a14ec3140..a87b44f29ec6 100644 --- a/config/locales/crowdin/js-ne.yml +++ b/config/locales/crowdin/js-ne.yml @@ -356,10 +356,10 @@ ne: learn_about: "Learn more about the new features" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-nl.yml b/config/locales/crowdin/js-nl.yml index 53b32c814ca6..7a2030cfe250 100644 --- a/config/locales/crowdin/js-nl.yml +++ b/config/locales/crowdin/js-nl.yml @@ -356,10 +356,10 @@ nl: learn_about: "Meer informatie over de nieuwe functies" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Abonneren op agenda" inital_setup_error_message: "Er is een fout opgetreden tijdens het ophalen van gegevens." diff --git a/config/locales/crowdin/js-no.yml b/config/locales/crowdin/js-no.yml index 617d3e4add22..4523fb085cc6 100644 --- a/config/locales/crowdin/js-no.yml +++ b/config/locales/crowdin/js-no.yml @@ -356,10 +356,10 @@ learn_about: "Lær mer om de nye funksjonene" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - Versjonen gir ulike funksjoner og forbedringer for deg, for eksempel
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Abonner på kalender" inital_setup_error_message: "En feil oppstod under henting av data." diff --git a/config/locales/crowdin/js-pl.yml b/config/locales/crowdin/js-pl.yml index 0ec1e7d6e6d0..c1bd7e9dd4e1 100644 --- a/config/locales/crowdin/js-pl.yml +++ b/config/locales/crowdin/js-pl.yml @@ -356,10 +356,10 @@ pl: learn_about: "Dowiedz się więcej o nowych funkcjach" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - Ta wersja wprowadza różne funkcje i ulepszenia m.in.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subskrybuj kalendarz" inital_setup_error_message: "Podczas pobierania danych wystąpił błąd." diff --git a/config/locales/crowdin/js-pt-BR.yml b/config/locales/crowdin/js-pt-BR.yml index 8f8703a48210..cdd44106c10e 100644 --- a/config/locales/crowdin/js-pt-BR.yml +++ b/config/locales/crowdin/js-pt-BR.yml @@ -355,10 +355,10 @@ pt-BR: learn_about: "Saiba mais sobre os novos recursos" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - A nova versão traz diversas funcionalidades e melhorias para você, como:
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Assinar calendário" inital_setup_error_message: "Ocorreu um erro ao buscar dados." diff --git a/config/locales/crowdin/js-pt-PT.yml b/config/locales/crowdin/js-pt-PT.yml index 50b0338c71cb..8548d406eacb 100644 --- a/config/locales/crowdin/js-pt-PT.yml +++ b/config/locales/crowdin/js-pt-PT.yml @@ -356,10 +356,10 @@ pt-PT: learn_about: "Saiba mais sobre os novos recursos" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - A versão traz várias funcionalidades e melhorias para si, por exemplo,
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscrever o calendário" inital_setup_error_message: "Ocorreu um erro ao recuperar os dados." diff --git a/config/locales/crowdin/js-ro.yml b/config/locales/crowdin/js-ro.yml index 8c72d15fe04d..3ded96a5ff5d 100644 --- a/config/locales/crowdin/js-ro.yml +++ b/config/locales/crowdin/js-ro.yml @@ -141,7 +141,7 @@ ro: revisions: "Show local modifications" no_revisions: "No local modifications found" preview: "Activaţi/Dezactivați previzualizarea" - source_code: "Fă clic pentru a activa/dezactiva modul ierarhic" + source_code: "Clic pentru a activa/dezactiva modul ierarhic" error_saving_failed: "Salvarea documentului a eșuat cu următoarea eroare: %{error}" ckeditor_error: "A apărut o eroare în cadrul CKEditor" mode: @@ -171,14 +171,14 @@ ro: macros: "Macro" chose_macro: "Alegeți macro" toc: "Cuprins" - toolbar_help: "Fă clic pentru a selecta widgetul și pentru a afișa bara de instrumente. Fă dublu clic pentru a edita widgetul" + toolbar_help: "Clic pentru a selecta piesa și pentru a afișa bara de instrumente. Dublu clic pentru a edita piesa" wiki_page_include: button: "Includeți conținul altei pagini wiki" text: "[Placeholder] Pagină wiki inclusă" page: "Pagină wiki" not_set: "(Pagina nu a fost încă stabilită)" hint: | - Includeți conținutul altei pagini wiki specificând titul sau adresa(slug). Puteți include pagina wiki din cadrul altui proiect separându-le prin simbolul ”:” ca și în exemplul următor. + Include conținutul altei pagini wiki specificând titlul sau adresa. Poți include pagina wiki din cadrul altui proiect separându-le prin simbolul ”:” ca și în exemplul următor. work_package_button: button: "Creați pachet de lucru" type: "Tip pachet de lucru" @@ -355,10 +355,10 @@ ro: learn_about: "Aflați mai multe despre noile caracteristici" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." @@ -381,7 +381,7 @@ ro: label_add_row_before: "Adauga un rand inainte" label_add_selected_columns: "Adăugați coloanele selectate" label_added_by: "adăugat de" - label_added_time_by: 'Added by %{author} at %{age}' + label_added_time_by: 'Adăugat de %{author} acum %{age}' label_ago: "zile în urmă" label_all: "toate" label_all_uppercase: "Toate" @@ -434,7 +434,7 @@ ro: label_global_roles: "Global roles" label_greater_or_equal: ">=" label_group: "Grup" - label_group_by: "Grupare după" + label_group_by: "Grupează după" label_group_plural: "Grupuri" label_hide_attributes: "Arată mai puţin" label_hide_column: "Ascunde coloana" @@ -533,8 +533,8 @@ ro: label_watcher_deleted_successfully: "Observator eliminat cu succes!" label_work_package_details_you_are_here: "Sunteți pe fila %{tab} pentru %{type} %{subject}." label_work_package_context_menu: "Work package context menu" - label_unwatch: "Stop monitorizare" - label_unwatch_work_package: "Opriți urmărirea pachetului de lucru" + label_unwatch: "Nu mai urmări" + label_unwatch_work_package: "Oprește urmărirea pachetului de lucru" label_uploaded_by: "Încărcat de" label_default_queries: "Implicit" label_starred_queries: "Favorit" @@ -641,7 +641,7 @@ ro: with_current_filter: "Nu există notificări în această vizualizare în acest moment" mark_all_read: "Marchează toate ca și citite" mark_as_read: "Marchează ca Citit" - text_update_date_by: "%{date} by" + text_update_date_by: "%{date} de" total_count_warning: "Se afișează %{newest_count} cele mai recente notificări. %{more_count} în plus nu sunt afișate." empty_state: no_notification: "Se pare că v-ați pus la punct." @@ -765,7 +765,7 @@ ro: error_no_table_configured: "Vă rugăm să configurați un tabel pentru %{group}." reset_title: "Resetare configurare formular" confirm_reset: > - Antenție: Sunteți sigur că doriți să resetați congurația formularului? Această acțiune va reseta toate atributele înapoi la grupul implicit și va dezactiva TOATE câmpurile personalizate. + Atenție: Ești sigur că vrei să resetați conjurația formularului? Această acțiune va reseta toate atributele înapoi la grupul implicit și va dezactiva TOATE câmpurile personalizate. upgrade_to_ee: "Actualizare la ediția Enterprise on-premise" upgrade_to_ee_text: "Wow! Dacă aveți nevoie de acest add-on sunteți un super profesionist! Vrei să ne sprijini pe noi, dezvoltatorii OpenSource, devenind client al ediției Enterprise?" more_information: "Mai multe informatii" @@ -799,7 +799,7 @@ ro: children: "Fii" relates: "În relație cu" duplicates: "Dublează" - duplicated: "Dublate de" + duplicated: "Dublat de" blocks: "Blochează" blocked: "Blocat de" precedes: "Precede" @@ -814,7 +814,7 @@ ro: hierarchy_headline: "Arată ierarhia" children_headline: "Fii" relation_buttons: - set_parent: "Setați părintele" + set_parent: "Setează părinte" change_parent: "Modificare părinte" remove_parent: "Elimina părinte" hierarchy_indent: "Ierarhia de indentare" @@ -831,7 +831,7 @@ ro: add_existing_relation: "Adăugarea unei relații existente" update_description: "Setează sau actualizează descrierea acestei relații" toggle_description: "Comută descrierea relației" - update_relation: "Fă click pentru a schimba tipul de legătură" + update_relation: "Clic pentru a schimba tipul de legătură" add_follower: "Adaugă urmăritor" show_relations: "Arată relațiile" add_predecessor: "Adăugați predecesorul" @@ -952,7 +952,7 @@ ro: hierarchy: show: "Afișați modul ierarhic" hide: "Ascundeți modul ierarhic" - toggle_button: "Fă clic pentru a activa/dezactiva modul ierarhic." + toggle_button: "Clic pentru a activa/dezactiva modul ierarhic." leaf: "Frunza pachetului de lucru la nivelul %{level}." children_collapsed: "Nivelul ierarhic %{level}, prăbușit. Faceți clic pentru a afișa copiii filtrați" children_expanded: "Nivelul ierarhic %{level}, extins. Faceți clic pentru a restrânge copiii filtrați" @@ -1300,29 +1300,29 @@ ro: does_not_match_search: "Proiectul nu corespunde criteriilor de căutare." no_results: "Niciun proiect nu corespunde criteriilor de căutare." baseline: - toggle_title: "Baseline" - clear: "Inițializare" + toggle_title: "Referință" + clear: "Golire" apply: "Aplică" - header_description: "Highlight changes made to this list since any point in the past." + header_description: "Evidențiază modificările făcute la această listă de la orice punct din trecut." enterprise_header_description: "Highlight changes made to this list since any point in the past with Enterprise edition." - show_changes_since: "Show changes since" - baseline_comparison: "Baseline comparison" - help_description: "Reference time zone for the baseline." - time_description: "In your local time: %{datetime}" - time: "Ora" + show_changes_since: "Arată modificările din" + baseline_comparison: "Comparație referință" + help_description: "Zona orară de referință" + time_description: "În timpul tău local: %{datetime}" + time: "Timp" from: "Din" to: "La" drop_down: none: "-" yesterday: "ieri" - last_working_day: "last working day" + last_working_day: "ultima zi lucrătoare" last_week: "săptămâna trecută" last_month: "luna trecută" - a_specific_date: "a specific date" - between_two_specific_dates: "between two specific dates" + a_specific_date: "o anumită dată" + between_two_specific_dates: "între două dăți specifice" legends: - changes_since: "Changes since" - changes_between: "Changes between" + changes_since: "Modificări de la" + changes_between: "Modificări între" now_meets_filter_criteria: "Now meets filter criteria" no_longer_meets_filter_criteria: "No longer meets filter criteria" maintained_with_changes: "Maintained with changes" @@ -1330,7 +1330,7 @@ ro: icon_tooltip: added: "Added to view within the comparison time period" removed: "Removed from view within the comparison time period" - changed: "Maintained with modifications" + changed: "Menținut cu modificări" forms: submit_success_message: "Formularul a fost trimis cu succes" load_error_message: "A apărut o eroare la încărcarea formularului" diff --git a/config/locales/crowdin/js-ru.yml b/config/locales/crowdin/js-ru.yml index 572408d63cea..b9cfe6bb1823 100644 --- a/config/locales/crowdin/js-ru.yml +++ b/config/locales/crowdin/js-ru.yml @@ -355,10 +355,10 @@ ru: learn_about: "Подробнее о новых функциях" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - В этом выпуске представлены различные функции и улучшения, например,
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Подписаться на календарь" inital_setup_error_message: "Произошла ошибка при получении данных." diff --git a/config/locales/crowdin/js-rw.yml b/config/locales/crowdin/js-rw.yml index 66df4abdd70d..7bba3fd311a5 100644 --- a/config/locales/crowdin/js-rw.yml +++ b/config/locales/crowdin/js-rw.yml @@ -356,10 +356,10 @@ rw: learn_about: "Learn more about the new features" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-si.yml b/config/locales/crowdin/js-si.yml index 82ab626006bc..e2c0f71de022 100644 --- a/config/locales/crowdin/js-si.yml +++ b/config/locales/crowdin/js-si.yml @@ -356,10 +356,10 @@ si: learn_about: "නව විශේෂාංග ගැන වැඩි විස්තර දැනගන්න" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-sk.yml b/config/locales/crowdin/js-sk.yml index 014a1ea572d9..3618d2d0405d 100644 --- a/config/locales/crowdin/js-sk.yml +++ b/config/locales/crowdin/js-sk.yml @@ -356,10 +356,10 @@ sk: learn_about: "Learn more about the new features" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-sl.yml b/config/locales/crowdin/js-sl.yml index 2480a0168b39..a5169c3cf2a4 100644 --- a/config/locales/crowdin/js-sl.yml +++ b/config/locales/crowdin/js-sl.yml @@ -355,10 +355,10 @@ sl: learn_about: "Preberite več o novostih" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-sr.yml b/config/locales/crowdin/js-sr.yml index de1ff1656e1b..d769518db554 100644 --- a/config/locales/crowdin/js-sr.yml +++ b/config/locales/crowdin/js-sr.yml @@ -356,10 +356,10 @@ sr: learn_about: "Learn more about the new features" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-sv.yml b/config/locales/crowdin/js-sv.yml index b11413f84da8..5cbb7a54465f 100644 --- a/config/locales/crowdin/js-sv.yml +++ b/config/locales/crowdin/js-sv.yml @@ -355,10 +355,10 @@ sv: learn_about: "Learn more about the new features" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-th.yml b/config/locales/crowdin/js-th.yml index bb04e7f86904..feec1d35ecfd 100644 --- a/config/locales/crowdin/js-th.yml +++ b/config/locales/crowdin/js-th.yml @@ -356,10 +356,10 @@ th: learn_about: "Learn more about the new features" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-tr.yml b/config/locales/crowdin/js-tr.yml index c80ccaccf257..e30c0e9362fc 100644 --- a/config/locales/crowdin/js-tr.yml +++ b/config/locales/crowdin/js-tr.yml @@ -355,10 +355,10 @@ tr: learn_about: "Yeni özellikler hakkında daha fazla bilgi edinin" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-uk.yml b/config/locales/crowdin/js-uk.yml index a3b6169d357d..1fef4fd2e4ed 100644 --- a/config/locales/crowdin/js-uk.yml +++ b/config/locales/crowdin/js-uk.yml @@ -356,10 +356,10 @@ uk: learn_about: "Дізнатися більше про нові функції" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - Цей випуск включає багато нових функцій і покращень.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Підписатися на календар" inital_setup_error_message: "Під час отримання даних сталася помилка." diff --git a/config/locales/crowdin/js-uz.yml b/config/locales/crowdin/js-uz.yml index 97bcfe519fa0..1a005a46e472 100644 --- a/config/locales/crowdin/js-uz.yml +++ b/config/locales/crowdin/js-uz.yml @@ -356,10 +356,10 @@ uz: learn_about: "Learn more about the new features" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Subscribe to calendar" inital_setup_error_message: "An error occured while fetching data." diff --git a/config/locales/crowdin/js-vi.yml b/config/locales/crowdin/js-vi.yml index c6bbd1cec8da..389b0b2b6225 100644 --- a/config/locales/crowdin/js-vi.yml +++ b/config/locales/crowdin/js-vi.yml @@ -356,10 +356,10 @@ vi: learn_about: "Tìm hiểu thêm về các tính năng mới" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - The release brings various features and improvements for you, e.g.
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "Đăng ký lịch" inital_setup_error_message: "Đã xảy ra lỗi khi lấy dữ liệu." diff --git a/config/locales/crowdin/js-zh-CN.yml b/config/locales/crowdin/js-zh-CN.yml index 778bad39f2fb..6b14a76b7cba 100644 --- a/config/locales/crowdin/js-zh-CN.yml +++ b/config/locales/crowdin/js-zh-CN.yml @@ -355,10 +355,10 @@ zh-CN: learn_about: "详细了解新功能" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - 此版本为您带来了各种功能和改进,例如:
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "订阅日历" inital_setup_error_message: "获取数据时发生错误。" diff --git a/config/locales/crowdin/js-zh-TW.yml b/config/locales/crowdin/js-zh-TW.yml index ac5863d3b1c4..0dc84bf2d235 100644 --- a/config/locales/crowdin/js-zh-TW.yml +++ b/config/locales/crowdin/js-zh-TW.yml @@ -354,10 +354,10 @@ zh-TW: learn_about: "瞭解更多新功能的資訊" #Include the version to invalidate outdated translations in other locales. #Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > - 該版本為您帶來了各種功能和改進,例如:
    + The release brings various features and improvements for you, e.g.
    ical_sharing_modal: title: "訂閱日曆" inital_setup_error_message: "更新資料時發生錯誤" diff --git a/config/locales/crowdin/ro.yml b/config/locales/crowdin/ro.yml index 63277c499fcc..faa80d5ca7af 100644 --- a/config/locales/crowdin/ro.yml +++ b/config/locales/crowdin/ro.yml @@ -82,7 +82,7 @@ ro: order: "Comandați ediția Enterprise on-premise" paste: "Lipiți token-ul de suport pentru ediția Enterprise" required_for_feature: "Acest add-on este disponibil numai cu un token de asistență pentru ediția Enterprise activ." - enterprise_link: "Pentru mai multe informații, fă clic aici." + enterprise_link: "Pentru mai multe informații, clic aici." start_trial: "Începeți o încercare gratuită" book_now: "Rezervă acum" get_quote: "Obțineți o ofertă" @@ -113,7 +113,7 @@ ro: error_cannot_act_self: "Cannot perform actions on your own uploaded files." attribute_help_texts: note_public: "Orice text și imagini pe care le adaugi în acest câmp sunt vizibile public pentru toți utilizatorii conectați!" - text_overview: "În această vizualizare, puteți crea texte de ajutor personalizate pentru vizualizarea atributelor. Atunci când sunt definite, aceste texte pot fi afișate făcând clic pe pictograma de ajutor de lângă atributul care îi aparține." + text_overview: "În această vizualizare, poți crea texte de ajutor personalizate pentru vizualizarea atributelor. Atunci când sunt definite, aceste texte pot fi afișate făcând clic pe pictograma de ajutor de lângă atributul care îi aparține." label_plural: "Texte de ajutor pentru atribute" show_preview: "Previzualizare text" add_new: "Adaugă text de ajutor" @@ -376,7 +376,7 @@ ro: index: no_results_title_text: În acest moment nu există niciun participant la acest proiect. no_results_content_text: Adăugare participant la proiect - invite_by_mail: "Trimiteți invitația la %{mail}" + invite_by_mail: "Trimite invitația la %{mail}" send_invite_to: "Send invite to" columns: shared: "Partajate" @@ -504,7 +504,7 @@ ro: prioritiies: edit: priority_color_text: | - Fă clic pentru a desemna sau schimba culoarea acestui nivel de prioritate. + Clic pentru a desemna sau schimba culoarea acestui nivel de prioritate. Poate fi utilizate pentru a evidenția pachetele de lucru dintr-un tabel. reactions: action_title: "React" @@ -523,7 +523,7 @@ ro: statuses: edit: status_color_text: | - Fă click pentru a desemna sau schimba culoarea pentru acest flux de lucru. + Clic pentru a desemna sau schimba culoarea pentru acest flux de lucru. Acesta este afișat în butonul de statut și poate fi folosit pentru a evidenția pachetele de lucru din tabel. status_default_text: |- New work packages are by default set to this type. They cannot be read-only. @@ -1279,11 +1279,11 @@ ro: updated_on: "actualizat în %{datetime}" updated_on_time_entry: "logged time updated on %{datetime}" deleted_on: "deleted on %{datetime}" - deleted_by_on: "deleted by %{user} on %{datetime}" + deleted_by_on: "șters de %{user} în %{datetime}" added_on: "added on %{datetime}" - added_by_on: "added by %{user} on %{datetime}" + added_by_on: "creat de %{user} în %{datetime}" removed_on: "removed on %{datetime}" - removed_by_on: "removed by %{user} on %{datetime}" + removed_by_on: "eliminat de %{user} în %{datetime}" parent_without_of: "Subproiect" parent_no_longer: "Nu mai sunt subproiecte ale" time_entry: @@ -1409,7 +1409,7 @@ ro: button_change_password: "Modificare parolă" button_check_all: "Selectează tot" button_clear: "Inițializare" - button_click_to_reveal: "Fă clic pentru a dezvălui" + button_click_to_reveal: "Clic pentru a dezvălui" button_close: "Închide" button_collapse_all: "Restrângere totală" button_configure: "Configurare" @@ -1458,7 +1458,7 @@ ro: button_uncheck_all: "Deselecteaza tot" button_unlock: "Deblocare" button_unfavorite: "Elimină din favorite" - button_unwatch: "Stop monitorizare" + button_unwatch: "Nu mai urmări" button_update: "Actualizare" button_upgrade: "Actualizare" button_upload: "Încarcă" @@ -1725,7 +1725,7 @@ ro: error_scm_not_found: "Nu am găsit articolul sau revizia în repo." error_type_could_not_be_saved: "Tipul nu a putut fi salvat" error_unable_delete_status: "Starea pachetului de lucru nu poate fi ștearsă deoarece este folosită de cel puțin un pachet de lucru." - error_unable_delete_default_status: "Starea implicită pentru pachetele de lucru nu poate fi ștearsă. Setați o altă stare implicită pentru pachetele de lucru și apoi ștergeți-o pe cea actuală." + error_unable_delete_default_status: "Starea implicită pentru pachetele de lucru nu poate fi ștearsă. Setează o altă stare implicită pentru pachetele de lucru și apoi șterge-o pe cea actuală." error_unable_to_connect: "Nu a putut fi stabilită conexiunea (%{value})" error_unable_delete_wiki: "Pagina wiki nu poate fi ștearsă." error_unable_update_wiki: "Pagina wiki nu poate fi actualizată." @@ -1929,8 +1929,8 @@ ro: configuration_guide: "Ghid de configurare" get_in_touch: "Aveți întrebări? Luați legătura cu noi." instructions_after_registration: "Vă puteţi autentifica de îndată ce contul a fost activat dând click pe %{signin}." - instructions_after_logout: "Vă puteţi autentifica din nou dând click pe %{signin}." - instructions_after_error: "Puteţi încerca să vă autentificați din nou dând click pe %{signin}. Dacă eroarea persistă, cereți ajutorul administratorului." + instructions_after_logout: "Te poți autentifica din nou dând click pe %{signin}." + instructions_after_error: "Poţi încerca să te autentifici din nou dând click pe %{signin}. Dacă eroarea persistă, cere ajutorul administratorului." menus: admin: mail_notification: "Notificări prin e-mail" @@ -2019,7 +2019,7 @@ ro: label_add_related_work_packages: "Adăugare pachet de lucru asociat" label_add_subtask: "Adăugare sub-activitate" label_added: "adăugat" - label_added_by: "Added by %{author}" + label_added_by: "Adăugat de %{author}" label_added_time_by: "Adăugat de %{author} acum %{age}" label_additional_workflow_transitions_for_assignee: "Tranziții suplimentare permise când utilizatorul este executantul" label_additional_workflow_transitions_for_author: "Tranziții suplimentare permise când utilizatorul este autorul" @@ -2113,7 +2113,7 @@ ro: label_close_versions: "Închidere versiuni complete" label_closed_work_packages: "închis" label_collapse: "Restrângere" - label_collapsed_click_to_show: "S-a restrâns. Fă clic pentru a afișa" + label_collapsed_click_to_show: "S-a restrâns. Clic pentru a afișa" label_configuration: configurație label_comment_add: "Adăugare comentariu" label_comment_added: "Comentariu adăugat" @@ -2133,7 +2133,7 @@ ro: label_copy_project: "Copiere proiect" label_core_version: "Versiune de bază" label_core_build: "Core build" - label_created_by: "Created by %{user}" + label_created_by: "Creat de %{user}" label_current_status: "Stare actuală" label_current_version: "Versiune actuală" label_custom_field_add_no_type: "Adaugați acest câmp la un pachet de lucru" @@ -2205,7 +2205,7 @@ ro: label_import: "Importă" label_export_to: "Disponibil și în:" label_expand: "Extindere" - label_expanded_click_to_collapse: "Extinse. Faceți clic pentru a restrânge" + label_expanded_click_to_collapse: "Extinse. Clic pentru a restrânge" label_f_hour: "%{value} oră" label_f_hour_plural: "%{value} ore" label_favorite: "Favorit" @@ -2216,7 +2216,7 @@ ro: label_file_plural: "Fișiere" label_filter: "Filtre" label_filter_add: "Adăugare filtru" - label_filter_by: "Filter by" + label_filter_by: "Filtrează după" label_filter_plural: "Filtre" label_filters_toggle: "Arată/ascunde filtre" label_float: "Număr real" @@ -2550,7 +2550,7 @@ ro: label_ui: "Interfaţa cu utilizatorul" label_updated_time: "Actualizat acum %{value}" label_updated_time_at: "%{author} %{age}" - label_updated_time_by: "Actualizat de către %{author} acum %{age}" + label_updated_time_by: "Actualizat de %{author} acum %{age}" label_upgrade_guides: "Ghiduri de actualizare" label_used_by: "Folosit de" label_used_by_types: "Folosit/ă/e de tipurile" @@ -2694,7 +2694,7 @@ ro: created_at: "Creat la %{timestamp} de %{user} " login_to_see_all: "Autentifică-te pentru a vedea toate notificările." mentioned: "Ai fost menționat într-un comentariu" - mentioned_by: "%{user} v-a menţionat într-un comentariu" + mentioned_by: "%{user} v-a menționat într-un comentariu" more_to_see: one: "Mai există un pachet de lucru cu notificări." few: "Există %{count} mai multe pachete de lucru cu notificări." @@ -2711,7 +2711,7 @@ ro: date_alert_start_date: "Alertă dată" date_alert_due_date: "Alertă de dată" see_all: "Vezi tot" - updated_at: "Actualizat la %{timestamp} de %{user}" + updated_at: "Actualizat în %{timestamp} de %{user}" sharing: work_packages: allowed_actions: "You may %{allowed_actions} this work package. This can change depending on your project role and permissions." @@ -2759,7 +2759,7 @@ ro: mail_body_backup_token_reset_user_info: Token-ul backup a fost resetat. mail_body_backup_token_info: Token-ul anterior nu mai este valid. mail_body_backup_waiting_period: Noul token va fi activat în %{hours} ore. - mail_body_backup_token_warning: Dacă nu ați fost dvs., conectați-vă la OpenProject imediat și resetați-l din nou. + mail_body_backup_token_warning: Dacă nu ai fost tu, conectează-te la OpenProject imediat și resetează-l din nou. mail_body_incoming_email_error: E-mailul trimis la OpenProject nu a putut fi procesat. mail_body_incoming_email_error_in_reply_to: "La %{received_at} %{from_email} a scris" mail_body_incoming_email_error_logs: "Jurnale" @@ -2768,7 +2768,7 @@ ro: title: "Password change not possible" body: "Your account at %{app_title} is connected to an external authentication provider (%{name})." subtext: "Passwords for external account cannot be changed in the application. Please use the lost password functionality of your authentication provider." - mail_body_register: "Bine ați venit la %{app_title}. Vă rugăm să vă activați contul făcând clic pe acest link:" + mail_body_register: "Bine ai venit la %{app_title}. Te rog să activezi contul făcând clic pe acest link:" mail_body_register_header_title: "E-mail de invitație pentru membrii proiectului" mail_body_register_user: "Dragă %{name}," mail_body_register_links_html: | @@ -3637,7 +3637,7 @@ ro: https_mismatch: title: "Nepotrivire de configurare a modului HTTPS" text_html: > - Aplicația dumneavoastră rulează cu modul HTTPS setat la %{set_protocol}, dar solicitarea este o cerere %{actual_protocol} . Acest lucru va duce la erori! Va trebui să setați următoarea valoare de configurare: %{setting_value}. Vedeți documentația de instalare despre cum să setați această configurație. + Aplicația ta rulează cu modul HTTPS setat la %{set_protocol}, dar solicitarea este o cerere %{actual_protocol} . Acest lucru va duce la erori! Va trebui să setezi următoarea valoare de configurare: %{setting_value}. Vezi documentația de instalare despre cum să setezi această configurație. hostname_mismatch: title: "Nepotrivire de setare a numelui de gazdă" text_html: > diff --git a/config/locales/crowdin/zh-CN.yml b/config/locales/crowdin/zh-CN.yml index 28706d6c9254..1256239d82a7 100644 --- a/config/locales/crowdin/zh-CN.yml +++ b/config/locales/crowdin/zh-CN.yml @@ -231,7 +231,7 @@ zh-CN: subitems: zero: 无子项目 one: 1个子项目 - other: "%{count} 子项目" + other: "%{count} 个子项目" text_add_new_custom_field: > 要向项目添加新的自定义字段,您需要先创建字段,然后才能将其添加到此项目中。 is_enabled_globally: "已全局启用" diff --git a/config/locales/js-en.yml b/config/locales/js-en.yml index 76f9a26d6066..322eeb3246f7 100644 --- a/config/locales/js-en.yml +++ b/config/locales/js-en.yml @@ -399,17 +399,18 @@ en: learn_about: "Learn more about the new features" # Include the version to invalidate outdated translations in other locales. # Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release. - "14_6": + "15_0": standard: new_features_html: > The release brings various features and improvements for you, e.g.
    ical_sharing_modal: diff --git a/docs/api/apiv3/paths/versions.yml b/docs/api/apiv3/paths/versions.yml index 07f1953b348d..e69e05a9acc6 100644 --- a/docs/api/apiv3/paths/versions.yml +++ b/docs/api/apiv3/paths/versions.yml @@ -2,18 +2,31 @@ --- get: parameters: - - description: |- - JSON specifying filter conditions. - Accepts the same format as returned by the [queries](https://www.openproject.org/docs/api/endpoints/queries/) endpoint. - Currently supported filters are: + - description: |- + JSON specifying filter conditions. + Accepts the same format as returned by the [queries](https://www.openproject.org/docs/api/endpoints/queries/) endpoint. + Currently supported filters are: - + sharing: filters versions by how they are shared within the server (*none*, *descendants*, *hierarchy*, *tree*, *system*). - example: '[{ "sharing": { "operator": "*", "values": ["system"] }" }]' - in: query - name: filters - required: false - schema: - type: string + + sharing: filters versions by how they are shared within the server (*none*, *descendants*, *hierarchy*, *tree*, *system*). + example: '[{ "sharing": { "operator": "*", "values": ["system"] }" }]' + in: query + name: filters + required: false + schema: + type: string + - description: |- + JSON specifying sort criteria. + Accepts the same format as returned by the [queries](https://www.openproject.org/docs/api/endpoints/queries/) endpoint. Currently supported attributes are: + + + id: Sort by the version id + + name: Sort by the version name using numeric collation, comparing sequences of decimal digits by their numeric value + + semver_name: Deprecated, use name instead + example: '[["name", "desc"]]' + in: query + name: sortBy + required: false + schema: + type: string responses: '200': content: @@ -23,57 +36,57 @@ get: value: _embedded: elements: - - _links: - availableInProjects: - href: "/api/v3/versions/11/projects" - definingProject: - href: "/api/v3/projects/12" - self: - href: "/api/v3/versions/11" - _type: Version - description: - format: plain - html: This version has a description - raw: This version has a description - endDate: - id: 11 - name: v3.0 Alpha - startDate: '2014-11-20' - status: Open - - _links: - availableInProjects: - href: "/api/v3/versions/12/projects" - definingProject: - href: "/api/v3/projects/11" - self: - href: "/api/v3/versions/12" - _type: Version - description: - format: plain - html: '' - raw: '' - endDate: - id: 12 - name: v2.0 - startDate: - status: Closed - - _links: - availableInProjects: - href: "/api/v3/versions/13/projects" - definingProject: - href: "/api/v3/projects/13" - self: - href: "/api/v3/versions/10" - _type: Version - description: - format: plain - html: '' - raw: '' - endDate: - id: 10 - name: v1.0 - startDate: - status: Open + - _links: + availableInProjects: + href: "/api/v3/versions/11/projects" + definingProject: + href: "/api/v3/projects/12" + self: + href: "/api/v3/versions/11" + _type: Version + description: + format: plain + html: This version has a description + raw: This version has a description + endDate: + id: 11 + name: v3.0 Alpha + startDate: '2014-11-20' + status: Open + - _links: + availableInProjects: + href: "/api/v3/versions/12/projects" + definingProject: + href: "/api/v3/projects/11" + self: + href: "/api/v3/versions/12" + _type: Version + description: + format: plain + html: '' + raw: '' + endDate: + id: 12 + name: v2.0 + startDate: + status: Closed + - _links: + availableInProjects: + href: "/api/v3/versions/13/projects" + definingProject: + href: "/api/v3/projects/13" + self: + href: "/api/v3/versions/10" + _type: Version + description: + format: plain + html: '' + raw: '' + endDate: + id: 10 + name: v1.0 + startDate: + status: Open _links: self: href: "/api/v3/versions" @@ -85,7 +98,7 @@ get: description: OK headers: {} tags: - - Versions + - Versions description: Returns a collection of versions. The client can choose to filter the versions similar to how work packages are filtered. In addition to the provided filters, the server will reduce the result set to only contain versions, for which @@ -130,7 +143,7 @@ post: * a constraint for a property was violated (`PropertyConstraintViolation`) headers: {} tags: - - Versions + - Versions description: |- Creates a new version applying the attributes provided in the body. Please note that while there is a fixed set of attributes, custom fields can extend a version's attributes and are accepted by the endpoint. diff --git a/docs/api/apiv3/tags/work_packages.yml b/docs/api/apiv3/tags/work_packages.yml index b916f3051b88..fd0907dc0ef9 100644 --- a/docs/api/apiv3/tags/work_packages.yml +++ b/docs/api/apiv3/tags/work_packages.yml @@ -16,6 +16,7 @@ description: |- | update | Form endpoint that aids in preparing and performing edits on a WP | **Permission**: edit work package | | updateImmediately | Directly perform edits on a work package | **Permission**: edit work package | | watch | Add current user to WP watchers | logged in; not watching | + | delete | Delete this work package | **Permission**: delete work package | ## Linked Properties diff --git a/docs/development/development-environment/docker/README.md b/docs/development/development-environment/docker/README.md index e9f54ab237dc..a16792714f2e 100644 --- a/docs/development/development-environment/docker/README.md +++ b/docs/development/development-environment/docker/README.md @@ -466,7 +466,7 @@ Once the keycloak service is started and running, you can access the keycloak in and login with initial username and password as `admin`. Keycloak being an OpenID connect provider, we need to setup an OIDC integration for OpenProject. -[Setup OIDC (keycloak) integration for OpenProject](../../../installation-and-operations/misc/custom-openid-connect-providers/#keycloak) +[Setup OIDC (keycloak) integration for OpenProject](../../../system-admin-guide/authentication/openid-providers/) Once the above setup is completed, In the root `docker-compose.override.yml` file, uncomment all the environment in `backend` service for keycloak and set the values according to configuration done in keycloak for OpenProject Integration. diff --git a/docs/installation-and-operations/misc/custom-openid-connect-providers/README.md b/docs/installation-and-operations/misc/custom-openid-connect-providers/README.md index ffda2ec0a781..fe442ea23ae3 100644 --- a/docs/installation-and-operations/misc/custom-openid-connect-providers/README.md +++ b/docs/installation-and-operations/misc/custom-openid-connect-providers/README.md @@ -1,314 +1,9 @@ # Custom OpenID Connect providers -OpenProject's admin interface only allows you to configure providers from a pre-defined list. -This includes Google Workspace and Microsoft Azure Active Directory right now. Find out how to use those in the [OpenID Providers Authentication Guide](../../../system-admin-guide/authentication/openid-providers/). +> [!IMPORTANT] +> OpenID Connect providers is an Enterprise add-on. If you do not see the button you will have to activate the Enterprise edition first. -You can still use an arbitrary provider. But for the time being there is no user interface for this. -That means you will have to do it directly using the console on the server or via environment variables. +Starting in OpenProject 15.0., you can create custom OpenID Connect providers with the user interface [OpenID Providers Authentication Guide](../../../system-admin-guide/authentication/openid-providers/). -> **Warning**: Only do this if you know what you are doing. This may break your existing OpenID Connect authentication or cause other issues otherwise. +Please use this document for references on all configuration options. Any providers you have created in earlier versions will have been migrated and should be available from the user interface. -First start the console. - -```shell -sudo openproject run console -# if user the docker all-in-one container: docker exec -it openproject bundle exec rails console -# if using docker-compose: docker-compose run --rm web bundle exec rails console -``` - -Once in the console you can change the `plugin_openproject_openid_connect` setting -directly to configure arbitrary providers. - -Next define the settings for your custom provider. In this example we are configuring Okta: - -```ruby -options = { - "display_name"=>"Okta", - "host"=>"mypersonal.okta.com", - "identifier"=>"", - "secret"=>"", - "authorization_endpoint" => "/oauth2/v1/authorize", - "token_endpoint" => "/oauth2/v1/token", - "userinfo_endpoint" => "/oauth2/v1/userinfo", - "end_session_endpoint" => "https://mypersonal.okta.com/oauth2/{authorizationServerId}/v1/logout" -} -``` - -For Keycloak, settings similar to the following would be used: - -```ruby -options = { - "display_name"=>"Keycloak", - "host"=>"keycloak.example.com", - "identifier"=>"", - "secret"=>"", - "authorization_endpoint" => "/auth/realms/REALM/protocol/openid-connect/auth", - "token_endpoint" => "/auth/realms/REALM/protocol/openid-connect/token", - "userinfo_endpoint" => "/auth/realms/REALM/protocol/openid-connect/userinfo" -} -``` - -Just type this into the console and confirm by pressing *Enter*. - -This assumes that you have configured your application in the respective provider correctly -including the **redirect URL** which will be the following: - -```ruby -"#{Setting.protocol}://#{Setting.host_name}/auth/okta/callback" -``` - -You can copy that into the console to get the URL you need. - -Finally you can the write the actual setting like this: - -```ruby -Setting.plugin_openproject_openid_connect = Hash(Setting.plugin_openproject_openid_connect || {}).deep_merge({ - "providers" => { - "okta" => options - } -}) -``` - -Replace "okta" with any other value such as "keycloak". It is used as the identifier in some URLs so keep it a plain lowercase string. - -Just copy these lines into the console and again confirm using *Enter*. -After you are done you can leave the console by entering `exit`. - -Once this is done you will see an "Okta" button in the bottom area of the login form. -Clicking on it will start the login process. - -_**Note**: This is an Enterprise add-on. If you do not see the button you will have to activate the Enterprise edition first._ - -## Environment variables - -Rather than setting these options via the rails console, you can also define them through the -[OpenProject configuration](../../configuration/) which can -also be defined through -[environment variables](../../configuration/environment/). - -The variable names can be derived from the options seen above. All variables will start with the prefix -`OPENPROJECT_OPENID__CONNECT_` followed by the provider name. For instance the okta example from above would -be defined via environment variables like this: - -```shell -OPENPROJECT_OPENID__CONNECT_OKTA_DISPLAY__NAME="Okta" -OPENPROJECT_OPENID__CONNECT_OKTA_HOST="mypersonal.okta.com" -OPENPROJECT_OPENID__CONNECT_OKTA_IDENTIFIER="" -# etc. -``` - -**Note**: Underscores in option names must be escaped by doubling them. So make sure to really do use two consecutive -underscores in `DISPLAY__NAME`, `TOKEN__ENDPOINT` and so forth. - -## More options - -You can see a list of possible options [here](https://github.com/m0n9oose/omniauth_openid_connect#options-overview). - -### Known providers and multiple connection per provider - -There are a number of known providers where the endpoints are configured automatically based on the provider name in the configuration. All that is required are the client ID (identifier) and secret in that case. - -If you want to configure multiple connections using the same provider you can prefix an arbitrary name with the -provider name followed by a period. For instance, if you want to configure 2 AzureAD connections and 1 Google connection it would look like this: - -```ruby -Setting.plugin_openproject_openid_connect = Hash(Setting.plugin_openproject_openid_connect || {}).deep_merge({ - "providers" => { - "azure.dept1" => { "display_name"=>"Department 1","identifier"=>"...","secret"=>"..." }, - "azure.dept2" => { "display_name"=>"Department 2","identifier"=>"...","secret"=>"..." }, - "google" => { "display_name"=>"Google","identifier"=>"...","secret"=>"..." } - } -}) -``` - -At the time of writing the known providers are: `azure`, `google`, `okta` - -### Attribute mapping - -You can override the default attribute mapping for values derived from the userinfo endpoint. For example, let's map the OpenProject login to the claim `preferred_username` that is sent by many OIDC providers. - -```ruby -options = { - # ... other options - attribute_map: { - 'login' => 'preferred_username' - } -} -``` - -### Back-channel logout - -OpenProject OIDC integration supports [back-channel logouts](https://openid.net/specs/openid-connect-backchannel-1_0.html) if OpenProject is configured for ActiveRecord based sessions (which is the default). - -On the identity provider side, you need to set `https:///auth//backchannel-logout`. `` is the identifier of the OIDC configuration as provided above. - -#### Respecting self-registration - -You can configure OpenProject to restrict which users can register on the system with the [authentication self-registration setting](../../../system-admin-guide/authentication/authentication-settings) - - By default, users returning from a SAML idP will be automatically created. If you'd like for the SAML integration to respect the configured self-registration option, please use setting `limit_self_registration`: - -```ruby -options = { - # ... other options - limit_self_registration: true -} -``` - -### Claims - -You can also request [claims](https://openid.net/specs/openid-connect-core-1_0-final.html#Claims) for both the id_token and userinfo endpoint. -Mind though that currently only claims requested for the id_token returned with the authorize response are validated. -That is authentication will fail if a requested essential claim is not returned. - -#### Requesting MFA authentication via the ACR claim - -Say for example that you want to request that the user authenticate using MFA (multi-factor authentication). -You can do this by using the ACR (Authentication Context Class Reference) claim. - -This may look different for each identity provider. But if they follow, for instance the [EAP (Extended Authentication Profile)](https://openid.net/specs/openid-connect-eap-acr-values-1_0.html) then the claims would be `phr` (phishing-resistant) and 'phrh' (phishing-resistant hardware-protected). Others may simply have an additional claim called `Multi_Factor`. - -You have to check with your identity provider how these values must be called. - -In the following example we request a list of ACR values. One of which must be satisfied -(i.e. returned in the ID token by the identity provider, meaning that the requested authentication mechanism was used) -for the login in OpenProject to succeed. If none of the requested claims are present, authentication will fail. - -```ruby -options = { ... } - -options["claims"] = { - "id_token": { - "acr": { - "essential": true, - "values": ["phr", "phrh", "Multi_Factor"] - } - } -} -``` - -#### Non-essential claims - -You may also request non-essential claims. In the example above this indicates that users should preferably be authenticated using -those mechanisms but it's not strictly required. The login into OpenProject will then work even if none of the claims -are returned by the identity provider. - -**The acr_values option** - -For non-essential ACR claims you can also use the shorthand form of the option like this: - -```ruby -options = { ... } - -options["acr_values"] = "phr phrh Multi_Factor" -``` - -The option takes a space-separated list of ACR values. This is functionally the same as using the -more complicated `claims` option above but with `"essential": false`. - -For all other claims there is no such shorthand. - -## Instructions for common OIDC providers - -The following section contains instructions for common OpenID Connect providers. Feel free to contribute your settings through the editing functionality at the bottom of this page. - -### Keycloak - -In Keycloak, use the following steps to set up a OIDC integration for OpenProject: - -- Select or create a realm you want to authenticate OpenProject with. Remember that realm identifier. For the remainder of this section, we're using REALM as the placeholder you'll need to replace. -- Under "Clients" menu, click on "Create" or "Create client" -- **Add client**: Enter the following details - - **Client type / protocol**: OpenID Connect - - **Client ID**: `https://` - - **Name**: Choose any name, used only within keycloak -- For the **Capability config**, keep Standard flow checked. In our tested version of Keycloak, this was the default. -- Click on Save - -You will be forwarded to the settings tab of the new client. Change these settings: - -- Set **Valid redirect URIs** to `https:///auth/keycloak/*` -- Enable **Sign Documents** -- If you want to enable [Backchannel logout](https://openid.net/specs/openid-connect-backchannel-1_0.html), set **Backchannel logout URL** to `https:///auth/keycloak/backchannel-logout` - -Next, you will need to create or note down the client secret for that client. - -- Go to the **Credentials** tab -- Click on the copy to clipboard button next to **Client secret** to copy that value - -**OPTIONAL:** By default, OpenProject will map the user's email to the login attribute in OpenProject. If you want to change that, you can do it by providing an alternate claim value in Keycloak: - -- Go to **Client scopes** -- Click on the `https://-dedicated` scope -- Click on **Add mapper** and **By configuration** -- Select **User property** -- Assuming you want to provide the username as `preferred_username` to OpenProject, set these values. This will depend on what attribute you want to map: - - Set name and to `username` - - Set Token claim name to `preferred_username` -- Click on **Save** - -#### Setting up OpenProject for Keycloak integration - -In OpenProject, these are the variables you will need to set. Please refer to the above documentation for the different ways you can configure these variables: - -```shell -# The name of the login button in OpenProject, you can freely set this to anything you like -OPENPROJECT_OPENID__CONNECT_KEYCLOAK_DISPLAY__NAME="Keycloak" -OPENPROJECT_OPENID__CONNECT_KEYCLOAK_HOST="" -OPENPROJECT_OPENID__CONNECT_KEYCLOAK_IDENTIFIER="https://" -OPENPROJECT_OPENID__CONNECT_KEYCLOAK_SECRET="" -OPENPROJECT_OPENID__CONNECT_KEYCLOAK_ISSUER="https:///realms/" -OPENPROJECT_OPENID__CONNECT_KEYCLOAK_AUTHORIZATION__ENDPOINT="/realms//protocol/openid-connect/auth" -OPENPROJECT_OPENID__CONNECT_KEYCLOAK_TOKEN__ENDPOINT="/realms//protocol/openid-connect/token" -OPENPROJECT_OPENID__CONNECT_KEYCLOAK_USERINFO__ENDPOINT="/realms//protocol/openid-connect/userinfo" -OPENPROJECT_OPENID__CONNECT_KEYCLOAK_END__SESSION__ENDPOINT="http:///realms//protocol/openid-connect/logout" -# Optional, if you have created the client scope mapper as shown above -# OPENPROJECT_OPENID__CONNECT_KEYCLOAK_ATTRIBUTE__MAP_LOGIN="preferred_username" -``` - -### Azure with Microsoft Graph API - -The Azure integration for OpenProject uses the previous userinfo endpoints, which for some tenants results in not being able to access the user's email attribute. [See this bug report for more information](https://community.openproject.org/projects/openproject/work_packages/45832). While our UI is still being extended to accept the new endpoints, you can manually configure Azure like follows. - -**What you need from Azure** - -Use our [Azure Active Directory guide](../../../system-admin-guide/authentication/openid-providers/#azure-active-directory) to create the OpenProject client and note down these values - -- The Client ID you set up for OpenProject (assumed to be `https://`) -- The client secret -- The tenant's UUID ([Please see this guide](https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc) for more information on the tenant value) - -#### Setting up OpenProject for Keycloak integration - -In OpenProject, these are the variables you will need to set. Please refer to the above documentation for the different ways you can configure these variables: - -```shell -openproject config:set OPENPROJECT_OPENID__CONNECT_AZURE_DISPLAY__NAME="Azure" -openproject config:set OPENPROJECT_OPENID__CONNECT_AZURE_HOST="login.microsoftonline.com" -openproject config:set OPENPROJECT_OPENID__CONNECT_AZURE_IDENTIFIER="https://" -openproject config:set OPENPROJECT_OPENID__CONNECT_AZURE_SECRET="" -openproject config:set OPENPROJECT_OPENID__CONNECT_AZURE_AUTHORIZATION__ENDPOINT="https://login.microsoftonline.com/%3CUUID%3E/oauth2/v2.0/authorize" -openproject config:set OPENPROJECT_OPENID__CONNECT_AZURE_TOKEN__ENDPOINT="https://login.microsoftonline.com/%3CUUID%3E/oauth2/v2.0/token" -openproject config:set OPENPROJECT_OPENID__CONNECT_AZURE_USERINFO__ENDPOINT="https://graph.microsoft.com/oidc/userinfo" -``` - -Restart your OpenProject server and test the login button to see if it works. - -## Troubleshooting - -**Q: After clicking on a provider badge, I am redirected to a signup form that says a user already exists with that login.** - -A: This can happen if you previously created user accounts in OpenProject with the same email than what is stored in the identity provider. In this case, if you want to allow existing users to be automatically remapped to the OIDC provider, you should do the following: - -Spawn an interactive console in OpenProject. The following example shows the command for the packaged installation. -See [our process control guide](../../../installation-and-operations/operation/control/) for information on other installation types. - -```shell -sudo openproject run console -> Setting.oauth_allow_remapping_of_existing_users = true -> exit -``` - -Then, existing users should be able to log in using their OIDC identity. Note that this works only if the user is using password-based authentication, and is not linked to any other authentication source (e.g. LDAP) or identity provider. - -Note that this setting is set to true by default for new installations already. diff --git a/docs/release-notes/15-0-0/README.md b/docs/release-notes/15-0-0/README.md index 3fae72b7668e..5ec88aa01e2b 100644 --- a/docs/release-notes/15-0-0/README.md +++ b/docs/release-notes/15-0-0/README.md @@ -10,13 +10,13 @@ release_date: 2024-10-31 Release date: 2024-11-13 -We released [OpenProject 15.0.0](https://community.openproject.org/versions/2076). The major release contains several bug fixes and we recommend updating to the newest version. In these Release Notes, we will give an overview of important feature changes. At the end, you will find a complete list of all changes and bug fixes. +We released [OpenProject 15.0.0](https://community.openproject.org/versions/2076). This major release introduces a new timeline for work package activities with emoji reactions, adds a user interface for configuring SSO providers, and more improvements. With over 50 bugs fixed, we recommend you update OpenProject. In these Release Notes, we will give an overview of important feature changes. At the end, you will find a complete list of all changes and bug fixes. ## Important feature changes -### Boost your communication with a better structured activity tab, real-time loading messages and notifications, emoji reactions, and more +### A new look-and feel for the Activity tab, with better structure, real-time loading messages, emoji reactions, and more -A major change in version 15.0 is the overall look-and-feel of the activity tab of work packages. Users will notice that the activity tab has a new design with additional options, and that emoji reactions are now enabled. Additionally, new comments will appear directly without having to reload. This also applies to the notification center, where new notifications will appear in real-time. +A major change in version 15.0 is the overall look-and-feel of the Activity tab of work packages. Users will notice that the Activity tab has a new design with additional options, and that emoji reactions are now enabled. Additionally, new comments will appear directly without having to reload. This also applies to the notification center, where new notifications will appear in real-time. This is a big bundle of new features that will greatly improve communication and interaction within OpenProject, making it more simple, more effective and more fun. @@ -29,14 +29,14 @@ Related features in 15.0: Let's take a closer look at the three biggest changes concerning this feature bundle: -#### A new timeline design for the activity tab of work packages +#### A new timeline design for the Activity tab of work packages Apart from some obvious design changes that all fit GitHub's Primer design system, users will benefit from some great new features, such as: - The comment box being a fixed element anchored to the bottom of the split screen area. -- Filtering the activity panel with options to either show everything, changes only or comments only. +- Filtering the Activity panel with options to either show everything, changes only or comments only. - Ordering to either newest on top or newest at the bottom -![Screenshot showing the new activity tab with highlighted changes](openproject-15-0-activity-tab-highlighted-all.png) +![Screenshot showing the new Activity tab with highlighted changes](openproject-15-0-activity-tab-highlighted-all.png) #### Emoji reactions to work package comments @@ -50,9 +50,9 @@ Please note that emoji reactions don't trigger notifications. If you need your c Starting with version 15.0, the notification center will continuously update and new notifications will appear directly. This means no more blue flash message mentioning that there are updates and asking if you want to reload the page. The number shown next to the bell icon will also update immediately. This feature adds to our goal to enable smooth communication and information. -### Benefit from easy Single Sign-On authentication settings with SAML and OIDC in your administration +### Admin interfaces for SAML and OpenID Connect (Enterprise only) -With OpenProject 15.0, particularly SaaS customers will benefit from our new user interface for SAML and OIDC. This means they can now set up integrations between OpenProject and SAML or OpenID connect stacks independently and offer users options for Single Sign-On (SSO). Before 15.0, SaaS customers had to contact the OpenProject support if they wanted custom integrations with their SAML or OpenID connect stacks. Now, they cannot only set them up on their own, but also have tools for debugging them if needed. +OpenProject has for a long time supported SAML and OpenID Connect configured through settings or environment variables. With OpenProject 15.0, all enterprise customers will benefit from our new user interface for SAML and OIDC. This means they can now set up integrations between OpenProject and SAML or OpenID connect stacks independently and offer users options for Single Sign-On (SSO). Before 15.0, SaaS customers had to contact the OpenProject support if they wanted custom integrations with their SAML or OpenID connect providers. These new interfaces greatly improve the user experience of adding these providers, which had been a hurdle at the beginning of your work with OpenProject. Related features in 15.0: @@ -69,17 +69,19 @@ Once set up, users can log in with their existing account, for example like show ![Example screenshot of the log in screen with options to single sign-on](OpenProject_SSO_SAML_OpenID-highlighted.png) -### Use the new 'Standard global role' and enable permissions to view email addresses +### A new 'Standard global role' with permissions to view email addresses -With OpenProject 15.0, you get a new default 'Standard global role' that is automatically and permanently given to all users. If you are an administrator responsible for roles, please check this under *Administration > Users and permissions > Roles and permissions > Standard global role*. This role has several permissions to choose from, one being 'View users' mail addresses'. Enable this permission to allow any user to see everyone's email address in autocomplete situations, such as when they select a work package assignee from a drop-down list. +With OpenProject 15.0, a new default 'Standard global role' is automatically and permanently given to all users. If you are an administrator responsible for roles, please check this under *Administration > Users and permissions > Roles and permissions > Standard global role*. This role has several permissions to choose from, one being 'View users' mail addresses'. Enable this permission to allow any user to see everyone's email address in autocomplete situations, such as when they select a work package assignee from a drop-down list. -Before version 15.0, users could choose whether their email address was displayed. Now this is an administrative decision that applies to either everyone or no one. +Before version 15.0, users could choose whether their email address was displayed. Now this is an administrative decision that applies to either everyone or no one. The reason behind this is that this privacy control should be decided at the organizational level rather than individually. + +As a default, this new role does not have any permissions selected, so there is no change in the granted permissions due to the update. ![Example screenshot of permissions view for the new Standard global role, with checkmark at 'View users' mail addresses](openproject-15-0-standard-global-role.png) -### Enjoy easier navigation in project lists with quick action table headers +### Quick action table headers in project lists for easier navigation -With OpenProject 15.0, we are pleased to release another great feature for our project lists: Clicking on the table headers in a project list now gives you a quick action menu that not only allows you to sort in descending or ascending order, but also to filter or change, remove or add a column. While these features are not new and you can still find them in the top-right menu button, these actions are now much quicker to access. +With OpenProject 15.0, we are pleased to release another great improvement for our project lists: Clicking on the table headers in a project list now gives you a quick action menu that not only allows you to sort in descending or ascending order, but also to filter or change, remove or add a column. You can still find the same actions in the top-right menu button, but now these actions are now much quicker to access. ![Example screenshot of a project list with dropdown menu on a table header](openproject-15-0-project-lists.png) @@ -106,12 +108,35 @@ With OpenProject 14.6, we released a feature that allows you to enable or disabl In the personal menu that can be accessed by clicking on your avatar, we renamed 'My account' to 'Account settings', in order to give you a clearer understanding that this menu item contains settings. It now also differs more clearly from 'My Page' and 'My Activities‘, which provide personal data instead of settings. -## Important updates and breaking changes +### Change the basic work package hovercard to Primer design system + +If you hover over a linked work package, e.g. in a work package description or in a wiki entry, you will now see a new hovercard, following the Primer design system. This card displays the most important information, like type, ID or status of the work package along with the assignee and their avatar. Here is an example of how this might look like now: + +![Example screenshot showing a work package hovercard](openproject-15-0-hovercard.png) + +### Improve display of work package and agenda titles in meetings - +There have been some design improvements regarding meetings again. The work package title will now always be fully displayed, so you won't have to click on it anymore and follow the link to read the whole title. This works in desktop as well as mobile view. +## Important technical updates + +### SAML and OpenID connect providers + +With the introduction of the user interface for SAML and OpenID connect providers, the previous settings-based configuration has been deprecated. All existing providers that you created with these settings have been automatically converted into the UI element. + +To modify or update your configuration, please visit *Administration* -> *Authentication* -> *SAML providers* or *OpenID providers*. If you experience issues with your configuration after your update, please step through the configuration in the administration and confirm the settings. If you experience new issues on the connection to your providers after upgrading, please do not hesitate to reach out to our support team. + +If you configured your provider using environment variables (e.g., in Docker-based or through the Helm-chart values), this configuration option remains. The configured provider will still appear in the user interface, but is marked read-only. If you need to modify the environment variables, you need to make sure the seed rake task has been run. + +For more information, please see our updated guides on these topics: + +- [Connecting your OpenID connect provider to OpenProject](../../system-admin-guide/authentication/openid-providers/) +- [Connecting your SAML identity provider to OpenProject](../../system-admin-guide/authentication/saml/) + + + ## Bug fixes and changes @@ -204,7 +229,7 @@ In the personal menu that can be accessed by clicking on your avatar, we renamed ## Contributions -A very special thank you goes to the City of Cologne for sponsoring parts of this release. Also a big thanks to our Community members for reporting bugs and helping us identify and provide fixes. Special thanks for reporting and finding bugs go to Bill Bai, Sam Yelman, Ivan Kuchin, Knight Chang, Gábor Alexovics, Gregor Buergisser, Andrey Dermeyko, Various Interactive, Clayton Belcher, Александр Татаринцев, and Keno Krewer. +A very special thank you goes to the City of Cologne for sponsoring parts of this release. Also a big thanks to our Community members for reporting bugs and helping us identify and provide fixes. Special thanks for reporting and finding bugs go to Bill Bai, Sam Yelman, Knight Chang, Gábor Alexovics, Gregor Buergisser, Andrey Dermeyko, Various Interactive, Clayton Belcher, Александр Татаринцев, and Keno Krewer. Last but not least, we are very grateful for our very engaged translation contributors on Crowdin, who translated quite a few OpenProject strings! This release we would like to highlight - [Alexander Aleschenko](https://crowdin.com/profile/top4ek), for a great number of translations into Russian. diff --git a/docs/release-notes/15-0-0/openproject-15-0-hovercard.png b/docs/release-notes/15-0-0/openproject-15-0-hovercard.png new file mode 100644 index 000000000000..1f5ac2b8c9c4 Binary files /dev/null and b/docs/release-notes/15-0-0/openproject-15-0-hovercard.png differ diff --git a/docs/system-admin-guide/authentication/authentication-faq/README.md b/docs/system-admin-guide/authentication/authentication-faq/README.md index 451636ac081e..157cfa6ac209 100644 --- a/docs/system-admin-guide/authentication/authentication-faq/README.md +++ b/docs/system-admin-guide/authentication/authentication-faq/README.md @@ -46,8 +46,8 @@ We support all authentication providers that support the SAML and OpenID Connec ## Is it possible to use a custom SSO provider (e.g. Keycloak) with the Enterprise cloud edition? -It is possible to use Keycloak, but you can't configure it yourself at the moment as there's no user interface (UI) for custom SSO providers. We can set up the custom provider for you. Then you can access and edit it in the administration. You will be able to enter client ID and client secret via the OpenProject UI. -For context: The connection of custom SSO providers is also described [here](../../../installation-and-operations/misc/custom-openid-connect-providers/#custom-openid-connect-providers) (however, we would enter this configuration for your Enterprise cloud environment). +It is possible to use Keycloak, Okta, or other OpenID Connect providers with the user interface (UI) for custom SSO providers. +For context: The connection of custom SSO providers is also described [here](../openid-providers/). ## I want to connect AD and LDAP to OpenProject. Which attribute for authentication sources does OpenProject use? diff --git a/docs/system-admin-guide/authentication/openid-providers/README.md b/docs/system-admin-guide/authentication/openid-providers/README.md index e8120fe018a1..0c226cb635e5 100644 --- a/docs/system-admin-guide/authentication/openid-providers/README.md +++ b/docs/system-admin-guide/authentication/openid-providers/README.md @@ -9,48 +9,38 @@ keywords: OpenID providers | Topic | Content | | ------------------------------------------------------------ | ------------------------------------------------------------ | -| [Google Workspace](#google-workspace) | How to use Google Workspace as an SSO provider for OpenProject? | -| [Azure Active Directory](#azure-active-directory) | How to use Microsoft Azure Active Directory as an SSO provider for OpenProject? | -| [Custom OpenID Connect Providers](#custom-openid-connect-providers) | Configuration of additional OpenID Connect providers. | +| Login with [Google Workspace](#google) | How to use Google Workspace as an SSO provider for OpenProject? | +| [Microsoft Entra ID](#microsoft-entra) (previously Azure) | How to use Microsoft Azure Active Directory as an SSO provider for OpenProject? | +| [Custom OpenID Connect Providers](#custom-openid-connect-provider) | Configuration of additional OpenID Connect providers. | | [Troubleshooting](#troubleshooting) | Common complications when using OpenID as SSO. | To activate and configure OpenID providers in OpenProject, navigate to *Administration* -> *Authentication* and choose -> *OpenID providers*. -## Add a new authentication application for oauth +## Add a new OpenID Connect provider To add a new OpenID provider, click the green **+ OpenID provider** button. -![OpenID providers in OpenProject administration](openproject_system-admin-guide_authentication_openid_provider.png) +![OpenIDprovider selection in OpenProject administration](openproject_system-admin-guide_authentication_openid_provider_empty.png) -You can configure the following options. -1. Choose **Google** or **Azure** to add as an OpenID provider to OpenProject. -2. Optionally enter a **display name**. +You can create different kinds of providers with a different set of properties. You can choose from: -3. Enter the **Identifier**. +- [Google](#google) +- [Microsoft Entra ID](#microsoft-entra) (previously Azure) +- [Custom OpenID Connect Providers](#custom-openid-connect-provider) -4. Enter the **Secret**. -5. Optionally, if you want to honor the system-wide self-registration setting, enable "Limit self registration". - When checked, users will be created according to the [self-registration setting](../authentication-settings). - -6. Set the **tenant** of your Azure endpoint. This will control who gets access to the OpenProject instance. For more information, please see [our user guide on Azure OpenID connect](#azure-active-directory) - -7. Press the **create** button. - - ![Add a new OpenID provider in OpenProject administration](openproject_system-admin-guide_authentication_openid_provider_new.png) - -## Google Workspace +## Google ### Step 1: Create the OAuth consent screen 1. Navigate to your GCP console. (https://console.cloud.google.com/) 2. Go to **APIs & Services** > OAuth consent screen. -![g1-apis-and-services-oauth-consent-screen](g1-apis-and-services-oauth-consent-screen.png) +![APIs and services OAuth consent screen](g1-apis-and-services-oauth-consent-screen.png) -3. Create a new project and a new app or edit an existing project and an existing app, setting the following fields (shall be Internal): +3. Create a new project and a new app or edit an existing project and an existing app, setting the following fields (should be internal): 1. **App name** (e.g. EXAMPLE.COM SSO) 2. **User support email** (e.g. user-support@example.com) 3. **App domains** (at minimum, you must provide the Application home page - e.g. `https://example.openproject.com`) @@ -58,7 +48,7 @@ You can configure the following options. 5. **Developer Contact information** (e.g. developer@example.com) 6. Click **SAVE AND CONTINUE** to proceed. -![g2-edit-app-registration](g2-edit-app-registration.png) +![Edit app registration](g2-edit-app-registration.png) 4. **Scopes** - Press **SAVE AND CONTINUE** 5. **Summary** - Press **SAVE AND CONTINUE** @@ -67,45 +57,45 @@ You can configure the following options. 1. Under **APIs & Services**, go to **Credentials**. -![g3-apis-and-services-credentials](g3-apis-and-services-credentials.png) +![APIs and services credentials](g3-apis-and-services-credentials.png) 2. Click **Create Credentials** and select **OAuth Client ID**. 1. When prompted for your **Application type**, choose **Web Application**. - 2. Provide a **Name** for your application. (e.g. example-openproject-com) + 2. Provide a **Name** for your application. (e.g. example-openproject.com) 3. Under Authorized redirect URIs, click **Add URI**, and provide your URI (e.g. [example.openproject.com]/auth/google/callback). 4. Click **CREATE** or **SAVE** . -![g4-create-credentials-oauth-client-id](g4-create-credentials-oauth-client-id.png) +![Create credentials for OAuth client id](g4-create-credentials-oauth-client-id.png) -After pressing **CREATE** you will get a pop-up window like the following +After pressing **CREATE** you will see a following pop-up window. -- Note **Client ID** -- Note **Client Secret** +> [!TIP] +> +> Make sure to note your **Client ID** and **Client Secret**. -![g5-oauth-client-created](g5-oauth-client-created.png) +![OAuth client created](g5-oauth-client-created.png) ### Step 3: Add Google as an OpenID Provider to OpenProject 1. Login as OpenProject Administrator -2. Navigate to *Administration* -> *Authentication* and choose -> *OpenID providers*. - 1. **Name** Choose Google - 2. **Display Name** (e.g. **EXAMPLE.COM SSO**) - 3. **Identifier** (**Client ID** from step 2) - 4. **Secret** (**Client Secret** from step 2) - 5. Enable **Limit self registration** option -3. Press **Create** +2. Navigate to *Administration* -> *Authentication* and choose -> *OpenID providers*. + - **Click** the green *+ OpenID Connect provider* button + - **Choose** Choose the Option Google + - Set a **Display Name**, this is the name of the login button shown to users. + - On the next section, set **Client ID** and **Client Secret** (from step 2) + - Enable **Limit self registration** option if you want users that create accounts with this provider to bypass the configured limit for self-registration. ![Add a new OpenID Gogole provider in OpenProject administration](openproject_system-admin-guide_authentication_openid_provider_new_google.png) -4. The following green notification **Successful creation** should appear +Press **Finish setup** to save the client and complete. If you go back to the index page of OpenID connect providers, the new provider should be visible. -![Successful OpenID creation message in OpenProject administration](openproject_system-admin-guide_authentication_openid_provider_new_google_successful_message.png) +![Saved Google authentication provider](openproject_system-admin-guide_authentication_openid_provider_saved_google.png) -## Azure Active Directory +## Microsoft Entra ### Step 1: Register an App in Azure Active Directory @@ -113,41 +103,51 @@ If your organization currently has an Azure Active Directory to manage users, an The steps are as follows: -Log into your Microsoft account, and go to the Azure Active Directory administration page. +1. Log into your Microsoft account, and go to the Azure Active Directory administration page. ![Azure Active Directory administration page](01-menu.png) -In the sidebar, click on "All services". +2. In the sidebar, click **All services**. ![Azure Active Directory All services](02-admin-dashboard.png) -Click on the link named "App registrations". +3. Click the link named **App registrations**. ![Azure Active Directory App registrations](03-app-registrations.png) -Click on "New registration". +4. Click **New registration**. ![Azure Active Directory New registration](04-register-app.png) -You are now asked for a few settings: +5. You will then be asked to specify the following settings: -* For "Name", enter "OpenProject". -* For "Supported account types", select "Accounts in this organization directory only". -* For "Redirect URI", select the "Web" type, and enter the URL to your OpenProject installation, followed by "/auth/azure/callback". For instance: "https://myserver.com/auth/azure/callback". +* For **Name**, enter *OpenProject*. +* For **Supported account types**, select *Accounts in this organization directory only*. +* For **Redirect URI**, select the *Web* type, and enter the URL to your OpenProject installation, followed by */auth/oidc-microsoft-entra/callback*. For instance: "https://myserver.com/auth/oidc-microsoft-entra/callback". -When you are done, click on the "Register" button at the end of the page. You are redirected to your new App registration, be sure to save the "Application (client) ID" that is now displayed. You will need it later. +> [!NOTE] +> +> The Redirect URI is dependent on the display name that you choose later on. You might need to change it to the correct value shown in the administration of OpenProject. + +6. When you are done, click the **Register** button at the end of the page. You will be redirected to your new App registration. + +> [!IMPORTANT] +> Make sure to save the **Application (client) ID** that is now displayed. You will need it later. ![Azure Active Directory Admin Dashboard](02-admin-dashboard-1580821056307.png) -You can now click on "Certificates & secret". +7. You can then click **Certificates & secret**. ![Azure Active Directory Certificates](06-certificates.png) -Then click on "New client secret", set the description to "client_secret", and the expiration to "730 days (24 months)". Then click on "Add". +8. Then click **New client secret**, set the description to *client_secret*, and the expiration to *730 days (24 months)*. Then click **Add**. ![Azure Active Directory New Client Secret](07-client-secret.png) -A secret should have been generated and is now displayed on the page. Be sure to save it somewhere because it will only be displayed once. +9. A secret should have been generated and will be displayed on the page. + +> [!IMPORTANT] +> Make sure to save it because it will only be displayed once. ![Azure Active Directory Add Secret](08-add-secret.png) @@ -155,30 +155,284 @@ At the end of this step, you should have a copy of the Application client ID as ### Step 2: Configure OpenProject -Now, head over to OpenProject > Administration > OpenID providers. Click on "New OpenID provider", select the Azure type, enter the client ID and client Secret. +Next, you need to create the OpenID Connect provider in OpenProject: + +1. Login as OpenProject Administrator +2. Navigate to *Administration* -> *Authentication* and choose -> *OpenID providers*. + - **Click** the green *+ OpenID Connect provider* button + - **Choose** Choose the option **Microsoft Entra** + - Set display name **Microsoft Entra**. Please note that if you change this value, the redirect URI in step 1) might change. The redirect URI is shown in the side panel on the right side once you saved the configuration. + - Set the **Tenant**: By default, OpenProject will use the Microsoft Graph API endpoint to perform user info requests. + For that, you will need to enter the correct tenant identifier for your Azure instance. + To find the correct value for your instance, [please see this guide](https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc#find-your-apps-openid-configuration-document-uri). + - In the next section, set **Client ID** and **Client Secret** (from step 1) + - Enable **Limit self registration** option if you want users that create accounts with this provider to bypass the configured limit for self-registration. + +![Add a new OpenID Google provider in OpenProject administration](azure-display-name-tenant.png) + +Press **Finish setup** to save the client and complete. If you go back to the index page of OpenID connect providers, the new provider should be visible. There you will see the redirect URI on the right side in case you set a custom display name. + +![Saved Google authentication provider](./oidc-index-page.png) Congratulations, your users can now authenticate using your Microsoft Entra ID provider using the button in the Login form. + +## Custom OpenID Connect Provider + +Starting with OpenProject 15.0., you can also create custom OpenID Connect providers using the user interface. + +To start creating a custom provider, please follow these steps: + +1. Login as OpenProject Administrator +2. Navigate to *Administration* -> *Authentication* and choose -> *OpenID providers*. + - **Click** the green *+ OpenID Connect provider* button + - **Choose** Choose the *Option* **Custom** + +#### Step 1: Display name + +- Set a **Display Name**, this is the name of the login button shown to users. Let's assume we're trying to connect *Keycloak* with OpenProject for this example. We will type in Keycloak as that's the label of the button to be shown to users trying to authenticate. + + + +#### Step 2: Discovery endpoint + +- In the next section, you have the option to specify a discovery endpoint URL to pre-fill some public attributes + - For Keycloak, this URL is based on the configured realm name `http://keycloak.example.com:443/realms/{realm}/.well-known/openid-configuration` +- If you have a discovery endpoint URL, choose **I have a discovery endpoint URL** and fill it in +- Click **Continue**. With a discovery endpoint URL, OpenProject will try to fetch this information and take you to the next step. Observe the page for error responses in case it cannot connect to the endpoint or the returned information is invalid. + +![Discovery endpoint URL](./custom-provider-metadata-discovery.png) + +#### Step 3: Advanced configuration + +- Unless the metadata endpoint provided these values, you will have to fill out some required endpoint URLs, such as **Authorization endpoint**, **User information endpoint**, and **Token endpoint**. +- Fill out the **Issuer** field which depends on the provider. For Keycloak, this value would be the realm URL: `http://keycloak.example.com:443/realms/{realm}` +- Optionally fill out: + - **End session endpoint**, an URL where OpenProject should redirect to terminate a user's session. + - **JWKS URI**. This is the URL of the provider's JSON Web Key Set document containing e.g., signing keys and certificates. + - A custom icon by using a publicly available URL to fetch the logo from. +- Click **Continue** to validate this form and move to the next step. If there are any errors in this form, they will turn red and inform you about what you need to change. + +![Custom OpenID provider advanced configuration in OpenProject](custom-provider-advanced-config.png) + +#### Step 5: Client details + +In the next section, fill out the client credentials provided from your OpenID Connect provider: + +- Fill out **Client ID** and **Client secret** +- If you want users to be redirected to a separate endpoint _after logging out_ at the identity provider, set **Post Logout Redirect URI**. +- If you want this login mechanism to respect the global setting for self registration limits, check **Limit self registration**. +- Click **Continue**. + +#### Step 6: Optional attribute mapping + +You can optionally provide a custom mapping for attributes in the `userinfo` endpoint response. In most cases, you can leave this empty, unless you are providing custom attributes for user properties. + +If you need to set some of these values, enter the attribute key used/returned in the `userinfo` endpoint. + +For example: Keycloak allows you to map custom properties of the user. This allows you to specify a login with, e.g, `preferred_username` userinfo. In this case, you would fill out `Mapping for: Username` with that attribute returned in the userinfo JSON response. + +#### Step 7: Claims + +You can optionally request [claims](https://openid.net/specs/openid-connect-core-1_0-final.html#Claims) for both the id_token and userinfo endpoint. Keep in mind that currently only claims requested for the id_token returned with the authorize response are validated. That means that the authentication will fail if a requested essential claim is not returned. + +If you do not need Claims or are unaware of their use-cases, simply skip this step and click **Finish setup** . + +**Requesting MFA authentication via the ACR claim** + +Say for example that you want to request that the user authenticate using MFA (multi-factor authentication). You can do this by using the ACR (Authentication Context Class Reference) claim. + +This may look different for each identity provider. But if they follow, for instance the [EAP (Extended Authentication Profile)](https://openid.net/specs/openid-connect-eap-acr-values-1_0.html) then the claims would be `phr` (phishing-resistant) and ‘phrh’ (phishing-resistant hardware-protected). Others may simply have an additional claim called `Multi_Factor`. + +You have to check with your identity provider how these values must be called, as they vary from provider. + +In the following example we request a list of ACR values. One of which must be satisfied (i.e. returned in the ID token by the identity provider, meaning that the requested authentication mechanism was used) for the login in OpenProject to succeed. If none of the requested claims are present, authentication will fail. + +To specify these, you can provide a JSON. Use the following template as a starting point: + +``` +{ + "id_token": { + "acr": { + "essential": true, + "values": ["phr", "phrh", "Multi_Factor"] + } + } +} +``` + + + +**Non-essential claims** + +You may also request non-essential claims. In the example above this indicates that users should preferably be authenticated using those mechanisms but it’s not strictly required. The login into OpenProject will then work even if none of the claims are returned by the identity provider. + +**The acr_values option** + +For non-essential ACR claims you can also use the shorthand form of the option like this: + +``` +options = { ... } + +options["acr_values"] = "phr phrh Multi_Factor" +``` + +The option takes a space-separated list of ACR values. This is functionally the same as using the more complicated `claims` option above but with `"essential": false`. For all other claims there is no such shorthand. + +After entering Claims information, click **Finish setup** to complete the provider creation form. -By default, OpenProject will use the Microsoft Graph API endpoint to perform user info requests. -For that, you will need to enter the correct tenant identifier for your Azure instance. -To find the correct value for your instance, [please see this guide](https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc#find-your-apps-openid-configuration-document-uri). +![Bildschirmfoto 2024-11-06 um 18.34.28](./custom-provider-claims.png) -Once you filled out the form, hit save and the Azure provider has been created. -You can now log out, and see that the login form displays a badge for authenticating with Azure. If you click on that badge, you will be redirected to Azure to enter your credentials and allow the App to access your Azure profile, and you should then be automatically logged in. -Congratulations, your users can now authenticate using your Azure Active Directory! +### Additional custom configuration instructions for Okta -#### Tenant configuration +If you use Okta with OpenID Connect, use these configuration properties in the custom provider form: -Sometimes you may need to configure the `tenant` option for the AzureAD connection. -Currently this is not possible through the user interface. +- **Display name:** Okta +- **Client ID / Secret**: Values provided from Okta +- **Authorization endpoint**: `/oauth2/v1/authorize` +- **User information endpoint**: `/oauth2/v1/userinfo` +- **Token endpoint**: `/oauth2/v1/token` +- **End session endpoint**: `https://mypersonal.okta.com/oauth2/{authorizationServerId}/v1/logout` -But you can do it via the console as described [here](../../../installation-and-operations/misc/custom-openid-connect-providers/#custom-openid-connect-providers) where you can add `tenant` next to the other options like `host`, `identifier` and `secret`. -## Custom OpenID Connect Providers -You can still use an arbitrary provider. But for the time being there is no user interface yet for this. That means you will have to do it directly using the console on the server or via environment variables. +### Additional custom configuration instructions for Keycloak + +In Keycloak, use the following steps to set up an OIDC integration for OpenProject: + +- Select or create a realm you want to authenticate OpenProject with. Remember that realm identifier. For the remainder of this section, we're using REALM as the placeholder you'll need to replace. +- Under **Clients** menu, click *Create* or *Create client* +- **Add client**: Enter the following details + - **Client type / protocol**: OpenID Connect + - **Client ID**: `https://` + - **Name**: Choose any name, used only within Keycloak +- For the **Capability config**, keep Standard flow checked. In our tested version of Keycloak, this was the default. +- Click **Save** + +You will be forwarded to the settings tab of the new client. Change these settings: + +- Set **Valid redirect URIs** to `https:///auth/oidc-keycloak/*` +- Enable **Sign Documents** +- If you want to enable [Backchannel logout](https://openid.net/specs/openid-connect-backchannel-1_0.html), set **Backchannel logout URL** to `https:///auth/oidc-keycloak/backchannel-logout` + +Next, you will need to create or note down the client secret for that client. + +- Go to the **Credentials** tab +- Click the **copy to clipboard button** next to **Client secret** to copy that value + +**OPTIONAL:** By default, OpenProject will map the user's email to the login attribute in OpenProject. If you want to change that, you can do it by providing an alternate claim value in Keycloak: + +- Go to **Client scopes** +- Click the `https://-dedicated` scope +- Click **Add mapper** and **By configuration** +- Select **User property** +- Assuming you want to provide the username as `preferred_username` to OpenProject, set these values. This will depend on what attribute you want to map: + - Set name to `username` + - Set Token claim name to `preferred_username` +- Click **Save** + + + +#### Form values for OpenProject + +In OpenProject, create a custom provider as shown above using these parameters + +- **Display name:** Keycloak +- **Client ID / Secret**: Credentials shown above +- **Authorization endpoint**: `/oauth2/v1/authorize` +- **User information endpoint**: `/oauth2/v1/userinfo` +- **Token endpoint**: `/oauth2/v1/token` +- **End session endpoint**: `https://mypersonal.okta.com/oauth2/{authorizationServerId}/v1/logout` +- **OpenProject Redirect URI**: `https://openproject.example.com/auth/oidc-keycloak/callback` (Note that this URL depends on the display name above. See the UI for the actual Redirect URI) + + + + + +## Configuration using environment variables + +For some deployment scenarios, it might be desirable to configure a provider through environment variables. + +> [!WARNING] +> Only do this if you know what you are doing. Otherwise this may break your existing OpenID Connect authentication or cause other issues. + +The provider entries are defined dynamically based on the environment keys. All variables will start with the prefix +`OPENPROJECT_OPENID__CONNECT_` followed by the provider name. For instance an Okta example would +be defined via environment variables like this: + +```shell +OPENPROJECT_OPENID__CONNECT_OKTA_DISPLAY__NAME="Okta" +OPENPROJECT_OPENID__CONNECT_OKTA_HOST="mypersonal.okta.com" +OPENPROJECT_OPENID__CONNECT_OKTA_IDENTIFIER="" +# etc. +``` + +Underscores in option names must be escaped by doubling them. So make sure to really do use two consecutive underscores in `DISPLAY__NAME`, `TOKEN__ENDPOINT` and so forth + +Use the following configuration as a template for your configuration. + +> [!NOTE] +> +> Replace `KEYCLOAK` in the environment name with an alphanumeric identifier. This will become the slug in the redirect URI like follows: +> +> `https://openproject.example.com/auth/keycloak/callback` +> +> You can also see the actual redirect URI in the user interface after the provider has been successfully created from these environment variables. + + + +```bash +# The name of the login button in OpenProject, you can freely set this to anything you like +OPENPROJECT_OPENID__CONNECT_KEYCLOAK_DISPLAY__NAME="Keycloak" + +# The Client ID of OpenProject, usually the client host in Keycloak +OPENPROJECT_OPENID__CONNECT_KEYCLOAK_IDENTIFIER="https://" + +# The Client Secret used by OpenProject for your provider +OPENPROJECT_OPENID__CONNECT_KEYCLOAK_SECRET="" + +# The Issuer configuration for your provider +OPENPROJECT_OPENID__CONNECT_KEYCLOAK_ISSUER="https://keycloak.example.com/realms/" + +# Endpoints for Authorization, Token, Userinfo +OPENPROJECT_OPENID__CONNECT_KEYCLOAK_AUTHORIZATION__ENDPOINT="/realms//protocol/openid-connect/auth" +OPENPROJECT_OPENID__CONNECT_KEYCLOAK_TOKEN__ENDPOINT="/realms//protocol/openid-connect/token" +OPENPROJECT_OPENID__CONNECT_KEYCLOAK_USERINFO__ENDPOINT="/realms//protocol/openid-connect/userinfo" + +# Optional: endpoint to redirect users for logout +OPENPROJECT_OPENID__CONNECT_KEYCLOAK_END__SESSION__ENDPOINT="http://keycloak.example.com/realms//protocol/openid-connect/logout" + +# Host name of Keycloak, required if endpoint information are not absolute URLs +OPENPROJECT_OPENID__CONNECT_KEYCLOAK_HOST="" + +# Optional: Specify if non-standard port +OPENPROJECT_OPENID__CONNECT_KEYCLOAK_PORT="443" + +# Optional: Specify if not using https (only for development/testing purposes) +OPENPROJECT_OPENID__CONNECT_KEYCLOAK_SCHEME="https" + +# Optional: Where to redirect the user after a completed logout flow +OPENPROJECT_OPENID__CONNECT_LOCALKEYCLOAK_POST__LOGOUT__REDIRECT__URI="http://example.com" + +# Optional: if you have created the client scope mapper as shown above +OPENPROJECT_OPENID__CONNECT_KEYCLOAK_ATTRIBUTE__MAP_LOGIN="preferred_username" + +# Optional: Claim mapping using acr_value syntax +OPENPROJECT_OPENID__CONNECT_KEYCLOAK_ACR__VALUES="phr phrh Multi_Factor" + +# Optional: Claim mapping using JSON, see Step 7 above for more information on syntax +OPENPROJECT_OPENID__CONNECT_KEYCLOAK_CLAIMS="{\"id_token\":{\"acr\":{\"essential\":true,\"values\":[\"phr\",\"phrh\",\"Multi_Factor\"]}}}" +``` + + + +### Applying the configuration + +To apply the configuration after changes, you need to run the `db:seed` rake task. In all installations, this command is run automatically when you upgrade or install your application. Use the following commands based on your installation method: + +- **Packaged installation**: `sudo openproject run bundle exec rake db:seed` +- **Docker**: `docker exec -it bundle exec rake db:seed`. + -Please continue reading in the [Miscellaneous section of the Installation and Operations Guide](../../../installation-and-operations/misc/custom-openid-connect-providers/). ## Troubleshooting @@ -186,22 +440,25 @@ Q: After clicking on a provider badge, I am redirected to a signup form that say A: This can happen if you previously created user accounts in OpenProject with the same email than what is stored in the OpenID provider. In this case, if you want to allow existing users to be automatically remapped to the OpenID provider, you should do the following: -Spawn an interactive console in OpenProject. The following example shows the command for the packaged installation. -See [our process control guide](../../../installation-and-operations/operation/control/) for information on other installation types. +Spawn an interactive console in OpenProject. The following example shows the command for the packaged installation. See [our process control guide](https://github.com/opf/openproject/blob/dev/docs/installation-and-operations/operation/control) for information on other installation types. -```shell +``` sudo openproject run console # or if using docker: # docker-compose run --rm web bundle exec rails console ``` + + Once in the console you can then enter the following to enable the setting and leave the console. -```shell +``` Setting.oauth_allow_remapping_of_existing_users = true exit ``` + + Then, existing users should be able to log in using their Azure identity. Note that this works only if the user is using password-based authentication, and is not linked to any other authentication source (e.g. LDAP) or OpenID provider. Note that this setting is set to true by default for new installations already. diff --git a/docs/system-admin-guide/authentication/openid-providers/azure-display-name-tenant.png b/docs/system-admin-guide/authentication/openid-providers/azure-display-name-tenant.png new file mode 100644 index 000000000000..9bbb7a5495db Binary files /dev/null and b/docs/system-admin-guide/authentication/openid-providers/azure-display-name-tenant.png differ diff --git a/docs/system-admin-guide/authentication/openid-providers/azure-provider-index.png b/docs/system-admin-guide/authentication/openid-providers/azure-provider-index.png new file mode 100644 index 000000000000..5908679a2964 Binary files /dev/null and b/docs/system-admin-guide/authentication/openid-providers/azure-provider-index.png differ diff --git a/docs/system-admin-guide/authentication/openid-providers/custom-provider-advanced-config.png b/docs/system-admin-guide/authentication/openid-providers/custom-provider-advanced-config.png new file mode 100644 index 000000000000..e7d477a353c9 Binary files /dev/null and b/docs/system-admin-guide/authentication/openid-providers/custom-provider-advanced-config.png differ diff --git a/docs/system-admin-guide/authentication/openid-providers/custom-provider-claims.png b/docs/system-admin-guide/authentication/openid-providers/custom-provider-claims.png new file mode 100644 index 000000000000..0178b49510b6 Binary files /dev/null and b/docs/system-admin-guide/authentication/openid-providers/custom-provider-claims.png differ diff --git a/docs/system-admin-guide/authentication/openid-providers/custom-provider-metadata-discovery.png b/docs/system-admin-guide/authentication/openid-providers/custom-provider-metadata-discovery.png new file mode 100644 index 000000000000..134426d9f556 Binary files /dev/null and b/docs/system-admin-guide/authentication/openid-providers/custom-provider-metadata-discovery.png differ diff --git a/docs/system-admin-guide/authentication/openid-providers/oidc-index-page.png b/docs/system-admin-guide/authentication/openid-providers/oidc-index-page.png new file mode 100644 index 000000000000..09c0e4432211 Binary files /dev/null and b/docs/system-admin-guide/authentication/openid-providers/oidc-index-page.png differ diff --git a/docs/system-admin-guide/authentication/openid-providers/openproject_system-admin-guide_authentication_openid_provider_empty.png b/docs/system-admin-guide/authentication/openid-providers/openproject_system-admin-guide_authentication_openid_provider_empty.png new file mode 100644 index 000000000000..57a9adaf5b34 Binary files /dev/null and b/docs/system-admin-guide/authentication/openid-providers/openproject_system-admin-guide_authentication_openid_provider_empty.png differ diff --git a/docs/system-admin-guide/authentication/openid-providers/openproject_system-admin-guide_authentication_openid_provider_new_google.png b/docs/system-admin-guide/authentication/openid-providers/openproject_system-admin-guide_authentication_openid_provider_new_google.png index 36301c3ff739..fece608b05a1 100644 Binary files a/docs/system-admin-guide/authentication/openid-providers/openproject_system-admin-guide_authentication_openid_provider_new_google.png and b/docs/system-admin-guide/authentication/openid-providers/openproject_system-admin-guide_authentication_openid_provider_new_google.png differ diff --git a/docs/system-admin-guide/authentication/openid-providers/openproject_system-admin-guide_authentication_openid_provider_new_google_successful_message.png b/docs/system-admin-guide/authentication/openid-providers/openproject_system-admin-guide_authentication_openid_provider_new_google_successful_message.png deleted file mode 100644 index 3ac418daf544..000000000000 Binary files a/docs/system-admin-guide/authentication/openid-providers/openproject_system-admin-guide_authentication_openid_provider_new_google_successful_message.png and /dev/null differ diff --git a/docs/system-admin-guide/authentication/openid-providers/openproject_system-admin-guide_authentication_openid_provider_saved_google.png b/docs/system-admin-guide/authentication/openid-providers/openproject_system-admin-guide_authentication_openid_provider_saved_google.png new file mode 100644 index 000000000000..9f2bb67a2a36 Binary files /dev/null and b/docs/system-admin-guide/authentication/openid-providers/openproject_system-admin-guide_authentication_openid_provider_saved_google.png differ diff --git a/frontend/src/app/core/turbo/turbo-requests.service.ts b/frontend/src/app/core/turbo/turbo-requests.service.ts index f5dfb7364c0d..23bae83a6c90 100644 --- a/frontend/src/app/core/turbo/turbo-requests.service.ts +++ b/frontend/src/app/core/turbo/turbo-requests.service.ts @@ -10,24 +10,28 @@ export class TurboRequestsService { } - public request(url:string, init:RequestInit = {}):Promise { + public request(url:string, init:RequestInit = {}):Promise<{ html:string, headers:Headers }> { return fetch(url, init) .then((response) => { if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } - - return response.text(); + return response.text().then((html) => ({ + html, + headers: response.headers, + })); }) - .then((html) => { - renderStreamMessage(html); - - return html; // enable further processing wherever this is called - }) - .catch((error) => this.toast.addError(error as string)); + .then((result) => { + renderStreamMessage(result.html); + return result; + }) + .catch((error) => { + this.toast.addError(error as string); + throw error; + }); } - public requestStream(url:string):Promise { + public requestStream(url:string):Promise<{ html:string, headers:Headers }> { return this.request(url, { method: 'GET', headers: { Accept: 'text/vnd.turbo-stream.html' }, diff --git a/frontend/src/app/features/homescreen/blocks/new-features.component.ts b/frontend/src/app/features/homescreen/blocks/new-features.component.ts index f0f27efcd798..3b0847db4439 100644 --- a/frontend/src/app/features/homescreen/blocks/new-features.component.ts +++ b/frontend/src/app/features/homescreen/blocks/new-features.component.ts @@ -33,9 +33,9 @@ import { I18nService } from 'core-app/core/i18n/i18n.service'; import { imagePath } from 'core-app/shared/helpers/images/path-helper'; // The key used in the I18n files to distinguish between versions. -const OpVersionI18n = '14_6'; +const OpVersionI18n = '15_0'; -const OpReleaseURL = 'https://www.openproject.org/docs/release-notes/14-6-0/'; +const OpReleaseURL = 'https://www.openproject.org/docs/release-notes/15-0-0/'; /** Update the teaser image to the next version */ const featureTeaserImage = `${OpVersionI18n}_features.svg`; diff --git a/frontend/src/assets/images/14_6_features.svg b/frontend/src/assets/images/15_0_features.svg similarity index 100% rename from frontend/src/assets/images/14_6_features.svg rename to frontend/src/assets/images/15_0_features.svg diff --git a/frontend/src/stimulus/controllers/dynamic/work-packages/activities-tab/index.controller.ts b/frontend/src/stimulus/controllers/dynamic/work-packages/activities-tab/index.controller.ts index 80c8305a3165..8393c8eec323 100644 --- a/frontend/src/stimulus/controllers/dynamic/work-packages/activities-tab/index.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/work-packages/activities-tab/index.controller.ts @@ -20,6 +20,7 @@ export default class IndexController extends Controller { userId: Number, workPackageId: Number, notificationCenterPathName: String, + lastServerTimestamp: String, }; static targets = ['journalsContainer', 'buttonRow', 'formRow', 'form', 'reactionButton']; @@ -32,7 +33,7 @@ export default class IndexController extends Controller { declare updateStreamsUrlValue:string; declare sortingValue:string; - declare lastUpdateTimestamp:string; + declare lastServerTimestampValue:string; declare intervallId:number; declare pollingIntervalInMsValue:number; declare notificationCenterPathNameValue:string; @@ -62,7 +63,6 @@ export default class IndexController extends Controller { this.apiV3Service = context.services.apiV3Service; this.setLocalStorageKey(); - this.setLastUpdateTimestamp(); this.setupEventListeners(); this.handleInitialScroll(); this.startPolling(); @@ -166,8 +166,8 @@ export default class IndexController extends Controller { const journalsContainerAtBottom = this.isJournalsContainerScrolledToBottom(); void this.performUpdateStreamsRequest(this.prepareUpdateStreamsUrl()) - .then((html) => { - this.handleUpdateStreamsResponse(html as string, journalsContainerAtBottom); + .then(({ html, headers }) => { + this.handleUpdateStreamsResponse(html, headers, journalsContainerAtBottom); }).catch((error) => { console.error('Error updating activities list:', error); }).finally(() => { @@ -183,11 +183,11 @@ export default class IndexController extends Controller { const url = new URL(this.updateStreamsUrlValue); url.searchParams.set('sortBy', this.sortingValue); url.searchParams.set('filter', this.filterValue); - url.searchParams.set('last_update_timestamp', this.lastUpdateTimestamp); + url.searchParams.set('last_update_timestamp', this.lastServerTimestampValue); return url.toString(); } - private performUpdateStreamsRequest(url:string):Promise { + private performUpdateStreamsRequest(url:string):Promise<{ html:string, headers:Headers }> { return this.turboRequests.request(url, { method: 'GET', headers: { @@ -196,8 +196,10 @@ export default class IndexController extends Controller { }); } - private handleUpdateStreamsResponse(html:string, journalsContainerAtBottom:boolean) { - this.setLastUpdateTimestamp(); + private handleUpdateStreamsResponse(html:string, lastResponseHeaders:Headers, journalsContainerAtBottom:boolean) { + // extract server timestamp from response headers in order to be in sync with the server + this.setLastServerTimestampViaHeaders(lastResponseHeaders); + this.checkForAndHandleWorkPackageUpdate(html); this.checkForNewNotifications(html); this.performAutoScrolling(html, journalsContainerAtBottom); @@ -426,6 +428,8 @@ export default class IndexController extends Controller { // scroll to (new) bottom if sorting is ascending and journals container was already at bottom before showing the form this.scrollJournalContainer(true); this.focusEditor(); + } else { + this.focusEditor(); } } @@ -515,8 +519,8 @@ export default class IndexController extends Controller { const formData = this.prepareFormData(); void this.submitForm(formData) - .then(() => { - this.handleSuccessfulSubmission(); + .then(({ html, headers }) => { + this.handleSuccessfulSubmission(html, headers); }) .catch((error) => { console.error('Error saving activity:', error); @@ -531,14 +535,14 @@ export default class IndexController extends Controller { const data = ckEditorInstance ? ckEditorInstance.getData({ trim: false }) : ''; const formData = new FormData(this.formTarget); - formData.append('last_update_timestamp', this.lastUpdateTimestamp); + formData.append('last_update_timestamp', this.lastServerTimestampValue); formData.append('filter', this.filterValue); formData.append('journal[notes]', data); return formData; } - private async submitForm(formData:FormData):Promise { + private async submitForm(formData:FormData):Promise<{ html:string, headers:Headers }> { return this.turboRequests.request(this.formTarget.action, { method: 'POST', body: formData, @@ -548,8 +552,9 @@ export default class IndexController extends Controller { }); } - private handleSuccessfulSubmission() { - this.setLastUpdateTimestamp(); + private handleSuccessfulSubmission(html:string, headers:Headers) { + // extract server timestamp from response headers in order to be in sync with the server + this.setLastServerTimestampViaHeaders(headers); if (!this.journalsContainerTarget) return; @@ -588,7 +593,9 @@ export default class IndexController extends Controller { this.journalsContainerTarget.classList.add('work-packages-activities-tab-index-component--journals-container_with-input-compensation'); } - setLastUpdateTimestamp() { - this.lastUpdateTimestamp = new Date().toISOString(); + private setLastServerTimestampViaHeaders(headers:Headers) { + if (headers.has('X-Server-Timestamp')) { + this.lastServerTimestampValue = headers.get('X-Server-Timestamp') as string; + } } } diff --git a/modules/auth_saml/app/services/saml/sync_service.rb b/modules/auth_saml/app/services/saml/sync_service.rb index ed7217fb8d6c..e6af871783bb 100644 --- a/modules/auth_saml/app/services/saml/sync_service.rb +++ b/modules/auth_saml/app/services/saml/sync_service.rb @@ -30,11 +30,12 @@ module Saml ## # Synchronize a configuration from ENV or legacy settings to a SAML provider record class SyncService - attr_reader :name, :configuration + attr_reader :name, :configuration, :contract_class - def initialize(name, configuration) + def initialize(name, configuration, contract_class: nil) @name = name @configuration = configuration + @contract_class = contract_class end def call @@ -52,7 +53,7 @@ def call def create(name, params) ::Saml::Providers::CreateService - .new(user: User.system) + .new(user: User.system, contract_class:) .call(params) .on_success { |call| call.message = "Successfully saved SAML provider #{name}." } .on_failure { |call| call.message = "Failed to create SAML provider: #{call.message}" } @@ -60,7 +61,7 @@ def create(name, params) def update(name, provider, params) ::Saml::Providers::UpdateService - .new(model: provider, user: User.system) + .new(model: provider, user: User.system, contract_class:) .call(params) .on_success { |call| call.message = "Successfully updated SAML provider #{name}." } .on_failure { |call| call.message = "Failed to update SAML provider: #{call.message}" } diff --git a/modules/auth_saml/db/migrate/20240821121856_migrate_saml_settings_to_providers.rb b/modules/auth_saml/db/migrate/20240821121856_migrate_saml_settings_to_providers.rb index 0510f6c8cba5..6fa104cad073 100644 --- a/modules/auth_saml/db/migrate/20240821121856_migrate_saml_settings_to_providers.rb +++ b/modules/auth_saml/db/migrate/20240821121856_migrate_saml_settings_to_providers.rb @@ -20,7 +20,7 @@ def down def migrate_provider!(name, options) puts "Trying to migrate SAML provider #{name} from previous settings format..." - call = ::Saml::SyncService.new(name, options).call + call = ::Saml::SyncService.new(name, options, contract_class: EmptyContract).call if call.success puts <<~SUCCESS diff --git a/modules/avatars/config/locales/crowdin/ro.yml b/modules/avatars/config/locales/crowdin/ro.yml index ed29cdfdaa11..38d2cd203c9e 100644 --- a/modules/avatars/config/locales/crowdin/ro.yml +++ b/modules/avatars/config/locales/crowdin/ro.yml @@ -24,13 +24,13 @@ ro: text_current_avatar: | Următoarea imagine prezintă avatarul actual. text_upload_instructions: | - Încărcați propriul avatar personalizat de 128 pe 128 pixeli. Fișierele mai mari vor fi redimensionate și decupate pentru a se potrivi. - O previzualizare a avatarului dvs. va fi afișată înainte de a fi încărcat, după ce ați selectat o imagine. + Încarcă propriul avatar personalizat de 128 pe 128 pixeli. Fișierele mai mari vor fi redimensionate și decupate pentru a se potrivi. + O previzualizare a avatarului tău va fi afișată înainte de a fi încărcat, după ce ai selectat o imagine. text_change_gravatar_html: 'Pentru a schimba sau adăuga Gravatar pentru adresa dvs. de e-mail, mergeți la %{gravatar_url}.' text_your_local_avatar: | OpenProject vă permite să vă încărcați propriul avatar personalizat. text_local_avatar_over_gravatar: | - Dacă setați unul, acest avatar personalizat este folosit cu prioritate față de gravatar-ul de mai sus. + Dacă setezi unul, acest avatar personalizat este folosit cu prioritate față de gravatar-ul de mai sus. text_your_current_gravatar: | OpenProject folosește gravatar-ul dvs. dacă ați înregistrat unul, sau o imagine sau o pictogramă implicită, dacă există una. Gravatar-ul actual este următorul: diff --git a/modules/bim/config/locales/crowdin/ro.yml b/modules/bim/config/locales/crowdin/ro.yml index 95ee5f8e3eeb..16677886f798 100644 --- a/modules/bim/config/locales/crowdin/ro.yml +++ b/modules/bim/config/locales/crowdin/ro.yml @@ -47,9 +47,9 @@ ro: unknown_emails_found: 'Adrese de e-mail necunoscute găsite' unknown_property: 'Proprietate necunoscută' non_members_found: 'Membrii non-proiect găsiți' - import_types_as: 'Setați toate aceste tipuri la' - import_statuses_as: 'Setați toate aceste statusuri la' - import_priorities_as: 'Setați toate aceste priorități pentru' + import_types_as: 'Setează toate aceste tipuri la' + import_statuses_as: 'Setează toate aceste statusuri la' + import_priorities_as: 'Setează toate aceste priorități pentru' invite_as_members_with_role: 'Invitați-i ca membri în proiectul "%{project}" cu rolul' add_as_members_with_role: 'Adăugă-i ca membri în proiectul "%{project}" cu rolul' no_type_provided: 'Nici un tip furnizat' diff --git a/modules/boards/config/locales/crowdin/js-ro.yml b/modules/boards/config/locales/crowdin/js-ro.yml index 48ba5a8a374b..1d9dbc15c521 100644 --- a/modules/boards/config/locales/crowdin/js-ro.yml +++ b/modules/boards/config/locales/crowdin/js-ro.yml @@ -30,7 +30,7 @@ ro: error_permission_missing: "Lipsește permisiunea de a crea interogări publice" error_cannot_move_into_self: "Nu puteți muta un pachet de lucru în coloana sa proprie." text_hidden_list_warning: "Nu toate listele sunt afișate deoarece nu aveți permisiunea. Contactați administratorul pentru mai multe informații." - click_to_remove_list: "Fă clic pentru a elimina această listă" + click_to_remove_list: "Clic pentru a elimina această listă" board_type: text: 'Tipul de placă' free: 'De baza' diff --git a/modules/grids/config/locales/crowdin/js-ro.yml b/modules/grids/config/locales/crowdin/js-ro.yml index 79b759e40073..13bd60c08616 100644 --- a/modules/grids/config/locales/crowdin/js-ro.yml +++ b/modules/grids/config/locales/crowdin/js-ro.yml @@ -41,7 +41,7 @@ ro: no_results: 'Cu subproiecte' project_favorites: title: 'Proiecte favorite' - no_results: 'Nu ai niciun proiect favorit. Fă clic pe pictograma cu steluță din tabloul de bord al proiectului pentru a adăuga unul la favorite.' + no_results: 'Nu ai niciun proiect favorit. Clic pe pictograma cu steluță din tabloul de bord al proiectului pentru a adăuga unul la favorite.' time_entries_current_user: title: 'Timpul meu petrecut' displayed_days: 'Zile afișate în lista de activități a proiectului' diff --git a/modules/openid_connect/app/components/openid_connect/providers/row_component.rb b/modules/openid_connect/app/components/openid_connect/providers/row_component.rb index 0eae5bfaabcf..c7e87dc5c26a 100644 --- a/modules/openid_connect/app/components/openid_connect/providers/row_component.rb +++ b/modules/openid_connect/app/components/openid_connect/providers/row_component.rb @@ -14,19 +14,24 @@ def column_args(column) end def name - link = render( + concat(provider_name) + unless provider.configured? + concat(incomplete_label) + end + end + + def provider_name + render( Primer::Beta::Link.new( - href: url_for(action: :edit, id: provider.id), + href: url_for(action: :show, id: provider.id), font_weight: :bold, mr: 1 ) ) { provider.display_name } - if !provider.configured? - link.concat( - render(Primer::Beta::Label.new(scheme: :attention)) { I18n.t(:label_incomplete) } - ) - end - link + end + + def incomplete_label + render(Primer::Beta::Label.new(scheme: :attention)) { I18n.t(:label_incomplete) } end def type diff --git a/modules/openid_connect/app/components/openid_connect/providers/sections/form_component.html.erb b/modules/openid_connect/app/components/openid_connect/providers/sections/form_component.html.erb index 5339d40ecdc1..677ed8c0b0fa 100644 --- a/modules/openid_connect/app/components/openid_connect/providers/sections/form_component.html.erb +++ b/modules/openid_connect/app/components/openid_connect/providers/sections/form_component.html.erb @@ -3,8 +3,7 @@ id: "openid-connect-providers-edit-form", model: provider, url:, - method: form_method, - data: { turbo: true, turbo_stream: true } + method: form_method ) do |form| flex_layout do |flex| if @heading diff --git a/modules/openid_connect/app/components/openid_connect/providers/side_panel/information_component.html.erb b/modules/openid_connect/app/components/openid_connect/providers/side_panel/information_component.html.erb new file mode 100644 index 000000000000..cc4a09471cab --- /dev/null +++ b/modules/openid_connect/app/components/openid_connect/providers/side_panel/information_component.html.erb @@ -0,0 +1,24 @@ +<%= + render(Primer::OpenProject::SidePanel::Section.new) do |section| + section.with_title { I18n.t("saml.providers.label_openproject_information") } + section.with_description { I18n.t("openid_connect.instructions.redirect_url") } + + component_collection do |collection| + collection.with_component(Primer::Beta::Heading.new(tag: :h5, mb: 1)) do + I18n.t("activemodel.attributes.openid_connect/provider.slug") + end + + collection.with_component( + OpPrimer::CopyToClipboardComponent.new(provider.slug, scheme: :input) + ) + + collection.with_component(Primer::Beta::Heading.new(tag: :h5, mt: 4, mb: 1)) do + I18n.t("activemodel.attributes.openid_connect/provider.redirect_url") + end + + collection.with_component( + OpPrimer::CopyToClipboardComponent.new(provider.callback_url, scheme: :input) + ) + end + end +%> diff --git a/app/models/queries/versions/orders/semver_name_order.rb b/modules/openid_connect/app/components/openid_connect/providers/side_panel/information_component.rb similarity index 78% rename from app/models/queries/versions/orders/semver_name_order.rb rename to modules/openid_connect/app/components/openid_connect/providers/side_panel/information_component.rb index 465429fb6bb0..610e01b3e969 100644 --- a/app/models/queries/versions/orders/semver_name_order.rb +++ b/modules/openid_connect/app/components/openid_connect/providers/side_panel/information_component.rb @@ -1,6 +1,6 @@ #-- copyright # OpenProject is an open source project management software. -# Copyright (C) the OpenProject GmbH +# Copyright (C) 2012-2024 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. @@ -26,22 +26,14 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class Queries::Versions::Orders::SemverNameOrder < Queries::Orders::Base - self.model = Version +module OpenIDConnect::Providers + module SidePanel + class InformationComponent < ApplicationComponent + include ApplicationHelper + include OpTurbo::Streamable + include OpPrimer::ComponentHelpers - def self.key - :semver_name - end - - private - - def order(scope) - ordered = scope.order_by_semver_name - - if direction == :desc - ordered = ordered.reverse_order + alias_method :provider, :model end - - ordered end end diff --git a/modules/openid_connect/app/components/openid_connect/providers/side_panel_component.html.erb b/modules/openid_connect/app/components/openid_connect/providers/side_panel_component.html.erb new file mode 100644 index 000000000000..b05772b94cb4 --- /dev/null +++ b/modules/openid_connect/app/components/openid_connect/providers/side_panel_component.html.erb @@ -0,0 +1,11 @@ +<%= + component_wrapper do + render(Primer::OpenProject::SidePanel.new(spacious: true)) do |panel| + [ + OpenIDConnect::Providers::SidePanel::InformationComponent.new(@provider), + ].each do |component| + panel.with_section(component) + end + end + end +%> diff --git a/modules/openid_connect/app/components/openid_connect/providers/side_panel_component.rb b/modules/openid_connect/app/components/openid_connect/providers/side_panel_component.rb new file mode 100644 index 000000000000..5764ea920d5a --- /dev/null +++ b/modules/openid_connect/app/components/openid_connect/providers/side_panel_component.rb @@ -0,0 +1,43 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 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 OpenIDConnect + module Providers + class SidePanelComponent < ApplicationComponent + include ApplicationHelper + include OpTurbo::Streamable + include OpPrimer::ComponentHelpers + + def initialize(provider) + super() + + @provider = provider + end + end + end +end diff --git a/modules/openid_connect/app/controllers/openid_connect/providers_controller.rb b/modules/openid_connect/app/controllers/openid_connect/providers_controller.rb index e2477b25b1ef..5433718cd63d 100644 --- a/modules/openid_connect/app/controllers/openid_connect/providers_controller.rb +++ b/modules/openid_connect/app/controllers/openid_connect/providers_controller.rb @@ -7,13 +7,15 @@ class ProvidersController < ::ApplicationController before_action :require_admin before_action :check_ee - before_action :find_provider, only: %i[edit update confirm_destroy destroy] + before_action :find_provider, only: %i[edit show update confirm_destroy destroy] before_action :set_edit_state, only: %i[create edit update] def index @providers = ::OpenIDConnect::Provider.all end + def show; end + def new oidc_provider = case params[:oidc_provider] when "google" @@ -44,7 +46,20 @@ def create end end - def edit; end + def edit + respond_to do |format| + format.turbo_stream do + component = OpenIDConnect::Providers::ViewComponent.new(@provider, + view_mode: :edit, + edit_mode: @edit_mode, + edit_state: @edit_state) + update_via_turbo_stream(component:) + scroll_into_view_via_turbo_stream("openid-connect-providers-edit-form", behavior: :instant) + render turbo_stream: turbo_streams + end + format.html + end + end def update update_params = params @@ -59,7 +74,7 @@ def update successful_save_response else @provider = call.result - failed_save_response(edit) + failed_save_response(:edit) end end diff --git a/modules/openid_connect/app/views/openid_connect/providers/edit.html.erb b/modules/openid_connect/app/views/openid_connect/providers/edit.html.erb index 186c079beb80..bc753198ad44 100644 --- a/modules/openid_connect/app/views/openid_connect/providers/edit.html.erb +++ b/modules/openid_connect/app/views/openid_connect/providers/edit.html.erb @@ -26,7 +26,21 @@ end %> -<%= render(OpenIDConnect::Providers::ViewComponent.new(@provider, - view_mode: :edit, - edit_mode: @edit_mode, - edit_state: @edit_state)) %> +<%= + render(Primer::Alpha::Layout.new(stacking_breakpoint: :md)) do |content| + content.with_main do + render(OpenIDConnect::Providers::ViewComponent.new(@provider, + view_mode: :edit, + edit_mode: @edit_mode, + edit_state: @edit_state)) + end + + if @provider.persisted? + content.with_sidebar(row_placement: :start, col_placement: :end) do + render(OpenIDConnect::Providers::SidePanelComponent.new(@provider)) + end + end + end +%> + +<%= %> diff --git a/modules/openid_connect/app/views/openid_connect/providers/show.html.erb b/modules/openid_connect/app/views/openid_connect/providers/show.html.erb new file mode 100644 index 000000000000..846ccd519fe6 --- /dev/null +++ b/modules/openid_connect/app/views/openid_connect/providers/show.html.erb @@ -0,0 +1,38 @@ +<% html_title(t(:label_administration), t('openid_connect.providers.plural'), @provider.display_name) -%> + +<% html_title(t(:label_administration), page_title) -%> + +<%= + render Primer::OpenProject::PageHeader.new do |header| + header.with_title { @provider.display_name } + header.with_breadcrumbs([{ href: admin_index_path, text: t(:label_administration) }, + { href: admin_settings_authentication_path, text: t(:label_authentication) }, + { href: openid_connect_providers_path, text: t("openid_connect.providers.plural") }, + @provider.display_name]) + header.with_action_button( + tag: :a, + scheme: :danger, + mobile_icon: :trash, + mobile_label: t(:button_delete), + size: :medium, + href: confirm_destroy_openid_connect_provider_path(@provider), + aria: { label: I18n.t(:button_delete) }, + title: I18n.t(:button_delete) + ) do |button| + button.with_leading_visual_icon(icon: :trash) + t(:button_delete) + end + end +%> + +<%= + render(Primer::Alpha::Layout.new(stacking_breakpoint: :md)) do |content| + content.with_main do + render OpenIDConnect::Providers::ViewComponent.new(@provider, view_mode: :show) + end + + content.with_sidebar(row_placement: :start, col_placement: :end) do + render OpenIDConnect::Providers::SidePanelComponent.new(@provider) + end + end +%> diff --git a/modules/openid_connect/config/locales/en.yml b/modules/openid_connect/config/locales/en.yml index 76689750d8c5..d6bafddb14f1 100644 --- a/modules/openid_connect/config/locales/en.yml +++ b/modules/openid_connect/config/locales/en.yml @@ -9,6 +9,7 @@ en: attributes: openid_connect/provider: name: Name + slug: Unique identifier display_name: Display name client_id: Client ID client_secret: Client secret @@ -27,6 +28,7 @@ en: icon: Custom icon claims: Claims acr_values: ACR values + redirect_url: Redirect URL activerecord: errors: models: @@ -55,6 +57,7 @@ en: delete_title: "Delete OpenID Connect provider" instructions: + redirect_url: This is the redirect URL that the OpenID Connect provider should use to redirect back to OpenProject after a successful login. endpoint_url: The endpoint URL given to you by the OpenID Connect provider metadata_none: I don't have this information metadata_url: I have a discovery endpoint URL diff --git a/modules/openid_connect/config/routes.rb b/modules/openid_connect/config/routes.rb index aa31fbcf26f3..110a5b553d6a 100644 --- a/modules/openid_connect/config/routes.rb +++ b/modules/openid_connect/config/routes.rb @@ -3,7 +3,7 @@ scope :admin do namespace :openid_connect do - resources :providers, except: %i[show] do + resources :providers do get :confirm_destroy, on: :member end end diff --git a/modules/two_factor_authentication/config/locales/crowdin/ro.yml b/modules/two_factor_authentication/config/locales/crowdin/ro.yml index 64dfc95a3038..cbe0b0ab98b8 100644 --- a/modules/two_factor_authentication/config/locales/crowdin/ro.yml +++ b/modules/two_factor_authentication/config/locales/crowdin/ro.yml @@ -60,9 +60,9 @@ ro: text_configuration: | Notă: Aceste valori reprezintă configurația curentă la nivelul întregii aplicații. Nu puteți dezactiva setările impuse de configurație sau modifica strategiile active curente, deoarece acestea necesită o repornire a serverului. text_configuration_guide: Pentru mai multe informații, consultați ghidul de configurare. - text_enforced: "Activați această setare pentru a forța toți utilizatorii să înregistreze un dispozitiv 2FA la următoarea autentificare. Poate fi dezactivată numai atunci când nu este impusă prin configurare." + text_enforced: "Activează această setare pentru a forța toți utilizatorii să înregistreze un dispozitiv 2FA la următoarea autentificare. Poate fi dezactivată numai atunci când nu este impusă prin configurare." text_remember: | - Setați această valoare la mai mare decât zero pentru a permite utilizatorilor să își amintească autentificarea 2FA pentru numărul de zile dat. + Setează această valoare mai mare decât zero pentru a permite utilizatorilor să își amintească autentificarea 2FA pentru numărul de zile dat. Nu li se va cere să o introducă din nou în această perioadă. Poate fi setat numai atunci când nu este impus prin configurare. error_invalid_settings: "Strategiile 2FA pe care le-ați selectat nu sunt valide" failed_to_save_settings: "Nu s-a reușit actualizarea setărilor 2FA: %{message}" @@ -161,7 +161,7 @@ ro: Contul dvs. are un modul cookie de memorare activ, valabil până la %{expires_on}. Acest modul cookie vă permite să vă conectați fără un al doilea factor la contul dvs. până la acel moment. other_active_session_notice: Contul dvs. are un modul cookie de reamintire activ într-o altă sesiune. label: "Reține autentificarea" - clear_cookie: "Faceți clic aici pentru a elimina toate sesiunile 2FA reținute." + clear_cookie: "Clic aici pentru a elimina toate sesiunile 2FA reținute." cookie_removed: "Toate sesiunile 2FA memorate au fost eliminate." dont_ask_again: "Creați un modul cookie pentru a reține autentificarea 2FA pe acest client timp de %{days} zile." field_phone: "Telefon mobil" @@ -177,9 +177,9 @@ ro: button_continue: "Continuă" button_make_default: "Marcați ca implicit" label_unverified_phone: "Telefonul mobil nu a fost încă verificat" - notice_phone_number_format: "Vă rugăm să introduceți numărul în următorul format: +XX XXXXXXXX." + notice_phone_number_format: "Te rog să introduci numărul în următorul format: +XX XXXXXXXX." text_otp_not_receive: "Alte metode de verificare" - text_send_otp_again: "Reîntoarceți parola unică prin:" + text_send_otp_again: "Retrimite parola unică prin:" button_resend_otp_form: "Retrimitere" button_otp_by_voice: "Apel vocal" button_otp_by_sms: "SMS" diff --git a/modules/webhooks/config/locales/crowdin/ro.yml b/modules/webhooks/config/locales/crowdin/ro.yml index eba92dedef14..b1a692b74937 100644 --- a/modules/webhooks/config/locales/crowdin/ro.yml +++ b/modules/webhooks/config/locales/crowdin/ro.yml @@ -38,7 +38,7 @@ ro: enabled: 'Webhook este activat' disabled: 'Webhook este dezactivat' enabled_text: 'Webhook va emite sarcini utile pentru evenimentele definite mai jos.' - disabled_text: 'Fă clic pe butonul de editare pentru a activa webhook-ul.' + disabled_text: 'Clic pe butonul de editare pentru a activa webhook-ul.' deliveries: no_results_table: Nu au fost efectuate livrări pentru acest webhook în ultimele zile. title: 'Livrări recente' diff --git a/spec/features/activities/work_package/activities_spec.rb b/spec/features/activities/work_package/activities_spec.rb index e5968834e576..b8ced544dd82 100644 --- a/spec/features/activities/work_package/activities_spec.rb +++ b/spec/features/activities/work_package/activities_spec.rb @@ -540,6 +540,30 @@ end end + describe "focus editor" do + current_user { admin } + let(:work_package) { create(:work_package, project:, author: admin) } + + before do + wp_page.visit! + wp_page.wait_for_activity_tab + end + + it "focuses the editor", :aggregate_failures do + activity_tab.set_journal_sorting(:desc) + + activity_tab.open_new_comment_editor + + activity_tab.expect_focus_on_editor + + activity_tab.set_journal_sorting(:asc) + + activity_tab.open_new_comment_editor + + activity_tab.expect_focus_on_editor + end + end + describe "sorting" do current_user { admin } let(:work_package) { create(:work_package, project:, author: admin) } diff --git a/spec/models/queries/versions/version_query_spec.rb b/spec/models/queries/versions/version_query_spec.rb new file mode 100644 index 000000000000..02b5751d29af --- /dev/null +++ b/spec/models/queries/versions/version_query_spec.rb @@ -0,0 +1,150 @@ +#-- 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" + +RSpec.describe Queries::Versions::VersionQuery do + def instance = subject + + describe "ordering" do + before do + allow(OpenProject::Deprecation).to receive(:warn) + + instance.order(attribute => direction) if attribute + end + + describe "without it" do + let(:attribute) { nil } + + it "orders by id descending" do + expected = Version.order(id: :desc) + + expect(instance.results.to_sql).to eql expected.to_sql + end + end + + describe "unknown order" do + let(:attribute) { :unknown } + let(:direction) { :asc } + + it "returns a query not returning anything" do + expected = Version.where(Arel::Nodes::Equality.new(1, 0)) + + expect(instance.results.to_sql).to eql expected.to_sql + end + + it { is_expected.not_to be_valid } + end + + describe "by id" do + let(:attribute) { :id } + + describe "ascending" do + let(:direction) { :asc } + + it "is the same as handwriting the query" do + expected = Version.order(id: :asc) + + expect(instance.results.to_sql).to eql expected.to_sql + end + end + + describe "descending" do + let(:direction) { :desc } + + it "is the same as handwriting the query" do + expected = Version.order(id: :desc) + + expect(instance.results.to_sql).to eql expected.to_sql + end + end + end + + describe "by name" do + let(:attribute) { :name } + + describe "ascending" do + let(:direction) { :asc } + + it "is the same as handwriting the query" do + expected = Version.order(name: :asc).order(id: :desc) + + expect(instance.results.to_sql).to eql expected.to_sql + end + end + + describe "descending" do + let(:direction) { :desc } + + it "is the same as handwriting the query" do + expected = Version.order(name: :desc).order(id: :desc) + + expect(instance.results.to_sql).to eql expected.to_sql + end + end + end + + describe "by semver_name" do + let(:attribute) { :semver_name } + + describe "ascending" do + let(:direction) { :asc } + + it "is the same as handwriting the query" do + expected = Version.order(name: :asc).order(id: :desc) + + expect(instance.results.to_sql).to eql expected.to_sql + end + + it "warns about being deprecated" do + instance.results + + expect(OpenProject::Deprecation) + .to have_received(:warn).with("Sorting by semver_name is deprecated, name should be used instead") + end + end + + describe "descending" do + let(:direction) { :desc } + + it "is the same as handwriting the query" do + expected = Version.order(name: :desc).order(id: :desc) + + expect(instance.results.to_sql).to eql expected.to_sql + end + + it "warns about being deprecated" do + instance.results + + expect(OpenProject::Deprecation) + .to have_received(:warn).with("Sorting by semver_name is deprecated, name should be used instead") + end + end + end + end +end diff --git a/spec/models/query/results_version_integration_spec.rb b/spec/models/query/results_version_integration_spec.rb index e06e3f5f4b3a..70a434630692 100644 --- a/spec/models/query/results_version_integration_spec.rb +++ b/spec/models/query/results_version_integration_spec.rb @@ -102,7 +102,7 @@ end end let(:work_packages_asc) { [old_version_wp, no_date_version_wp, new_version_wp, no_version_wp] } - let(:work_packages_desc) { [new_version_wp, no_date_version_wp, old_version_wp, no_version_wp] } + let(:work_packages_desc) { [no_version_wp, new_version_wp, no_date_version_wp, old_version_wp] } before do login_as(user) diff --git a/spec/models/query/sort_criteria_spec.rb b/spec/models/query/sort_criteria_spec.rb index 04bf3de16177..db61ccc019ad 100644 --- a/spec/models/query/sort_criteria_spec.rb +++ b/spec/models/query/sort_criteria_spec.rb @@ -87,7 +87,7 @@ it "adds the order handling (and the default order by id)" do expect(subject) - .to eq [["work_packages.start_date NULLS LAST"], ["work_packages.id DESC"]] + .to eq [["work_packages.start_date"], ["work_packages.id DESC"]] end end @@ -96,7 +96,7 @@ it "adds the order handling (and the default order by id)" do expect(subject) - .to eq [["work_packages.start_date NULLS LAST"], ["work_packages.id DESC"]] + .to eq [["work_packages.start_date"], ["work_packages.id DESC"]] end end @@ -105,7 +105,7 @@ it "adds the order handling (and the default order by id)" do expect(subject) - .to eq [["work_packages.start_date DESC NULLS LAST"], ["work_packages.id DESC"]] + .to eq [["work_packages.start_date DESC"], ["work_packages.id DESC"]] end end @@ -114,8 +114,8 @@ it "adds the order handling (and the default order by id)" do expect(subject) - .to eq [["name DESC NULLS LAST"], - ["work_packages.start_date NULLS LAST"], + .to eq [["name DESC"], + ["work_packages.start_date"], ["work_packages.id DESC"]] end end diff --git a/spec/support/components/work_packages/activities.rb b/spec/support/components/work_packages/activities.rb index cab633769ee1..b35ce32150be 100644 --- a/spec/support/components/work_packages/activities.rb +++ b/spec/support/components/work_packages/activities.rb @@ -144,11 +144,19 @@ def expect_no_input_field expect(page).not_to have_test_selector("op-work-package-journal-form") end - def add_comment(text: nil, save: true) - sleep 1 # otherwise the stimulus component is not mounted yet and the click does not work + def open_new_comment_editor + page.find_test_selector("op-open-work-package-journal-form-trigger").click + end + + def expect_focus_on_editor + page.within_test_selector("op-work-package-journal-form-element") do + expect(page).to have_css(".ck-content:focus") + end + end + def add_comment(text: nil, save: true) if page.find_test_selector("op-open-work-package-journal-form-trigger") - page.find_test_selector("op-open-work-package-journal-form-trigger").click + open_new_comment_editor else expect(page).to have_test_selector("op-work-package-journal-form-element") end @@ -182,8 +190,6 @@ def edit_comment(journal, text: nil) end def quote_comment(journal) - sleep 1 # otherwise the stimulus component is not mounted yet and the click does not work - within_journal_entry(journal) do page.find_test_selector("op-wp-journal-#{journal.id}-action-menu").click page.find_test_selector("op-wp-journal-#{journal.id}-quote").click