diff --git a/Gemfile.lock b/Gemfile.lock index f3c89a818753..52fe7ca18b01 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -68,7 +68,7 @@ PATH remote: modules/auth_saml specs: openproject-auth_saml (1.0.0) - omniauth-saml (~> 1.10.1) + omniauth-saml (~> 1.10.5) PATH remote: modules/avatars @@ -780,9 +780,9 @@ GEM bigdecimal (>= 3.0) ostruct (>= 0.2) okcomputer (1.18.5) - omniauth-saml (1.10.3) + omniauth-saml (1.10.5) omniauth (~> 1.3, >= 1.3.2) - ruby-saml (~> 1.9) + ruby-saml (~> 1.17) op-clamav-client (3.4.2) open4 (1.3.4) openid_connect (2.2.1) @@ -869,7 +869,7 @@ GEM eventmachine_httpserver http_parser.rb (~> 0.6.0) multi_json - puma (6.4.2) + puma (6.4.3) nio4r (~> 2.0) puma-plugin-statsd (2.6.0) puma (>= 5.0, < 7) @@ -1041,7 +1041,7 @@ GEM ruby-prof (1.7.0) ruby-progressbar (1.13.0) ruby-rc4 (0.1.5) - ruby-saml (1.16.0) + ruby-saml (1.17.0) nokogiri (>= 1.13.10) rexml ruby2_keywords (0.0.5) diff --git a/app/components/projects/row_component.rb b/app/components/projects/row_component.rb index d775a4669c73..961a9583d518 100644 --- a/app/components/projects/row_component.rb +++ b/app/components/projects/row_component.rb @@ -293,7 +293,8 @@ def more_menu_settings_item scheme: :default, icon: :gear, label: I18n.t(:label_project_settings), - href: project_settings_general_path(project) + href: project_settings_general_path(project), + data: { turbo: false } } end end diff --git a/app/contracts/members/delete_contract.rb b/app/contracts/members/delete_base_contract.rb similarity index 87% rename from app/contracts/members/delete_contract.rb rename to app/contracts/members/delete_base_contract.rb index f876029feaa0..b0041998d9ec 100644 --- a/app/contracts/members/delete_contract.rb +++ b/app/contracts/members/delete_base_contract.rb @@ -27,12 +27,12 @@ #++ module Members - class DeleteContract < ::DeleteContract - delete_permission :manage_members - + # Abstract contract for deleting members. Should not be used directly. Look at the child classes for + # individual use cases. + class DeleteBaseContract < ::DeleteContract validate :member_is_deletable - private + protected def member_is_deletable errors.add(:base, :not_deletable) unless model.some_roles_deletable? diff --git a/app/contracts/members/delete_from_project_contract.rb b/app/contracts/members/delete_from_project_contract.rb new file mode 100644 index 000000000000..109a644e3ee0 --- /dev/null +++ b/app/contracts/members/delete_from_project_contract.rb @@ -0,0 +1,33 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Members + class DeleteFromProjectContract < DeleteBaseContract + delete_permission :manage_members + end +end diff --git a/app/contracts/members/delete_globally_contract.rb b/app/contracts/members/delete_globally_contract.rb new file mode 100644 index 000000000000..3b1f2c2746f4 --- /dev/null +++ b/app/contracts/members/delete_globally_contract.rb @@ -0,0 +1,33 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Members + class DeleteGloballyContract < DeleteBaseContract + delete_permission :admin + end +end diff --git a/app/helpers/members_helper.rb b/app/helpers/members_helper.rb index 83adce4f65d3..a310b638b0e4 100644 --- a/app/helpers/members_helper.rb +++ b/app/helpers/members_helper.rb @@ -36,11 +36,13 @@ def global_member_role_deletion_link(member, role) if member.roles.length == 1 link_to("", principal_membership_path(member.principal, member), - { method: :delete, class: "icon icon-delete", title: t(:button_delete) }) + { method: :delete, class: "icon icon-delete", title: t(:button_delete), + data: { "test-selector" => "delete-global-role" } }) else link_to("", principal_membership_path(member.principal, member, "membership[role_ids]" => member.roles - [role]), - { method: :patch, class: "icon icon-delete", title: t(:button_delete) }) + { method: :patch, class: "icon icon-delete", title: t(:button_delete), + data: { "test-selector" => "delete-global-role" } }) end end diff --git a/app/services/members/delete_service.rb b/app/services/members/delete_service.rb index 0bf8ff9ad196..79a1b2dbc441 100644 --- a/app/services/members/delete_service.rb +++ b/app/services/members/delete_service.rb @@ -60,4 +60,13 @@ def cleanup_for_group(member) .new(member.principal, current_user: user, contract_class: EmptyContract) .call end + + def default_contract_class + # We have different contracts for project roles and global roles + if model.project_role? + "#{namespace}::DeleteFromProjectContract".constantize + else + "#{namespace}::DeleteGloballyContract".constantize + end + end end diff --git a/app/services/principals/replace_references_service.rb b/app/services/principals/replace_references_service.rb index d74dcd0b1dc9..e1c0810a49c1 100644 --- a/app/services/principals/replace_references_service.rb +++ b/app/services/principals/replace_references_service.rb @@ -49,6 +49,7 @@ def rewrite_active_models(from, to) rewrite_actor(from, to) rewrite_owner(from, to) rewrite_logged_by(from, to) + rewrite_presenter(from, to) end def rewrite_custom_value(from, to) @@ -135,12 +136,20 @@ def rewrite_logged_by(from, to) end end + def rewrite_presenter(from, to) + [ + MeetingAgendaItem + ].each do |klass| + rewrite(klass, :presenter_id, from, to) + end + end + def journal_classes [Journal] + Journal::BaseJournal.subclasses end def foreign_keys - %w[author_id user_id assigned_to_id responsible_id logged_by_id] + %w[author_id user_id assigned_to_id responsible_id logged_by_id presenter_id] end def rewrite(klass, attribute, from, to) diff --git a/app/views/my/account.html.erb b/app/views/my/account.html.erb index 316e5c7db6f6..3b67ce5144bd 100644 --- a/app/views/my/account.html.erb +++ b/app/views/my/account.html.erb @@ -74,9 +74,7 @@ See COPYRIGHT and LICENSE files for more details.
<%= f.text_field :mail, required: true, container_class: '-middle', disabled: login_via_ldap %> - <% if login_via_provider %> - <%= t('user.text_change_disabled_for_provider_login') %> - <% elsif login_via_ldap %> + <% if login_via_ldap %> <%= t('user.text_change_disabled_for_ldap_login') %> <% end %>
diff --git a/app/workers/attachments/finish_direct_upload_job.rb b/app/workers/attachments/finish_direct_upload_job.rb index dcf878310109..18865d4cc3df 100644 --- a/app/workers/attachments/finish_direct_upload_job.rb +++ b/app/workers/attachments/finish_direct_upload_job.rb @@ -79,7 +79,7 @@ def validate_attachment(attachment, whitelist) contract = create_contract attachment, whitelist unless contract.valid? - errors = contracterrors.full_messages.join(", ") + errors = contract.errors.full_messages.join(", ") raise "Failed to validate attachment #{attachment.id}: #{errors}" end end diff --git a/config/locales/crowdin/de.yml b/config/locales/crowdin/de.yml index 2393b8e3f85d..0581d4408526 100644 --- a/config/locales/crowdin/de.yml +++ b/config/locales/crowdin/de.yml @@ -1657,27 +1657,27 @@ de: label: "XLS" columns: input_label_report: "Spalten zur Attributtabelle hinzufügen" - input_caption_report: "By default all attributes added as columns in the work package list are selected. Long text fields are not available in the attribute table, but can be displayed below it." - input_caption_table: "By default all attributes added as columns in the work package list are selected. Long text fields are not available in table based exports." + input_caption_report: "Standardmäßig sind alle Attribute, die als Spalten in der Arbeitspaketliste hinzugefügt wurden, ausgewählt. Textfelder sind in der Attribut-Tabelle nicht verfügbar, können aber unterhalb der Tabelle angezeigt werden." + input_caption_table: "Standardmäßig sind alle Attribute, die als Spalten in der Arbeitspaketliste hinzugefügt wurden, ausgewählt. Textfelder sind in tabellenbasierten Exporten nicht verfügbar." pdf: export_type: - label: "PDF export type" + label: "PDF-Exporttyp" options: table: label: "Tabelle" - caption: "Export the work packages list in a table with the desired columns." + caption: "Exportieren Sie die Liste der Arbeitspakete in eine Tabelle mit den gewünschten Spalten." report: label: "Report" - caption: "Export the work package on a detailed report of all work packages in the list." + caption: "Exportieren Sie das Arbeitspaket in einen detaillierten Bericht über alle Arbeitspakete in der Liste." gantt: label: "Gantt-Diagramm" - caption: "Export the work packages list in a Gantt diagram view." + caption: "Exportieren Sie die Liste der Arbeitspakete in einer Gantt-Diagramm-Ansicht." include_images: - label: "Include images" - caption: "Exclude images to reduce the size of the PDF export." + label: "Bilder inkludieren" + caption: "Schließen Sie Bilder aus, um die Größe des PDF-Exports zu reduzieren." gantt_zoom_levels: - label: "Zoom levels" - caption: "Select what is the zoom level for dates displayed in the chart." + label: "Zoomstufe" + caption: "Wählen Sie die Zoomstufe für die im Diagramm angezeigten Daten." options: days: "Tage" weeks: "Wochen" @@ -1696,18 +1696,18 @@ de: long_text_fields: input_caption: "Standardmäßig sind alle Langtextfelder ausgewählt." input_label: "Langtextfelder hinzufügen" - input_placeholder: "Search for long text fields" - drag_area_label: "Manage long text fields" + input_placeholder: "Nach Textfeldern suchen" + drag_area_label: "Textfelder hinzufügen" xls: include_relations: - label: "Include relations" - caption: "This option will create a duplicate of each work package for every relation this has with another work package." + label: "Beziehungen inkludieren" + caption: "Jedes Arbeitspakets, mit dem eine Beziehung mit dem exportierten Arbeitspaket besteht, als zusätzliche Zeile einfügen." include_descriptions: - label: "Include descriptions" - caption: "This option will add a description column in raw format." - your_work_packages_export: "Work packages are being exported" - succeeded: "Export completed" - failed: "An error has occurred while trying to export the work packages: %{message}" + label: "Beschreibungen inkludieren" + caption: "Mit dieser Option wird die Beschreibungs-Spalte im Rohformat hinzugefügt." + your_work_packages_export: "Arbeitspakete werden exportiert" + succeeded: "Export abgeschlossen" + failed: "Beim Versuch, die Arbeitspakete zu exportieren, ist ein Fehler aufgetreten: %{message}" format: atom: "Atom" csv: "CSV" @@ -2358,8 +2358,8 @@ de: label_role_plural: "Rollen" label_role_search: "Rolle für neue Mitglieder zuweisen" label_scm: "Versionskontrollsystem" - label_scroll_left: "Scroll left" - label_scroll_right: "Scroll right" + label_scroll_left: "Nach links scrollen" + label_scroll_right: "Nach rechts scrollen" label_search: "Suche" label_search_by_name: "Nach Name suchen" label_send_information: "Neue Anmeldeinformationen an den Benutzer senden" @@ -3117,9 +3117,9 @@ de: setting_work_package_done_ratio_field: "Arbeitsbezogen" setting_work_package_done_ratio_status: "Statusbezogen" setting_work_package_done_ratio_explanation_pre_14_4_without_percent_complete_edition_html: > - In work-based mode, % Complete is calculated from how much work is done in relation to total work. In status-based mode, each status has a % Complete value associated with it. Changing status will change % Complete. + Im arbeitsbezogenen Modus beschreibt % abgeschlossen das Verhältnis zwischen verbeibendem Aufwand und Gesamtaufwand. Im statusbasierten Modus ist jedem Status ein Wert für % abgeschlossen zugeordnet. Wenn Sie den Status ändern, ändert sich % abgeschlossen. setting_work_package_done_ratio_explanation_html: > - In work-based mode, % Complete can be freely set to any value. If you optionally enter a value for Work, Remaining work will automatically be derived. In status-based mode, each status has a % Complete value associated with it. Changing status will change % Complete. + Im arbeitsbezogenen Modus ist % abgeschlossen frei wählbar. Falls Sie einen Wert für Aufwand angeben, wird Verbleibender Aufand automatisch abgeleitet. Im statusbasierten Modus ist jedem Status ein Wert für % abgeschlossen zugeordnet. Wenn Sie den Status ändern, ändert sich % abgeschlossen. setting_work_package_properties: "Arbeitspaket-Eigenschaften" setting_work_package_startdate_is_adddate: "Neue Arbeitspakete haben \"Heute\" als Anfangsdatum" setting_work_packages_projects_export_limit: "Arbeitspakete / Exportlimit für Projekte" @@ -3501,26 +3501,26 @@ de: progress: label_note: "Hinweis:" modal: - work_based_help_text: "Each field is automatically calculated from the two others when possible." + work_based_help_text: "Jedes Feld wird, wenn möglich, automatisch aus den beiden anderen berechnet." work_based_help_text_pre_14_4_without_percent_complete_edition: "% Abgeschlossen wird automatisch aus Aufwand und Verbleibender Aufwand abgeleitet." status_based_help_text: "% Abgeschlossen wird durch den Status des Arbeitspakets festgelegt." migration_warning_text: "Im aufwandsbezogenen Modus, kann % Fertig nicht manuell eingegeben werden und ist immer an den Aufwand gebunden. Der vorhandene Wert wurde beibehalten, kann aber nicht bearbeitet werden. Bitte geben Sie zuerst den Wert für Aufwand ein." derivation_hints: done_ratio: - cleared_because_remaining_work_is_empty: "Cleared because Remaining work is empty." - cleared_because_work_is_0h: "Cleared because Work is 0h." - derived: "Derived from Work and Remaining work." + cleared_because_remaining_work_is_empty: "Gelöscht, weil Verbleibender Aufwand leer ist." + cleared_because_work_is_0h: "Gelöscht, weil Aufwand 0h beträgt." + derived: "Von Aufwand und Verbleibendem Aufwand abgeleitet." estimated_hours: - cleared_because_remaining_work_is_empty: "Cleared because Remaining work is empty." - derived: "Derived from Remaining work and % Complete." - same_as_remaining_work: "Set to same value as Remaining work." + cleared_because_remaining_work_is_empty: "Gelöscht, weil Verbleibender Aufwand leer ist." + derived: "Von Aufwand und % abgeschlossen abgeleitet." + same_as_remaining_work: "Auf denselben Wert wie Verbleibender Aufwand gesetzt." remaining_hours: - cleared_because_work_is_empty: "Cleared because Work is empty." - cleared_because_percent_complete_is_empty: "Cleared because % Complete is empty." + cleared_because_work_is_empty: "Gelöscht, weil Aufwand leer ist." + cleared_because_percent_complete_is_empty: "Gelöscht, da % abgeschlossen leer ist." decreased_like_work: "Verringert um den gleichen Betrag wie Aufwand." - derived: "Derived from Work and % Complete." + derived: "Abgeleitet von Aufwand und % abgeschlossen." increased_like_work: "Erhöht um den gleichen Betrag wie Aufwand." - same_as_work: "Set to same value as Work." + same_as_work: "Auf denselben Wert wie Aufwand gesetzt." permissions: comment: "Kommentar" comment_description: "Kann dieses Arbeitspaket anzeigen und kommentieren." diff --git a/config/locales/crowdin/js-de.yml b/config/locales/crowdin/js-de.yml index 66d38cfd34db..729b038f35e5 100644 --- a/config/locales/crowdin/js-de.yml +++ b/config/locales/crowdin/js-de.yml @@ -280,7 +280,7 @@ de: warning_progress_calculation_mode_change_from_status_to_field_pre_14_4_without_percent_complete_edition_html: >- Wenn Sie den Modus der Fortschrittsberechnung von statusbezogen auf aufwandsbezogen ändern, wird % Abgeschlossen zu einem nicht editierbaren Feld, dessen Wert von Aufwand und Verbleibender Aufwand abgeleitet wird. Vorhandene Werte für % Abgeschlossen werden beibehalten. Wenn die Werte für Aufwand und Verbleibender Aufwand nicht vorhanden waren, werden sie benötigt, um % Abgeschlossen zu ändern. warning_progress_calculation_mode_change_from_status_to_field_html: >- - Changing progress calculation mode from status-based to work-based will make the % Complete field freely editable. If you optionally enter values for Work or Remaining work, they will also be linked to % Complete. Changing Remaining work can then update % Complete. + Wenn Sie den Modus der Fortschrittsberechnung von statusbasiert auf arbeitsbasiert ändern, ist das Feld % abgeschlossen frei editierbar. Wenn Sie optional Werte für Aufwand oder Verbleibenden Aufwand eingeben, werden diese auch mit % abgeschlossen verknüpft. Eine Änderung des verbleibenden Aufwands aktualisiert dann % abgschlossen. warning_progress_calculation_mode_change_from_field_to_status_html: >- Wenn Sie den Modus der Fortschrittsberechnung von aufwandsbezogen auf statusbezogen ändern, gehen alle bestehenden Werte für % Fertigstellung verloren und werden durch Werte ersetzt, die mit dem jeweiligen Status verbunden sind. Bestehende Werte für Verbleibender Aufwand können ebenfalls neu berechnet werden, um diese Änderung widerzuspiegeln. Diese Aktion ist nicht umkehrbar. custom_actions: diff --git a/config/locales/crowdin/js-lt.yml b/config/locales/crowdin/js-lt.yml index 1a437c88b35d..bcb33257c337 100644 --- a/config/locales/crowdin/js-lt.yml +++ b/config/locales/crowdin/js-lt.yml @@ -1350,7 +1350,7 @@ lt: close: "Close modal" open_project_storage_modal: waiting_title: - timeout: "Timeout" + timeout: "Skirtojo laiko pabaiga" waiting_subtitle: network_off: "Yra tinklo problema." network_on: "Tinklas grįžo. Mes bandome." diff --git a/config/locales/crowdin/js-ro.yml b/config/locales/crowdin/js-ro.yml index 507cb4421572..446298d02e23 100644 --- a/config/locales/crowdin/js-ro.yml +++ b/config/locales/crowdin/js-ro.yml @@ -112,7 +112,7 @@ ro: inline: "Evidențiați în linie:" entire_card_by: "Întreaga carte de" remove_from_list: "Alegeți intervalul din listă" - caption_rate_history: "Istoricul ratei" + caption_rate_history: "Istoric cotă" clipboard: browser_error: "Your browser doesn't support copying to clipboard. Please copy it manually: %{content}" copied_successful: "Copiat cu succes în clipboard!" @@ -483,7 +483,7 @@ ro: label_project: "Proiect" label_project_list: "Listă proiecte" label_project_plural: "Proiecte" - label_visibility_settings: "Setări de vizibilitate" + label_visibility_settings: "Setări vizibilitate" label_quote_comment: "Citare comentariu" label_recent: "Recente" label_reset: "Resetare" @@ -495,7 +495,7 @@ ro: label_repository_plural: "Repo-uri" label_save_as: "Salvare ca" label_search_columns: "Search a column" - label_select_project: "Selectați un proiect" + label_select_project: "Selectează un proiect" label_select_watcher: "Selectaţi un observator..." label_selected_filter_list: "Filtre selectate" label_show_attributes: "Afișați toate câmpurile" @@ -583,7 +583,7 @@ ro: sprints: "În dreapta aveți lista de rezultate și erorile restante, în stânga aveți sprintele respective. Aici poți crea epicuri, povestiri pentru utilizatori și erori, prioritizează prin drag & drop și adaugă-le la un sprint." task_board_arrow: "Pentru a vedea panoul de sarcini, deschideți meniul Sprint..." task_board_select: "... și selectați intrarea Task board." - task_board: "Grupul de lucru vizualizează progresul pentru această sprint. Dă click pe pictograma plus (+) de lângă o povestire de utilizator pentru a adăuga sarcini noi sau impedimente.
Starea poate fi actualizată de drag și drop." + task_board: "Grupul de lucru vizualizează progresul pentru această iterație. Dă click pe pictograma plus (+) de lângă o cerință de utilizator pentru a adăuga sarcini noi sau impedimente.
Starea poate fi actualizată cu glisare și plasare." boards: overview: "Selectează secțiunile pentru a schimba vizualizarea și administrarea proiectului tău folosind vizualizarea secțiunilor agile." lists_kanban: "Aici puteţi crea mai multe liste (coloane) în cadrul secţiunii. Această funcţie vă permite să creaţi o tabelă Kanban, de exemplu." @@ -639,7 +639,7 @@ ro: no_results: at_all: "Aici vor apărea notificări noi atunci când există o activitate care vă interesează." with_current_filter: "Nu există notificări în această vizualizare în acest moment" - mark_all_read: "Marchează Tot ca Citit" + mark_all_read: "Marchează toate ca și citite" mark_as_read: "Marchează ca Citit" text_update_date_by: "%{date} by" total_count_warning: "Se afișează %{newest_count} cele mai recente notificări. %{more_count} în plus nu sunt afișate." @@ -654,7 +654,7 @@ ro: link_text: "Click here to load them." settings: change_notification_settings: 'Puteți modifica setările de notificare pentru a vă asigura că nu pierdeți niciodată o actualizare importantă.' - title: "Setări de notificare" + title: "Setări notificare" notify_me: "Anunta-ma" reminders: no_notification: Nicio notificare @@ -694,7 +694,7 @@ ro: teaser_text: "Cu alertele de date, veți fi notificat cu privire la următoarele date de început sau de sfârșit, astfel încât să nu ratați sau să uitați niciodată un termen limită important." overdue: În caz de întârziere project_specific: - title: "Setări de notificare specifice proiectului" + title: "Setări notificare specifice proiectului" description: "Aceste setări specifice proiectului prevalează asupra setărilor implicite de mai sus." add: "Adăugați o setare pentru proiect" already_selected: "Acest proiect este deja selectat" @@ -1017,7 +1017,7 @@ ro: label_content: "Click aici pentru a sări peste tabelul cu pachete de lucru și a ajunge la paginare" placeholders: default: "-" - date: "Selectați data" + date: "Selectează data" query: column_names: "Coloane" group_by: "Grupare rezultate" @@ -1114,7 +1114,7 @@ ro: save: "Salvare" save_as: "Salvare ca" export: "Exportare" - visibility_settings: "Setări de vizibilitate" + visibility_settings: "Setări vizibilitate" share_calendar: "Subscribe to calendar" page_settings: "Redenumire" delete: "Șterge" @@ -1223,14 +1223,14 @@ ro: invite_principal_to_project: "Invitați %{principal} la %{project}" project: label: "Proiect" - required: "Vă rugăm să selectați un proiect" - lacking_permission: "Vă rugăm să selectați un alt proiect, deoarece nu aveți permisiuni pentru a atribui utilizatori la cel selectat în prezent." + required: "Te rog să selectezi un proiect" + lacking_permission: "Te rog să selectezi un alt proiect, deoarece nu ai permisiuni pentru a atribui utilizatori la cel selectat în prezent." lacking_permission_info: "Nu aveți permisiunea de a atribui utilizatori la proiectul în care vă aflați în prezent. Trebuie să selectați un alt proiect." next_button: "Înainte" no_results: "Nu au fost găsite proiecte" no_invite_rights: "Nu aveți voie să invitați membri în acest proiect" type: - required: "Vă rugăm să selectați tipul de invitat" + required: "Te rog să selectezi tipul de invitat" user: title: "Utilizator" description: "Permisiuni bazate pe rolul atribuit în proiectul selectat" @@ -1254,15 +1254,15 @@ ro: no_results_group: "Nu au fost găsite grupuri" next_button: "Înainte" required: - user: "Vă rugăm să selectați un utilizator" - placeholder: "Vă rugăm să selectați limba dvs" - group: "Vă rugăm să selectați limba dvs" + user: "Te rog să selectezi un utilizator" + placeholder: "Te rog să selectezi un substituent" + group: "Te rog să selectezi un grup" role: label: "Rolul în %{project}" no_roles_found: "Nu au fost găsite roluri" description: >- This is the role that the user will receive when they join your project. Rolul definește acțiunile pe care utilizatorul are voie să le întreprindă și informațiile pe care le poate vedea. Aflați mai multe despre roluri și permisiuni. - required: "Vă rugăm să selectați un rol" + required: "Te rog să selectezi un rol" next_button: "Înainte" message: label: "Mesaj invitație" diff --git a/config/locales/crowdin/lt.yml b/config/locales/crowdin/lt.yml index 1fa5ec882b3f..483876a1246e 100644 --- a/config/locales/crowdin/lt.yml +++ b/config/locales/crowdin/lt.yml @@ -270,7 +270,7 @@ lt: favored: "Mėgstami projektai" archived: "Archyvuoti projektai" shared: "Shared project lists" - my_lists: "My project lists" + my_lists: "Mano projektų sąrašai" new: placeholder: "Naujas projektų sąrašas" delete_modal: @@ -297,7 +297,7 @@ lt: actions: label_enable_single: "Aktyvus šiame projekte, spauskite, jei norite išjungti" label_disable_single: "Neakvyus šiame projekte, spauskite, jei norite įjungti" - remove_from_project: "Remove from project" + remove_from_project: "Pašalinti iš projekto" label_enable_all: "Įjungti visus" label_disable_all: "Išjungti visus" is_required_blank_slate: @@ -493,7 +493,7 @@ lt: is_readonly: "Tik skaityti" excluded_from_totals: "Excluded from totals" themes: - dark: "Dark (Beta)" + dark: "Tamsus (Beta versija)" light: "Šviesi" light_high_contrast: "Šviesi kontrastinga" types: @@ -3650,7 +3650,7 @@ lt: publishing_denied: "You do not have permission to make project lists public." access_warning: "Users will only see the projects they have access to. Sharing project lists does not impact individual project permissions." user_details: - owner: "List owner" + owner: "Sąrašo savininkas" can_view_because_public: "Can already view because list is shared with everyone" can_manage_public_lists: "Can edit due to global permissions" public_flag: diff --git a/config/locales/crowdin/ro.seeders.yml b/config/locales/crowdin/ro.seeders.yml index d30a1063be4c..7e67db5199c5 100644 --- a/config/locales/crowdin/ro.seeders.yml +++ b/config/locales/crowdin/ro.seeders.yml @@ -468,7 +468,7 @@ ro: item_4: name: Epic item_5: - name: Scenariu de utilizare + name: Cerință utilizator item_6: name: Defect welcome: diff --git a/config/locales/crowdin/ro.yml b/config/locales/crowdin/ro.yml index 22a38da9be28..78baed63974e 100644 --- a/config/locales/crowdin/ro.yml +++ b/config/locales/crowdin/ro.yml @@ -1910,7 +1910,7 @@ ro: shared: "Partajat" watched: "Observator" facets: - unread: "necitită" + unread: "Necitite" unread_title: "Afișare necitite" all: "Toate" all_title: "Afișare toate" @@ -2260,7 +2260,7 @@ ro: label_never: "Niciodată" label_new: "Nou" label_new_features: "Caracteristici noi" - label_new_statuses_allowed: "Stări noi permise" + label_new_statuses_allowed: "Setări noi permise" label_news_singular: "Noutăţi" label_news_added: "Noutăți adăugate" label_news_comment_added: "Comentariu adăugat la noutăți" @@ -2407,7 +2407,7 @@ ro: label_send_test_email: "Trimite e-mail de test" label_session: "Sesiune" label_setting_plural: "Setări" - label_system_settings: "Setări de sistem" + label_system_settings: "Setări sistem" label_show_completed_versions: "Afișare versiuni complete" label_columns: "Coloane" label_sort: "Sortare" @@ -2518,7 +2518,7 @@ ro: label_work_packages_settings: "Work packages settings" label_work_package_status: "Stare pachet de lucru" label_work_package_status_new: "Stare nouă" - label_work_package_status_plural: "Stări pachete de lucru" + label_work_package_status_plural: "Setări pachete de lucru" label_work_package_types: "Tipuri de pachete de lucru" label_work_package_tracking: "Urmărire pachete de lucru" label_work_package_view_all: "Toate pachetele de lucru" @@ -2583,7 +2583,7 @@ ro: digests: including_mention_singular: "inclusiv o mențiune" including_mention_plural: "inclusiv %{number_mentioned} menționări" - unread_notification_singular: "1 notificare necitită" + unread_notification_singular: "o notificare necitită" unread_notification_plural: "%{number_unread} notificări necitite" you_have: "Aveți" logo_alt_text: "Logo-ul" @@ -2887,7 +2887,7 @@ ro: permission_rename_wiki_pages: "Redenumire pagini wiki" permission_save_queries: "Salvați vizualizările" permission_search_project: "Caută proiect" - permission_select_custom_fields: "Selectați câmpurile personalizate" + permission_select_custom_fields: "Selectează câmpuri personalizate" permission_select_project_custom_fields: "Select project attributes" permission_select_project_modules: "Selectare module proiect" permission_share_work_packages: "Share work packages" @@ -3257,7 +3257,7 @@ ro: mode_long: inline: "Evidențiați atributul (atributele) inline" none: "Fără evidențiere" - status: "Rândul întreg după statut" + status: "Întregul rând după Stare" type: "Întregul rând pe tipuri" priority: "Întregul rând în funcție de Prioritate" icalendar: @@ -3298,7 +3298,7 @@ ro: markdown: "Markdown" plain: "Text simplu" status_active: "activ" - status_archived: "Arhivat" + status_archived: "arhivat" status_blocked: "blocked" status_invited: invitat status_locked: blocat @@ -3494,7 +3494,7 @@ ro: registered: "înregistrat" reset_failed_logins: "Resetare autentificări eşuate" status_user_and_brute_force: "%{user} şi %{brute_force}" - status_change: "Schimbare de stare" + status_change: "Schimbare stare" text_change_disabled_for_provider_login: "Numele este setat de furnizorul de logare și, prin urmare, nu poate fi modificat." text_change_disabled_for_ldap_login: "The name and email is set by LDAP and can thus not be changed." unlock: "Deblochează" diff --git a/docs/installation-and-operations/installation/README.md b/docs/installation-and-operations/installation/README.md index 26e3702d8054..b446ad3df95b 100644 --- a/docs/installation-and-operations/installation/README.md +++ b/docs/installation-and-operations/installation/README.md @@ -6,15 +6,15 @@ sidebar_navigation: # Installing OpenProject -OpenProject can be setup in three different ways: +OpenProject can be setup in these different ways: -| Topic | Content | -| ------------------------------------------------ | ------------------------------------------------------------ | -| [Installation with DEB/RPM packages](./packaged) | This is the recommended way to install OpenProject | -| [Installation with Docker](./docker) | This allows to setup OpenProject in an isolated manner using Docker | -| [Installation with Kubernetes](./kubernetes) | This allows to setup OpenProject using Kubernetes | -| [Installation with Helm charts](./helm-chart) | This allows to setup OpenProject using Helm charts | -| [Other](misc/) | Extra information on installing OpenProject on specific platforms such as Kubernetes. | +| Topic | Content | +| ---------------------------------------------------- | ------------------------------------------------------------------------------------- | +| [Installation with DEB/RPM packages](./packaged) | This is the recommended way to install OpenProject | +| [Installation with Docker Compose](./docker-compose) | This allows to setup OpenProject in an isolated manner using Docker Compose | +| [Installation with single Docker container](./docker)| This allows to setup OpenProject in a single Docker container | +| [Installation with Helm charts](./helm-chart) | This allows to setup OpenProject using Helm charts | +| [Other](misc/) | Extra information on installing OpenProject on specific platforms such as Kubernetes. | > **NOTE: We recommend using the DEB/RPM package installation.** diff --git a/docs/installation-and-operations/installation/docker/README.md b/docs/installation-and-operations/installation/docker/README.md index 27bcdcecbe9f..08a051a153e8 100644 --- a/docs/installation-and-operations/installation/docker/README.md +++ b/docs/installation-and-operations/installation/docker/README.md @@ -1,10 +1,10 @@ --- sidebar_navigation: - title: Docker + title: Docker (all-in-one) priority: 300 --- -# Install OpenProject with Docker +# OpenProject on Docker all-in-one container [Docker](https://www.docker.com) is a way to distribute self-contained applications easily. We provide a Docker image for the Community edition that you can very easily install and upgrade on your servers. However, contrary to the manual or package-based installation, your machine needs to have the Docker Engine @@ -12,7 +12,7 @@ installed first, which usually requires a recent operating system. Please see th *** -**Supported architectures** +## Supported architectures Starting with OpenProject 12.5.6 we publish our containers for three architectures. @@ -24,75 +24,24 @@ The OpenProject **BIM Edition** is only supported on AMD64, however. *** -**Limitations** +## Limitations Note that the docker container setup does not allow for integration of repositories within OpenProject. You can reference external repositories, but cannot set them up through OpenProject itself. For that feature to work, you need to use the packaged installation method. -**Overview** +## Overview OpenProject's docker setup can be launched in two ways: -1. Multiple containers (recommended), each with a single process inside, using a Compose file. Allows to easily choose which services you want to run, and simplifies scaling and monitoring aspects. +### One container per process (recommend) -2. One container with all the processes inside. Easy but not recommended for production. This is the legacy behavior. +This is the recommended approach for using OpenProject with Docker, where each component has a single container inside, orchestrated using a Compose file. Allows to easily choose which services you want to run, and simplifies scaling and monitoring aspects. -## One container per process (recommended) +Please follow the [OpenProject for Docker compose](../docker-compose/) documentation for this installation method -### Quick Start - -First, you must clone the [openproject-deploy](https://github.com/opf/openproject-deploy/tree/stable/14/compose) repository: - -```shell -git clone https://github.com/opf/openproject-deploy --depth=1 --branch=stable/14 openproject -``` - -Then, change into the compose folder, this folder will be the location where you enter all following commands: - -```shell -cd openproject/compose -``` - -Make sure you are using the latest version of the Docker images: - -```shell -docker-compose pull -``` - -Launch the containers: - -```shell -OPENPROJECT_HTTPS=false docker-compose up -d -``` - -After a while, OpenProject should be up and running on `http://localhost:8080`. The default username and password is login: `admin`, and password: `admin`. You need to explicitly disable HTTPS mode on startup as OpenProject assumes it's running behind HTTPS in production by default. - -> **Note:** The `docker-compose.yml` file present in the repository can be adjusted to your convenience. With each pull it will be overwritten. Best practice is to use the file `docker-compose.override.yml` for that case. For instance you could mount specific configuration files, override environment variables, or switch off services you don't need. Please refer to the official [Docker Compose documentation](https://docs.docker.com/compose/extends/) for more details. - -You can stop the Compose stack by running: - -```shell -docker-compose stop -``` - -You can stop and remove all containers by running: - -```shell -docker-compose down -``` - -This will not remove your data which is persisted in named volumes, likely called `compose_opdata` (for attachments) and `compose_pgdata` (for the database). The exact name depends on the name of the directory where your `docker-compose.yml` and/or you `docker-compose.override.yml` files are stored (`compose` in this case). - -If you want to start from scratch and remove the existing data you will have to remove these volumes via -`docker volume rm compose_opdata compose_pgdata`. - -### Configuration - -Please see the [advanced configuration guide's docker paragraphs](../../configuration/#docker) - -#### BIM edition +### Single docker container -In order to install or change to BIM inside a Docker environment, please navigate to the [Docker Installation for OpenProject BIM](../../bim-edition/#docker-installation-openproject-bim) paragraph at the BIM edition documentation. +This guide will show you to install OpenProject in one container with all the processes inside. This allows for a very quick start but is not recommended for production as it hinders upgradability of the different components such as the database. ## All-in-one container diff --git a/docs/installation-and-operations/installation/kubernetes/README.md b/docs/installation-and-operations/installation/kubernetes/README.md index a57325d7300b..3238db6e304b 100644 --- a/docs/installation-and-operations/installation/kubernetes/README.md +++ b/docs/installation-and-operations/installation/kubernetes/README.md @@ -7,6 +7,6 @@ sidebar_navigation: # Kubernetes Kubernetes is a container orchestration tool. As such it can use the -OpenProject docker container in the same manner as shown in the [docker section](../docker/#one-container-per-process-recommended). +OpenProject docker container in the same manner as shown in the [docker section](../docker/). -In the [openproject-deploy](https://github.com/opf/openproject-deploy/blob/stable/14/kubernetes/README.md) repository we provide further information and an exemplary set of YAML files defining a complete OpenProject setup on Kubernetes. +If you'd like to run OpenProject on Kubernetes, please take a look at the [OpenProject helm chart](../helm-chart). diff --git a/docs/installation-and-operations/installation/misc/README.md b/docs/installation-and-operations/installation/misc/README.md index f0e8ce1b4c5f..b73e67d0c498 100644 --- a/docs/installation-and-operations/installation/misc/README.md +++ b/docs/installation-and-operations/installation/misc/README.md @@ -12,7 +12,7 @@ The respective sections explain everything you need to know. To make things easier here we give further instructions on how to get up and running with OpenProject on different platforms which use either the docker container or the package: -* [Kubernetes](../kubernetes) +* [Kubernetes using the OpenProject Helm chart](../helm-chart) * [Synology](../synology) * [Manual (not recommended)](../manual) diff --git a/docs/installation-and-operations/installation/synology/README.md b/docs/installation-and-operations/installation/synology/README.md index ee8431a6d286..d0ca8080b429 100644 --- a/docs/installation-and-operations/installation/synology/README.md +++ b/docs/installation-and-operations/installation/synology/README.md @@ -5,7 +5,7 @@ sidebar_navigation: false # Synology Synology offers NAS devices that come with a UI for starting docker containers on them. -This means OpenProject has to be used exactly as described in the [docker](../docker/#one-container-per-process-recommended) section. +This means OpenProject has to be used exactly as described in the [docker](../docker/) section. ## Launching the container diff --git a/docs/installation-and-operations/misc/migration-to-postgresql13/README.md b/docs/installation-and-operations/misc/migration-to-postgresql13/README.md index d5d093d3a2c5..ab0fa2450f63 100644 --- a/docs/installation-and-operations/misc/migration-to-postgresql13/README.md +++ b/docs/installation-and-operations/misc/migration-to-postgresql13/README.md @@ -160,7 +160,7 @@ sudo yum remove pgsql10 ## Compose-based docker installation -> Please follow this section only if you have installed OpenProject using [this procedure](../../installation/docker/#one-container-per-process-recommended). +> Please follow this section only if you have installed OpenProject using [this procedure](../../installation/docker/). > Before attempting the upgrade, please ensure you have performed a backup of your installation by following the [backup guide](../../operation/backing-up/). You can find the upgrade instructions for your docker-compose setup in the [openproject-deploy](https://github.com/opf/openproject-deploy/blob/stable/14/compose/control/README.md#upgrade) repository. diff --git a/docs/installation-and-operations/operation/restoring/README.md b/docs/installation-and-operations/operation/restoring/README.md index 82e1ddf22bab..8b5ffa236e49 100644 --- a/docs/installation-and-operations/operation/restoring/README.md +++ b/docs/installation-and-operations/operation/restoring/README.md @@ -246,7 +246,7 @@ You may need to create the `files` directory if it doesn't exist yet. #### 4) Start OpenProject -Start the container as described in the [installation section](../../installation/docker/#one-container-per-process-recommended) +Start the container as described in the [installation section](../../installation/docker/) mounting `/var/lib/openproject/pgdata` (and `/var/lib/openproject/assets/` for attachments). ## Changing the database schema from cloud to on-premises diff --git a/docs/installation-and-operations/operation/upgrading/README.md b/docs/installation-and-operations/operation/upgrading/README.md index 4be46a4a2428..f752714d9832 100644 --- a/docs/installation-and-operations/operation/upgrading/README.md +++ b/docs/installation-and-operations/operation/upgrading/README.md @@ -159,7 +159,7 @@ sudo chown -R 102 /volume1/openproject/* ``` After that it's simply a matter of launching the new container mounted with the copied `pgdata` and `assets` folders -as described in the [installation section](../../installation/docker/#one-container-per-process-recommended). +as described in the [installation section](../../installation/docker/). ## Upgrade notes from 9.x diff --git a/docs/release-notes/14-5-1/README.md b/docs/release-notes/14-5-1/README.md new file mode 100644 index 000000000000..37a09e27f62a --- /dev/null +++ b/docs/release-notes/14-5-1/README.md @@ -0,0 +1,45 @@ +--- +title: OpenProject 14.5.1 +sidebar_navigation: + title: 14.5.1 +release_version: 14.5.1 +release_date: 2024-09-24 +--- + +# OpenProject 14.5.1 + +Release date: 2024-09-24 + +We released OpenProject [OpenProject 14.5.1](https://community.openproject.org/versions/2118). +The 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. + + + +## Bug fixes and changes + + + + +- Bugfix: Internal server error opening budget \[[#57905](https://community.openproject.org/wp/57905)\] +- Bugfix: User can't create a new global role \[[#57906](https://community.openproject.org/wp/57906)\] +- Bugfix: German translations not complete \[[#57908](https://community.openproject.org/wp/57908)\] +- Bugfix: User can't be removed from global role \[[#57928](https://community.openproject.org/wp/57928)\] +- Bugfix: Incorrect read-only label for SSO logins \[[#57961](https://community.openproject.org/wp/57961)\] +- Bugfix: The Project Settings-Information page does not load \[[#57981](https://community.openproject.org/wp/57981)\] +- Bugfix: Bump ruby-saml to address CVE-2024-45409 \[[#57984](https://community.openproject.org/wp/57984)\] +- Bugfix: Cannot delete users who created meeting agenda items \[[#57986](https://community.openproject.org/wp/57986)\] + + + + +## Contributions +A very special thank you goes to our sponsors for 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 Александр Татаринцев, Niklas Grönblom. + +Last but not least, we are very grateful for our very engaged translation contributors on Crowdin, who translated quite a few OpenProject strings! +Would you like to help out with translations yourself? +Then take a look at our translation guide and find out exactly how you can contribute. +It is very much appreciated! diff --git a/docs/release-notes/README.md b/docs/release-notes/README.md index 67fc2a42f316..29ae784e31ea 100644 --- a/docs/release-notes/README.md +++ b/docs/release-notes/README.md @@ -13,6 +13,13 @@ Stay up to date and get an overview of the new features included in the releases +## 14.5.1 + +Release date: 2024-09-24 + +[Release Notes](14-5-1/) + + ## 14.5.0 Release date: 2024-09-11 diff --git a/docs/security-and-privacy/processing-of-personal-data/README.md b/docs/security-and-privacy/processing-of-personal-data/README.md index c3ed7cbcd19b..0b35434299d5 100644 --- a/docs/security-and-privacy/processing-of-personal-data/README.md +++ b/docs/security-and-privacy/processing-of-personal-data/README.md @@ -7,7 +7,7 @@ keywords: GDPR, data flow, processing personal data, data privacy information --- # Processing of personal data -Status of this document: 2024-01-10 +Status of this document: 2024-09-22 ## Purpose of this document @@ -116,6 +116,16 @@ Depending on the individual use and permissions of the user the following person - Change history - Person mentioned in a file (incl. file attributes) +#### GitHub pull requests (cg-01) + +* Assignment of a person to a pull request in GitHub (author, reviewer, participant) +* Change history + +#### GitLab merge requests and issues (cg-02) + +* Assignment of a person to a merge request or issue in GitLab (author, reviewer, participant) +* Change history + #### Meetings (cm-01) - Assignment of a person (author, invitee, participant) to a meeting @@ -591,6 +601,7 @@ flowchart LR #### Processed data +* cg-01 * cw-02 #### Security measure @@ -638,6 +649,7 @@ flowchart LR #### Processed data +* cg-02 * cw-02 #### Security measure diff --git a/frontend/src/stimulus/controllers/dynamic/admin/roles.controller.ts b/frontend/src/stimulus/controllers/dynamic/admin/roles.controller.ts index 4f1b52e20d49..da3b6092fde1 100644 --- a/frontend/src/stimulus/controllers/dynamic/admin/roles.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/admin/roles.controller.ts @@ -54,8 +54,17 @@ export default class RolesController extends Controller { } globalRoleValueChanged() { - this.memberAttributesTarget.hidden = this.globalRoleValue; - this.memberPermissionsTarget.hidden = this.globalRoleValue; - this.globalPermissionsTarget.hidden = !this.globalRoleValue; + this.toggleEnabled(this.memberAttributesTarget, !this.globalRoleValue); + this.toggleEnabled(this.memberPermissionsTarget, !this.globalRoleValue); + this.toggleEnabled(this.globalPermissionsTarget, this.globalRoleValue); + } + + toggleEnabled(target:HTMLElement, enabled:boolean) { + target.hidden = !enabled; + target + .querySelectorAll('input,select') + .forEach((input:HTMLInputElement) => { + input.disabled = !enabled; + }); } } diff --git a/lib/open_project/version.rb b/lib/open_project/version.rb index 04fc0bfcb96e..93d308ae9fff 100644 --- a/lib/open_project/version.rb +++ b/lib/open_project/version.rb @@ -33,7 +33,7 @@ module OpenProject module VERSION # :nodoc: MAJOR = 14 MINOR = 5 - PATCH = 0 + PATCH = 1 class << self # Used by semver to define the special version (if any). diff --git a/modules/auth_saml/openproject-auth_saml.gemspec b/modules/auth_saml/openproject-auth_saml.gemspec index 00f0550855e7..7aa3b2f6c426 100644 --- a/modules/auth_saml/openproject-auth_saml.gemspec +++ b/modules/auth_saml/openproject-auth_saml.gemspec @@ -10,6 +10,6 @@ Gem::Specification.new do |s| s.files = Dir["{app,lib}/**/*"] + %w(README.md) - s.add_dependency "omniauth-saml", "~> 1.10.1" + s.add_dependency "omniauth-saml", "~> 1.10.5" s.metadata["rubygems_mfa_required"] = "true" end diff --git a/modules/backlogs/config/locales/crowdin/js-ro.yml b/modules/backlogs/config/locales/crowdin/js-ro.yml index 973c029778f8..ec3398de1ef4 100644 --- a/modules/backlogs/config/locales/crowdin/js-ro.yml +++ b/modules/backlogs/config/locales/crowdin/js-ro.yml @@ -23,4 +23,4 @@ ro: js: work_packages: properties: - storyPoints: "Puncte" + storyPoints: "Puncte cerință" diff --git a/modules/backlogs/config/locales/crowdin/ro.yml b/modules/backlogs/config/locales/crowdin/ro.yml index 7dd5d1d49753..974ccf5b79e6 100644 --- a/modules/backlogs/config/locales/crowdin/ro.yml +++ b/modules/backlogs/config/locales/crowdin/ro.yml @@ -37,7 +37,7 @@ ro: can_only_contain_work_packages_of_current_sprint: "poate conține numai ID-uri ale pachetelor de lucru din sprintul curent." must_block_at_least_one_work_package: "trebuie să conțină ID-ul a cel puțin un bilet." version_id: - task_version_must_be_the_same_as_story_version: "trebuie să fie aceeași cu versiunea povestirii-mamă." + task_version_must_be_the_same_as_story_version: "trebuie să fie aceeași cu versiunea cerinței părinte." sprint: cannot_end_before_it_starts: "Sprint nu se poate termina înainte de a începe." backlogs: @@ -68,7 +68,7 @@ ro: remaining_hours_ideal: "Munca rămasă (ideal)" show_burndown_chart: "Size Chart" story: "Articol" - story_points: "Puncte" + story_points: "Puncte cerință" story_points_ideal: "Puncte" task: "Sarcină" task_color: "Sarcină" @@ -86,7 +86,7 @@ ro: backlogs_sprint_unestimated: "Sprinturi închise sau active cu povești neestimate" backlogs_sprint_unsized: "Proiectul are povești pe sprinturi active sau recent închise care nu au fost dimensionate" backlogs_sprints: "Sprinturi" - backlogs_story: "Articol" + backlogs_story: "Cerință" backlogs_story_type: "Tipuri de povești" backlogs_task: "Sarcină" backlogs_task_type: "Sarcină" @@ -134,7 +134,7 @@ ro: label_wiki: "Wiki" permission_view_master_backlog: "Vizualizare master backlog" permission_view_taskboards: "Vizualizați tablourile de sarcini" - permission_select_done_statuses: "Selectați statusuri terminate" + permission_select_done_statuses: "Selectează stările realizate" permission_update_sprints: "Sprinturi" points_accepted: "puncte acceptate" points_committed: "puncte comise" @@ -144,7 +144,7 @@ ro: project_module_backlogs: "Restanțe" rb_label_copy_tasks: "Copierea pachetelor de lucru" rb_label_copy_tasks_all: "Toate" - rb_label_copy_tasks_none: "Nimic" + rb_label_copy_tasks_none: "Niciuna" rb_label_copy_tasks_open: "Deschis" rb_label_link_to_original: "Includeți un link către povestea originală" remaining_hours: "muncă rămasă" @@ -152,7 +152,7 @@ ro: required_burn_rate_points: "rata de ardere necesară (puncte)" todo_work_package_description: "%{summary}: %{url}\n%{description}" todo_work_package_summary: "%{type}: %{summary}" - version_settings_display_label: "Coloană în backlog" + version_settings_display_label: "Coloană în restanță" version_settings_display_option_left: "stanga" version_settings_display_option_none: "niciuna" version_settings_display_option_right: "dreapta" diff --git a/modules/backlogs/config/locales/crowdin/th.yml b/modules/backlogs/config/locales/crowdin/th.yml index b8902a53355c..17dc6b64aaf3 100644 --- a/modules/backlogs/config/locales/crowdin/th.yml +++ b/modules/backlogs/config/locales/crowdin/th.yml @@ -27,14 +27,14 @@ th: attributes: work_package: position: "ตำแหน่ง" - story_points: "Story Points" + story_points: "" backlogs_work_package_type: "Backlog type" errors: models: work_package: attributes: blocks_ids: - can_only_contain_work_packages_of_current_sprint: "can only contain IDs of work packages in the current sprint." + can_only_contain_work_packages_of_current_sprint: "" must_block_at_least_one_work_package: "must contain the ID of at least one ticket." version_id: task_version_must_be_the_same_as_story_version: "must be the same as the parent story's version." diff --git a/modules/boards/config/locales/crowdin/js-kk.yml b/modules/boards/config/locales/crowdin/js-kk.yml index 102d11ff8201..f5b6aafa59f6 100644 --- a/modules/boards/config/locales/crowdin/js-kk.yml +++ b/modules/boards/config/locales/crowdin/js-kk.yml @@ -7,7 +7,7 @@ kk: label_unnamed_list: 'Unnamed list' label_board_type: 'Board type' upsale: - teaser_text: 'Would you like to automate your workflows with Boards? Advanced boards are an Enterprise add-on. Please upgrade to a paid plan.' + teaser_text: 'Өзіңіздегі жұмыс ағымын автоматтандырғыңыз келеді ме? Жетілдірілген тақталар Enterprise қосымшасы болып келеді. Ақылы жоспарға өтуіңізді сұраймыз.' upgrade: 'Upgrade now' lists: delete: 'Delete list' @@ -62,7 +62,7 @@ kk: status: Status version: Version subproject: Subproject - subtasks: Parent-child + subtasks: Бас-бағыныңқы basic: Basic select_attribute: "Action attribute" add_list_modal: diff --git a/modules/boards/config/locales/crowdin/kk.yml b/modules/boards/config/locales/crowdin/kk.yml index 76ed0a750cc2..d60b8773affa 100644 --- a/modules/boards/config/locales/crowdin/kk.yml +++ b/modules/boards/config/locales/crowdin/kk.yml @@ -1,8 +1,8 @@ #English strings go here kk: plugin_openproject_boards: - name: "OpenProject Boards" - description: "Provides board views." + name: "OpenProject тақталары" + description: "Тақталардың берілген әлпеттері." permission_show_board_views: "View boards" permission_manage_board_views: "Manage boards" project_module_board_view: "Boards" diff --git a/modules/budgets/app/components/budgets/actual_labor_budget_items_component.rb b/modules/budgets/app/components/budgets/actual_labor_budget_items_component.rb index 08c79608ace6..e449c10c873b 100644 --- a/modules/budgets/app/components/budgets/actual_labor_budget_items_component.rb +++ b/modules/budgets/app/components/budgets/actual_labor_budget_items_component.rb @@ -58,7 +58,7 @@ def entry_hours(work_package, entry) end def entry_user(entry) - helpers.avatar(entry.principal, hide_name: false, size: :mini) + helpers.avatar(entry.user, hide_name: false, size: :mini) end def entry_costs(entry) diff --git a/modules/budgets/spec/components/budgets/actual_labor_budget_items_component_spec.rb b/modules/budgets/spec/components/budgets/actual_labor_budget_items_component_spec.rb new file mode 100644 index 000000000000..e371a9e801a9 --- /dev/null +++ b/modules/budgets/spec/components/budgets/actual_labor_budget_items_component_spec.rb @@ -0,0 +1,64 @@ +#-- 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 "rails_helper" + +RSpec.describe Budgets::ActualLaborBudgetItemsComponent, type: :component do + let(:project) do + create( + :project, + enabled_module_names: %i[costs work_package_tracking budgets], + members: { + user => member_role + } + ) + end + + let(:member_role) { create(:project_role, name: "Member", permissions: [:view_time_entries]) } + let(:budget) { create :budget, project: } + let(:work_package) { create :work_package, project:, budget:, author: user } + let(:user) { create :user } + + subject do + described_class.new budget:, project: + end + + before do + login_as user + end + + describe "with time entries" do + let!(:time_entry) { create :time_entry, work_package:, user: } + + it "renders the link to the time entry's user's avatar" do + rendered = render_inline(subject) + + expect(rendered).to have_css("opce-principal[data-title='\"#{user.name}\"']") + end + end +end diff --git a/modules/costs/config/locales/crowdin/ro.yml b/modules/costs/config/locales/crowdin/ro.yml index 3ab544c7d99c..5dd3e9b46714 100644 --- a/modules/costs/config/locales/crowdin/ro.yml +++ b/modules/costs/config/locales/crowdin/ro.yml @@ -41,7 +41,7 @@ ro: spent_costs: "Costuri" spent_units: "Unități consumate" rate: - rate: "Impozit" + rate: "Tarif" user: default_rates: "Tarife" models: @@ -49,7 +49,7 @@ ro: one: "Tipul de cost" few: "Tipuri de costuri" other: "Tipuri de costuri" - rate: "Impozit" + rate: "Evaluează" errors: models: work_package: @@ -73,12 +73,12 @@ ro: caption_materials: "Unități" caption_rate_history: "Istoricul ratei" caption_rate_history_for: "Istoric al ratei pentru %{user}" - caption_rate_history_for_project: "Istoricul ratei pentru %{user} în proiectul %{project}" - caption_save_rate: "Salvează rata" - caption_set_rate: "Setați rata curentă" + caption_rate_history_for_project: "Istoricul cotei pentru %{user} în proiectul %{project}" + caption_save_rate: "Salvează tarif" + caption_set_rate: "Setează tarif curent" caption_show_locked: "Afișați tipurile blocate" - description_date_for_new_rate: "Data pentru noua rată" - group_by_others: "Selectați ce tipuri de grup este permis acest tip de profil. (Lăsați toate marcate pentru a permite crearea oricărui tip de grup.)" + description_date_for_new_rate: "Data pentru noul tarif" + group_by_others: "nici într-un grup" label_between: "între" label_cost_filter_add: "Adăugați un filtru de intrare a costurilor" label_costlog: "Costuri unitare înregistrate" @@ -88,7 +88,7 @@ ro: label_costs_per_page: "Costuri pe pagină" label_currency: "Moneda" label_currency_format: "Formatul monedei" - label_current_default_rate: "Rata actuală de neplată" + label_current_default_rate: "Tarif implicit actual" label_date_on: "pe" label_deleted_cost_types: "Tipuri de costuri șterse" label_locked_cost_types: "Tipuri de costuri blocate" @@ -109,7 +109,7 @@ ro: label_no: "Nu" label_option_plural: "Opțiuni" label_overall_costs: "Costuri totale" - label_rate: "Impozit" + label_rate: "Evaluează" label_rate_plural: "Tarife" label_status_finished: "Finalizat" label_units: "Unități de cost" @@ -125,13 +125,13 @@ ro: permission_edit_own_cost_entries: "Editați propriile costuri unitare contabilizate" permission_edit_hourly_rates: "Editați tarifele orare" permission_edit_own_hourly_rate: "Editați propriile tarife orare" - permission_edit_rates: "Editarea ratelor" + permission_edit_rates: "Editează tarife" permission_log_costs: "Costurile unitare ale cărților" permission_log_own_costs: "Costurile unitare de contabilizare pentru sine" permission_view_cost_entries: "Vizualizați costurile rezervate" - permission_view_cost_rates: "Vizualizați tarifele de cost" - permission_view_hourly_rates: "Toate pachetele de lucru" - permission_view_own_cost_entries: "Vizualizați propriile costuri înregistrate" + permission_view_cost_rates: "Vizualizează tarifele" + permission_view_hourly_rates: "Vezi toate tarifele orare" + permission_view_own_cost_entries: "Vizualizează propriile costuri înregistrate" permission_view_own_hourly_rate: "Vezi propriul tarif orar" permission_view_own_time_entries: "Vizualizați propriul timp petrecut" project_module_costs: "Timp și costuri" diff --git a/modules/ldap_groups/config/locales/crowdin/ro.yml b/modules/ldap_groups/config/locales/crowdin/ro.yml index cac477bd99f4..2df085c35361 100644 --- a/modules/ldap_groups/config/locales/crowdin/ro.yml +++ b/modules/ldap_groups/config/locales/crowdin/ro.yml @@ -64,7 +64,7 @@ ro: plural: 'Grupuri LDAP sincronizate' singular: 'Grup LDAP sincronizat' form: - auth_source_text: 'Selectați ce conexiune LDAP trebuie utilizată.' + auth_source_text: 'Selectează ce conexiune LDAP trebuie utilizată.' sync_users_text: > Dacă activați această opțiune, utilizatorii găsiți vor fi, de asemenea, creați automat în OpenProject. Fără această opțiune, doar conturile existente în OpenProject vor fi adăugate la grupuri. dn_text: 'Introduceți DN-ul complet al grupului în LDAP' diff --git a/modules/meeting/spec/factories/meeting_agenda_item_journal_factory.rb b/modules/meeting/spec/factories/meeting_agenda_item_journal_factory.rb new file mode 100644 index 000000000000..e57694bcf6fb --- /dev/null +++ b/modules/meeting/spec/factories/meeting_agenda_item_journal_factory.rb @@ -0,0 +1,31 @@ +#-- 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. +#++ + +FactoryBot.define do + factory :journal_meeting_agenda_item_journal, class: "Journal::MeetingAgendaItemJournal" +end diff --git a/modules/meeting/spec/services/principals/replace_references_service_call_integration_spec.rb b/modules/meeting/spec/services/principals/replace_references_service_call_integration_spec.rb index 1a46851cc7d0..a3156bf03e54 100644 --- a/modules/meeting/spec/services/principals/replace_references_service_call_integration_spec.rb +++ b/modules/meeting/spec/services/principals/replace_references_service_call_integration_spec.rb @@ -30,38 +30,32 @@ require "spec_helper" require_module_spec_helper +require Rails.root.join("spec/services/principals/replace_references_context") RSpec.describe Principals::ReplaceReferencesService, "#call", type: :model do - shared_let(:principal) { create(:user) } - shared_let(:to_principal) { create(:user) } - subject(:service_call) { instance.call(from: principal, to: to_principal) } + shared_let(:other_user) { create(:user, firstname: "other user") } + shared_let(:principal) { create(:user, firstname: "old principal") } + shared_let(:to_principal) { create(:user, firstname: "new principal") } + let(:instance) do described_class.new end - shared_examples "replaces the creator" do - before do - model - end - - it "is successful" do - expect(service_call) - .to be_success - end - - it "replaces principal with to_principal" do - service_call - model.reload + context "with MeetingAgendaItem" do + it_behaves_like "rewritten record", + :meeting_agenda_item, + :author_id - expect(model.author).to eql to_principal - end + it_behaves_like "rewritten record", + :meeting_agenda_item, + :presenter_id end - context "with MeetingAgendaItem" do - it_behaves_like "replaces the creator" do - let(:model) { create(:meeting_agenda_item, author: principal) } - end + context "with Journal::MeetingAgendaItemJournal" do + it_behaves_like "rewritten record", + :journal_meeting_agenda_item_journal, + :author_id end end diff --git a/modules/storages/config/locales/crowdin/js-ro.yml b/modules/storages/config/locales/crowdin/js-ro.yml index afde0e1ccc4b..ad61339388e9 100644 --- a/modules/storages/config/locales/crowdin/js-ro.yml +++ b/modules/storages/config/locales/crowdin/js-ro.yml @@ -11,7 +11,7 @@ ro: login_to: "Autentificare în %{storageType}" no_connection: "Nu există %{storageType} conexiune" open_storage: "Deschide %{storageType}" - select_location: "Selectați locația" + select_location: "Selectează locația" choose_location: "Alege Locația" types: nextcloud: "Nextcloud" @@ -54,8 +54,8 @@ ro: remove_confirmation: > Sigur doriți să deconectați fișierul de la acest pachet de lucru? Deconectarea nu afectează fișierul original și doar elimină conexiunea la acest pachet de lucru. remove_short: "Ștergeți linkul" - select: "Selectați fișierele" - select_all: "Selectați tot" + select: "Selectează fișiere" + select_all: "Selectează tot" selection: zero: "Selectaţi fişierele pentru a conecta" one: "Link 1 fișier" diff --git a/modules/two_factor_authentication/config/locales/crowdin/ro.yml b/modules/two_factor_authentication/config/locales/crowdin/ro.yml index 717e80c0e557..3a3b11bd3c82 100644 --- a/modules/two_factor_authentication/config/locales/crowdin/ro.yml +++ b/modules/two_factor_authentication/config/locales/crowdin/ro.yml @@ -8,7 +8,7 @@ ro: attributes: two_factor_authentication/device: identifier: "Identificator" - default: "URL a paginii dorinței (lăsați gol pentru a utiliza setările implicite)" + default: "Utilizează ca implicit" two_factor_authentication/device/sms: phone_number: "Număr de telefon" errors: diff --git a/script/docs/check_links b/script/docs/check_links index 9bb54e765ced..553eab0878a7 100755 --- a/script/docs/check_links +++ b/script/docs/check_links @@ -141,6 +141,7 @@ class DocsChecker @docs.push( { path: "#{@root_path}/api/v3/spec.json", anchors: [], links: [] }, { path: "#{@root_path}/api/v3/spec.yml", anchors: [], links: [] }, + { path: "#{@root_path}/installation-and-operations/installation/docker-compose", anchors: [], links: [] }, { path: "#{@root_path}/installation-and-operations/installation/helm-chart", anchors: [], links: [] }, { path: "#{@root_path}/development/translate-openproject/fair-language", anchors: [], links: [] } ) diff --git a/spec/contracts/members/delete_contract_spec.rb b/spec/contracts/members/delete_from_project_contract_spec.rb similarity index 98% rename from spec/contracts/members/delete_contract_spec.rb rename to spec/contracts/members/delete_from_project_contract_spec.rb index a3bf312e60e8..9c3d7846504d 100644 --- a/spec/contracts/members/delete_contract_spec.rb +++ b/spec/contracts/members/delete_from_project_contract_spec.rb @@ -29,7 +29,7 @@ require "spec_helper" require "contracts/shared/model_contract_shared_context" -RSpec.describe Members::DeleteContract do +RSpec.describe Members::DeleteFromProjectContract do include_context "ModelContract shared context" let(:contract) { described_class.new(member, current_user) } diff --git a/spec/contracts/members/delete_globally_contract_spec.rb b/spec/contracts/members/delete_globally_contract_spec.rb new file mode 100644 index 000000000000..c260613ce37a --- /dev/null +++ b/spec/contracts/members/delete_globally_contract_spec.rb @@ -0,0 +1,89 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +require "spec_helper" +require "contracts/shared/model_contract_shared_context" + +RSpec.describe Members::DeleteGloballyContract do + include_context "ModelContract shared context" + + let(:contract) { described_class.new(member, current_user) } + let(:member) { build_stubbed(:global_member, roles:, principal:) } + let(:roles) { [build_stubbed(:global_role)] } + let(:principal) { build_stubbed(:user) } + + context "when member is deletable" do + it_behaves_like "contract is valid for active admins and invalid for regular users" + + include_examples "contract reuses the model errors" do + let(:current_user) { build_stubbed(:user) } + end + + context "for admin" do + let(:current_user) { build_stubbed(:admin) } + + it_behaves_like "contract is valid" + end + + context "for regular users" do + let(:current_user) { build_stubbed(:user) } + + before do + mock_permissions_for(current_user) do |mock| + mock.allow_globally(&:allow_everything) + end + end + + it_behaves_like "contract is invalid" + end + end + + context "when member is not deletable" do + before do + allow(member).to receive(:some_roles_deletable?).and_return(false) + end + + context "for admin" do + let(:current_user) { build_stubbed(:admin) } + + it_behaves_like "contract is invalid" + end + + context "for regular user" do + let(:current_user) { build_stubbed(:user) } + + before do + mock_permissions_for(current_user) do |mock| + mock.allow_globally(&:allow_everything) + end + end + + it_behaves_like "contract is invalid" + end + end +end diff --git a/spec/features/global_roles/global_role_crud_spec.rb b/spec/features/global_roles/global_role_crud_spec.rb index 7fbcb68b527f..0731870ec1d1 100644 --- a/spec/features/global_roles/global_role_crud_spec.rb +++ b/spec/features/global_roles/global_role_crud_spec.rb @@ -62,4 +62,18 @@ # Then I should see "Successful creation." expect(page).to have_text "Successful creation." end + + context "with a non-member using dependent project permissions" do + let!(:non_member) { create(:non_member, permissions: %i[view_project_attributes]) } + + it "can still create it (Regression #57906)" do + # When I go to the new page of "Role" + visit new_role_path + check "Global role" + fill_in "Name", with: "Manager" + click_on "Create" + # Then I should see "Successful creation." + expect(page).to have_text "Successful creation." + end + end end diff --git a/spec/features/users/user_memberships_spec.rb b/spec/features/users/user_memberships_spec.rb index 7aadd630045c..8d14f5811842 100644 --- a/spec/features/users/user_memberships_spec.rb +++ b/spec/features/users/user_memberships_spec.rb @@ -39,6 +39,24 @@ current_user { create(:admin) } it_behaves_like "principal membership management flows" + + context "when setting global permissions" do + let(:global_role) { create(:global_role) } + let!(:global_user) { create(:global_member, principal:, roles: [global_role]) } + + it "removes a global user (bug #57928)" do + # Check if user with global role is there + principal_page.visit! + principal_page.open_global_roles_tab! + principal_page.expect_global_roles([global_role.name]) + + # Remove the global role from the user + principal_page.remove_global_role!(global_role.id) + + # Verify that it is gone + principal_page.expect_global_roles([]) + end + end end it_behaves_like "global user principal membership management flows", :manage_user diff --git a/spec/services/members/delete_service_spec.rb b/spec/services/members/delete_service_spec.rb index d3fedf4f552b..a89de4d1ff60 100644 --- a/spec/services/members/delete_service_spec.rb +++ b/spec/services/members/delete_service_spec.rb @@ -31,6 +31,10 @@ RSpec.describe Members::DeleteService, type: :model do it_behaves_like "BaseServices delete service" do + let(:contract_class) do + "#{namespace}::DeleteFromProjectContract".constantize + end + let(:principal) { user } before do model_instance.principal = principal @@ -142,4 +146,37 @@ end end end + + context "when deleting global role memberships" do + let(:user) { create(:user) } + let(:global_role) { create(:global_role) } + let!(:membership) { create(:global_member, principal: user, roles: [global_role]) } + + subject { described_class.new(user: current_user, model: membership) } + + context "as an admin" do + let(:current_user) { create(:admin) } + + it "deletes them" do + expect(membership.member_roles.map(&:role_id)).to eq([global_role.id]) + + subject.call + + expect(membership.member_roles.map(&:role_id)).to be_empty + end + end + + context "as a non-admin user" do + let(:current_user) { create(:user) } + + it "does not delete them" do + expect(membership.member_roles.map(&:role_id)).to eq([global_role.id]) + + result = subject.call + expect(result).not_to be_success + + expect(membership.member_roles.map(&:role_id)).to eq([global_role.id]) + end + end + end end diff --git a/spec/services/principals/replace_references_context.rb b/spec/services/principals/replace_references_context.rb new file mode 100644 index 000000000000..2c9c0537e720 --- /dev/null +++ b/spec/services/principals/replace_references_context.rb @@ -0,0 +1,88 @@ +#-- 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. +#++ + +RSpec.shared_examples_for "rewritten record" do |factory, attribute, format = Integer| + let!(:model) do + klass = FactoryBot.factories.find(factory).build_class + all_attributes = other_attributes.merge(attribute => principal_id) + all_attributes[:created_at] ||= "NOW()" if klass.column_names.include?("created_at") + all_attributes[:updated_at] ||= "NOW()" if klass.column_names.include?("updated_at") + + inserted = ActiveRecord::Base.connection.select_one <<~SQL.squish + INSERT INTO #{klass.table_name} + (#{all_attributes.keys.join(', ')}) + VALUES + (#{all_attributes.values.join(', ')}) + RETURNING id + SQL + + klass.find(inserted["id"]) + end + + let(:other_attributes) do + defined?(attributes) ? attributes : {} + end + + def expected(user, format) + if format == String + user.id.to_s + else + user.id + end + end + + context "for #{factory}" do + context "when #{attribute} is set to the replaced user" do + let(:principal_id) { principal.id } + + before do + service_call + model.reload + end + + it "replaces its value" do + expect(model.send(attribute)) + .to eql expected(to_principal, format) + end + end + + context "when #{attribute} is set to a different user" do + let(:principal_id) { other_user.id } + + before do + service_call + model.reload + end + + it "keeps its value" do + expect(model.send(attribute)) + .to eql expected(other_user, format) + end + end + end +end diff --git a/spec/services/principals/replace_references_service_call_integration_spec.rb b/spec/services/principals/replace_references_service_call_integration_spec.rb index eed56ed882dc..5daffad06e58 100644 --- a/spec/services/principals/replace_references_service_call_integration_spec.rb +++ b/spec/services/principals/replace_references_service_call_integration_spec.rb @@ -27,6 +27,7 @@ #++ require "spec_helper" +require_relative "replace_references_context" RSpec.describe Principals::ReplaceReferencesService, "#call", type: :model do subject(:service_call) { instance.call(from: principal, to: to_principal) } @@ -82,65 +83,6 @@ end end - shared_examples_for "rewritten record" do |factory, attribute, format = Integer| - let!(:model) do - klass = FactoryBot.factories.find(factory).build_class - all_attributes = other_attributes.merge(attribute => principal_id) - - inserted = ActiveRecord::Base.connection.select_one <<~SQL.squish - INSERT INTO #{klass.table_name} - (#{all_attributes.keys.join(', ')}) - VALUES - (#{all_attributes.values.join(', ')}) - RETURNING id - SQL - - klass.find(inserted["id"]) - end - - let(:other_attributes) do - defined?(attributes) ? attributes : {} - end - - def expected(user, format) - if format == String - user.id.to_s - else - user.id - end - end - - context "for #{factory}" do - context "with the replaced user" do - let(:principal_id) { principal.id } - - before do - service_call - model.reload - end - - it "replaces #{attribute}" do - expect(model.send(attribute)) - .to eql expected(to_principal, format) - end - end - - context "with a different user" do - let(:principal_id) { other_user.id } - - before do - service_call - model.reload - end - - it "keeps #{attribute}" do - expect(model.send(attribute)) - .to eql expected(other_user, format) - end - end - end - end - context "with Attachment" do it_behaves_like "rewritten record", :attachment, @@ -258,9 +200,7 @@ def expected(user, format) :meeting_agenda, :author_id do let(:attributes) do - { type: "'MeetingAgenda'", - created_at: "NOW()", - updated_at: "NOW()" } + { type: "'MeetingAgenda'" } end end @@ -268,32 +208,19 @@ def expected(user, format) :meeting_minutes, :author_id do let(:attributes) do - { type: "'MeetingMinutes'", - created_at: "NOW()", - updated_at: "NOW()" } + { type: "'MeetingMinutes'" } end end it_behaves_like "rewritten record", :journal_meeting_content_journal, - :author_id do - let(:attributes) do - {} - end - end + :author_id end context "with MeetingParticipant" do it_behaves_like "rewritten record", :meeting_participant, - :user_id do - let(:attributes) do - { - created_at: "NOW()", - updated_at: "NOW()" - } - end - end + :user_id end context "with News" do @@ -330,8 +257,7 @@ def expected(user, format) it_behaves_like "rewritten record", :journal_wiki_page_journal, - :author_id do - end + :author_id end context "with WorkPackage" do @@ -525,9 +451,7 @@ def expected(user, format) { recipient_id: user.id, resource_id: 1234, - resource_type: "'WorkPackage'", - created_at: "NOW()", - updated_at: "NOW()" + resource_type: "'WorkPackage'" } end end @@ -542,9 +466,7 @@ def expected(user, format) name: "'foo'", uid: "'bar'", secret: "'bar'", - redirect_uri: "'urn:whatever'", - created_at: "NOW()", - updated_at: "NOW()" + redirect_uri: "'urn:whatever'" } end end diff --git a/spec/support/pages/admin/individual_principals/edit.rb b/spec/support/pages/admin/individual_principals/edit.rb index d9accf52db7f..cfd6bd594878 100644 --- a/spec/support/pages/admin/individual_principals/edit.rb +++ b/spec/support/pages/admin/individual_principals/edit.rb @@ -111,6 +111,25 @@ def select_project!(project_name) results_selector: "body" end + def open_global_roles_tab! + within(".PageHeader-tabNav") do + click_on "Global roles" + end + end + + def expect_global_roles(roles) + roles_in_on_page = page.find_all("#table_principal_roles tr td.role") + + expect(roles_in_on_page.map(&:text)).to eq(roles) + end + + def remove_global_role!(role_id) + within("#table_principal_roles") do + role_tr = find("#assigned_global_role_#{role_id}") + role_tr.find("a[data-test-selector='delete-global-role']").click + end + end + def activate! within ".toolbar-items" do click_button "Activate"