From c7a2851ebf04517e560659ad27511437aea0776d Mon Sep 17 00:00:00 2001 From: OpenProject Actions CI Date: Mon, 18 Nov 2024 03:19:38 +0000 Subject: [PATCH 1/4] update locales from crowdin [ci skip] --- config/locales/crowdin/js-ro.yml | 18 ++++----- config/locales/crowdin/pt-BR.seeders.yml | 4 +- config/locales/crowdin/ro.yml | 38 +++++++++---------- config/locales/crowdin/sv.yml | 2 +- .../calendar/config/locales/crowdin/js-ro.yml | 2 +- .../calendar/config/locales/crowdin/ro.yml | 2 +- modules/meeting/config/locales/crowdin/ro.yml | 2 +- .../config/locales/crowdin/js-ro.yml | 8 ++-- .../config/locales/crowdin/ro.yml | 6 +-- .../config/locales/crowdin/ro.yml | 2 +- 10 files changed, 42 insertions(+), 42 deletions(-) diff --git a/config/locales/crowdin/js-ro.yml b/config/locales/crowdin/js-ro.yml index 3046243e3ff..ea8bf5f859a 100644 --- a/config/locales/crowdin/js-ro.yml +++ b/config/locales/crowdin/js-ro.yml @@ -97,7 +97,7 @@ ro: button_show_table: "Afișați vizualizarea tabelului" button_show_gantt: "Arată vizualizarea Gantt" button_show_fullscreen: "Arată vizualizarea pe tot ecranul" - button_more_actions: "Mai multe actiuni" + button_more_actions: "Mai multe acțiuni" button_quote: "Citat" button_save: "Salvează" button_settings: "Setări" @@ -239,7 +239,7 @@ ro: upsale: become_hero: "Deveniți un erou!" enterprise_info_html: "%{feature_title} is an Enterprise add-on." - upgrade_info: "Vă rugăm să treceți la un plan plătit pentru a-l activa și a începe să îl utilizați în echipa dumneavoastră." + upgrade_info: "Te rog să treci la un plan plătit pentru a-l activa și a începe să îl utilizezi în echipa ta." benefits: description: "Care sunt avantajele ediției Enterprise on-premise?" high_security: "Caracteristici de securitate" @@ -252,7 +252,7 @@ ro: professional_support_text: "Beneficiați de asistență fiabilă, de înaltă calitate, din partea unor ingineri de asistență seniori cu cunoștințe de specialitate în ceea ce privește utilizarea OpenProject în medii critice pentru afaceri." button_start_trial: "Începeți o încercare gratuită" button_upgrade: "Actualizează acum" - button_contact_us: "Contactați-ne pentru o demonstrație" + button_contact_us: "Contactează-ne pentru o demonstrație" button_book_now: "Rezervă acum" confidence: > Oferim încrederea unui software de gestionare a proiectelor de clasă enterprise testat și susținut - cu Open Source și o minte deschisă. @@ -444,7 +444,7 @@ ro: label_in_more_than: "în mai mult de" label_incoming_emails: "E-mailuri primite" label_information_plural: "Informații" - label_invalid: "Nevalid" + label_invalid: "Invalid" label_import: "Importă" label_latest_activity: "Activitate recentă" label_last_updated_on: "Ultima actualizare la data de" @@ -655,14 +655,14 @@ ro: settings: change_notification_settings: 'Poți modifica setările de notificare pentru a te asigura că nu pierzi niciodată o actualizare importantă.' title: "Setări notificare" - notify_me: "Anunta-ma" + notify_me: "Notifică-mă" reminders: no_notification: Nicio notificare timeframes: normal: PT0S: în aceeași zi - P1D: Cu 1 zi înainte - P3D: Cu 3 zile înainte + P1D: cu 1 zi înainte + P3D: cu 3 zile înainte P7D: ' cu o săptămână înainte ' overdue: P1D: zilnic @@ -671,7 +671,7 @@ ro: reasons: mentioned: title: "Menţionat" - description: "Primește o notificare de fiecare dată când cineva mă menționează oriunde" + description: "Primesc o notificare de fiecare dată când cineva mă menționează oriunde" assignee: "Executant" responsible: "Responsabil" shared: "Partajat" @@ -679,7 +679,7 @@ ro: work_package_commented: "Toate comentariile noi" work_package_created: "Pachete de lucru noi" work_package_processed: "Toate modificările de stare" - work_package_prioritized: "Toate modificările prioritare" + work_package_prioritized: "Toate modificările de prioritate" work_package_scheduled: "Toate modificările dății" global: immediately: diff --git a/config/locales/crowdin/pt-BR.seeders.yml b/config/locales/crowdin/pt-BR.seeders.yml index 027152e8b1b..517fde5c705 100644 --- a/config/locales/crowdin/pt-BR.seeders.yml +++ b/config/locales/crowdin/pt-BR.seeders.yml @@ -210,9 +210,9 @@ pt-BR: Neste módulo, você pode compartilhar as atualizações do projeto com os membros da sua equipe. versions: item_0: - name: Registro de erros e pendências + name: Backlog de Bugs item_1: - name: Erros e Pendências do Produto + name: Backlog de produtos item_2: name: Sprint 1 wiki: diff --git a/config/locales/crowdin/ro.yml b/config/locales/crowdin/ro.yml index be3bc92af43..a353e62f37b 100644 --- a/config/locales/crowdin/ro.yml +++ b/config/locales/crowdin/ro.yml @@ -88,7 +88,7 @@ ro: get_quote: "Obțineți o ofertă" buttons: upgrade: "Actualizează acum" - contact: "Contactați-ne pentru o demonstrație" + contact: "Contactați-ne pentru un demo" enterprise_info_html: "is an Enterprise add-on." upgrade_info: "Vă rugăm să treceți la un plan plătit pentru a-l activa și a începe să îl utilizați în echipa dumneavoastră." jemalloc_allocator: Jemalloc memory allocator @@ -498,7 +498,7 @@ ro: irreversible: "Această acțiune este ireversibilă" confirmation: "Introduceți numele de utilizator de tip placeholder %{name} pentru a confirma ștergerea." upsale: - title: Utilizatori de tip Placeholder + title: Utilizatori Placeholder description: > Utilizatorii de tip substituție sunt o modalitate de a atribui pachete de lucru utilizatorilor care nu fac parte din proiectul tău. Acesta poate fi utilă într-o serie de scenarii; de exemplu, dacă trebuie să urmărești sarcinile pentru o resursă care nu este încă numită sau disponibilă, sau dacă nu vrei să acorzi acestei persoane acces la OpenProject dar totuși vrei să urmărești sarcinile care le-au fost atribuite. prioritiies: @@ -587,11 +587,11 @@ ro: few: "%{count} descendenți ai pachetului de lucru" other: "%{count} pachete de lucru descendente" bulk: - copy_failed: "Pachetele de lucru nu au putut fi copiate." - move_failed: "Pachetele de lucru nu au putut fi mutate." + copy_failed: "Pachetele de lucru nu pot fi copiate." + move_failed: "Pachetele de lucru nu pot fi mutate." could_not_be_saved: "Următoarele pachete de lucru nu au putut fi salvate:" - none_could_be_saved: "Niciunul dintre pachetele de lucru %{total} nu a putut fi actualizat." - x_out_of_y_could_be_saved: "%{failing} din %{total} pachete de lucru nu au putut fi actualizate în timp ce %{success} ar putea fi actualizat." + none_could_be_saved: "Niciunul dintre pachetele de lucru %{total} nu poate fi actualizat." + x_out_of_y_could_be_saved: "%{failing} din %{total} pachete de lucru nu pot fi actualizate în timp ce %{success} poate fi actualizat." selected_because_descendants: "While %{selected} work packages were selected, in total %{total} work packages are affected which includes descendants." descendant: "descendent de selectat" move: @@ -626,7 +626,7 @@ ro: account: delete: "Ştergere cont" delete_confirmation: "Sunteţi sigur că doriţi să ştergeţi contul?" - deletion_pending: "Contul a fost blocat și a fost programat pentru ștergere. Rețineți că acest proces are loc în fundal. Poate dura câteva momente până când utilizatorul este complet șters." + deletion_pending: "Contul a fost blocat și a fost programat pentru ștergere. Reține că acest proces are loc în fundal. Poate dura câteva momente până când utilizatorul este complet șters." deletion_info: data_consequences: other: 'Vor fi șterse cât mai multe din datele create de utilizator (e.g. email, preferințe, pachete de lucru, contribuții wiki). De reținut, totuși, că date precum pachete de lucru și contribuții wiki nu pot fi șterse fără a afecta activitatea altor utilizatori. Prin urmare, astfel de date sunt realocate la un cont numit "Utilizator șters". Deoarece datele fiecărui cont șters sunt realocate acestui cont, nu va mai fi posibil să se facă distincția între datele create de utilizator și datele altui cont șters.' @@ -1008,7 +1008,7 @@ ro: foreign_wps_reference_version: "Pachetele de lucru din proiectele care nu sunt descendente fac trimitere la versiuni ale proiectului sau la descendenți ai acestuia." attributes: base: - archive_permission_missing_on_subprojects: "Nu aveţi permisiunile necesare pentru a arhiva toate sub-proiectele. Vă rugăm să contactaţi un administrator." + archive_permission_missing_on_subprojects: "Nu ai permisiunile necesare pentru a arhiva toate sub-proiectele. Te rog să contactezi un administrator." types: in_use_by_work_packages: "încă în uz în pachetele de lucru: %{types}" enabled_modules: @@ -2532,7 +2532,7 @@ ro: label_system_storage: "Informații privind stocarea" label_table_of_contents: "Cuprins" label_tag: "Etichetă" - label_team_planner: "Team Planner" + label_team_planner: "Planificare echipă" label_text: "Text lung" label_this_month: "luna curentă" label_this_week: "săptămâna curentă" @@ -2772,10 +2772,10 @@ ro: mail_body_register_header_title: "E-mail de invitație pentru membrii proiectului" mail_body_register_user: "Dragă %{name}," mail_body_register_links_html: | - Vă rugăm să nu ezitați să navigați pe canalul nostru de youtube (%{youtube_link}) unde oferim un webinar (%{webinar_link}) + Nu ezita să navighezi pe canalul nostru de youtube (%{youtube_link}) unde oferim un webinar (%{webinar_link}) și videoclipuri cu “Începe” (%{get_started_link}) pentru a face primii pași în OpenProject cât mai ușor posibil.
- Dacă aveți orice întrebări suplimentare, consultați documentația noastră (%{documentation_link}) sau contactați administratorul. + Dacă ai orice întrebări suplimentare, consultă documentația noastră (%{documentation_link}) sau contactează administratorul. mail_body_register_closing: "Echipa dumneavoastră OpenProject" mail_body_register_ending: "Rămâneți conectați! Vă salutăm cu drag," mail_body_reminder: "%{count} pachete de lucru care vă sunt atribuite au scadență în următoarele %{days} zile:" @@ -3335,7 +3335,7 @@ ro: If this option is active, login requests will redirect to the configured omniauth provider. The login dropdown and sign-in page will be disabled.
Note: Unless you also disable password logins, with this option enabled, users can still log in internally by visiting the %{internal_path} login page. attachments: whitelist_text_html: > - Definește o listă validă de extensii de fișiere și/sau tipuri mime pentru fișierele încărcate.
Introduceți extensii de fișiere (de ex. %{ext_example}) sau tipuri mime (e. ., %{mime_example}).
Lăsați gol pentru a permite oricărui tip de fișier să fie încărcat. Valori multiple permise (o linie pentru fiecare valoare). + Definește o listă validă de extensii de fișiere și/sau tipuri mime pentru fișierele încărcate.
Introdu extensii de fișiere (de ex. %{ext_example}) sau tipuri mime (e. ., %{mime_example}).
Lasă gol pentru a permite oricărui tip de fișier să fie încărcat. Valori multiple permise (o linie pentru fiecare valoare). show_work_package_attachments: > Deactivating this option will hide the attachments list on the work packages files tab for new projects. The files attached in the description of a work package will still be uploaded in the internal attachments storage. antivirus: @@ -3429,7 +3429,7 @@ ro: text_access_token_hint: "Tichetele de acces permit aplicaţiilor externe accesul la resurse din OpenProject." text_analyze: "Continuare analiză: %{subject}" text_are_you_sure: "Sunteți sigur?" - text_are_you_sure_continue: "Sigur doriţi să continuați?" + text_are_you_sure_continue: "Sigur dorești să continui?" text_are_you_sure_with_children: "Doriți ștergerea pachetului de lucru și a tuturor pachetelor fii?" text_are_you_sure_with_project_custom_fields: "Deleting this attribute will also delete its values in all projects. Are you sure you want to do this?" text_assign_to_project: "Alocare la proiect" @@ -3791,7 +3791,7 @@ ro: work_package: "Pachetul de lucru pe care îl căutați nu poate fi găsit sau a fost șters." expected: date: "AAAA-MM-ZZ (doar formate ISO 8601)" - datetime: "AAAA-LL-ZZZZH:mm:ss[.lll][+h:mm] (orice date compatibile ISO 8601 datetime)" + datetime: "AAAA-LL-ZZZZH:mm:ss[.lll][+h:mm] (orice dăți compatibile ISO 8601 dată-oră)" duration: "Durată ISO 8601" invalid_content_type: "CONTENT-TYPE așteptat '%{content_type}', dar am primit '%{actual}'." invalid_format: "Format invalid pentru proprietatea '%{property}': format așteptat '%{expected_format}', dar am primit '%{actual}'." @@ -3836,7 +3836,7 @@ ro: #Common error messages invalid_request: unknown: "În cerere lipsește un parametru obligatoriu, include o valoare de parametru neacceptată sau este deformată în alt mod." - missing_param: "Lipseste parametrul necesar: %{value}." + missing_param: "Lipsește parametrul obligatoriu: %{value}." request_not_authorized: "Solicitarea trebuie să fie autorizată. Parametrul necesar pentru solicitarea de autorizare lipsește sau este invalid." invalid_redirect_uri: "URL-ul de redirecționare solicitat este deformat sau nu se potrivește cu URL-ul de redirecționare al clientului." unauthorized_client: "Clientul nu este autorizat să efectueze această cerere folosind această metodă." @@ -3930,12 +3930,12 @@ ro: urn_connection_status: connected: "Conectat" error: "Eroare" - failed_authorization: "Autorizarea nu a reuşit" + failed_authorization: "Autorizare nereușită" labels: label_oauth_integration: "Integrare OAuth2" - label_redirect_uri: "Redirecționați URI" - label_request_token: "Token de cerere" - label_refresh_token: "Reîmprospătare token" + label_redirect_uri: "URI redirecționare" + label_request_token: "Token cerere" + label_refresh_token: "Token reîmprospătare" errors: oauth_authorization_code_grant_had_errors: "OAuth2 Authorization grant unsuccessful" oauth_reported: "Furnizor OAuth2 raportat" diff --git a/config/locales/crowdin/sv.yml b/config/locales/crowdin/sv.yml index d3c3c2aa198..dc6c99dcbde 100644 --- a/config/locales/crowdin/sv.yml +++ b/config/locales/crowdin/sv.yml @@ -266,7 +266,7 @@ sv: filled?: "must be filled" not_unique: "must be unique within the same hierarchy level" rules: - label: "Label" + label: "Etikett" global_search: placeholder: "Search in %{app_title}" overwritten_tabs: diff --git a/modules/calendar/config/locales/crowdin/js-ro.yml b/modules/calendar/config/locales/crowdin/js-ro.yml index ced4dcf1b8f..c970dc3245a 100644 --- a/modules/calendar/config/locales/crowdin/js-ro.yml +++ b/modules/calendar/config/locales/crowdin/js-ro.yml @@ -2,7 +2,7 @@ ro: js: calendar: - create_new: 'Creați un calendar nou' + create_new: 'Creează un calendar nou' title: 'Calendar' too_many: 'Există %{count} pachete de lucru în total, dar numai %{max} poate fi afișat.' unsaved_title: 'Calendar fără nume' diff --git a/modules/calendar/config/locales/crowdin/ro.yml b/modules/calendar/config/locales/crowdin/ro.yml index e17a56839dc..bb084898069 100644 --- a/modules/calendar/config/locales/crowdin/ro.yml +++ b/modules/calendar/config/locales/crowdin/ro.yml @@ -7,6 +7,6 @@ ro: label_calendar_plural: "Calendare" label_new_calendar: "New calendar" permission_view_calendar: "Vezi calendarele" - permission_manage_calendars: "Gestionare calendare" + permission_manage_calendars: "Gestionează calendare" permission_share_calendars: "Subscribe to iCalendars" project_module_calendar_view: "Calendare" diff --git a/modules/meeting/config/locales/crowdin/ro.yml b/modules/meeting/config/locales/crowdin/ro.yml index afe45f003ad..d696519184c 100644 --- a/modules/meeting/config/locales/crowdin/ro.yml +++ b/modules/meeting/config/locales/crowdin/ro.yml @@ -62,7 +62,7 @@ ro: meeting_section: "Section" activity: filter: - meeting: "Reuniuni" + meeting: "Întâlniri" item: meeting_agenda_item: duration: diff --git a/modules/team_planner/config/locales/crowdin/js-ro.yml b/modules/team_planner/config/locales/crowdin/js-ro.yml index a8960cd99bc..2a4b159aaec 100644 --- a/modules/team_planner/config/locales/crowdin/js-ro.yml +++ b/modules/team_planner/config/locales/crowdin/js-ro.yml @@ -5,10 +5,10 @@ ro: add_existing: 'Adaugă existent' add_existing_title: 'Adăugarea pachetelor de lucru existente' create_label: 'Planificator echipă' - create_title: 'Creați un nou planificator de echipă' + create_title: 'Creează planificare echipă nouă' unsaved_title: 'Planificator de echipă nenumit' no_data: 'Adăugați persoane desemnate pentru a vă configura planificatorul echipei.' - add_assignee: 'Assignee' + add_assignee: 'Executant' remove_assignee: 'Înlătură responsabil' two_weeks: '2-săptămână' one_week: '1-săptămână' @@ -18,9 +18,9 @@ ro: today: 'Azi' drag_here_to_remove: 'Trageți aici pentru a elimina responsabilul și a începe și a termina datele.' cannot_drag_here: 'Nu se poate elimina pachetul de lucru din cauza permisiunilor sau restricțiilor de editare.' - cannot_drag_to_non_working_day: 'Acest pachet de lucru nu poate începe/încheia o zi nelucrătoare.' + cannot_drag_to_non_working_day: 'Acest pachet de lucru nu poate să înceapă/se încheie într-o zi nelucrătoare.' quick_add: - empty_state: 'Utilizați câmpul de căutare pentru a găsi pachete de lucru și trageți-le la planificator pentru a-l atribui cuiva și defini datele de început și de sfârșit.' + empty_state: 'Utilizează câmpul de căutare pentru a găsi pachete de lucru și trage-le în planificare pentru a-l atribui cuiva și defini datele de început și de sfârșit.' search_placeholder: 'Caută...' modify: errors: diff --git a/modules/team_planner/config/locales/crowdin/ro.yml b/modules/team_planner/config/locales/crowdin/ro.yml index 7bd738200e0..1b3c3660aa0 100644 --- a/modules/team_planner/config/locales/crowdin/ro.yml +++ b/modules/team_planner/config/locales/crowdin/ro.yml @@ -1,16 +1,16 @@ #English strings go here ro: plugin_openproject_team_planner: - name: "OpenProject Team Planner" + name: "Planificare echipă OpenProject" description: "Provides team planner views." permission_view_team_planner: "Vezi planificatorul echipei" permission_manage_team_planner: "Gestionează planificatorul de echipe" - project_module_team_planner_view: "Planificatori echipă" + project_module_team_planner_view: "Planificare echipă" team_planner: label_team_planner: "Planificator echipă" label_new_team_planner: "Planificator echipă nou" label_create_new_team_planner: "Creează planificator echipă nou" - label_team_planner_plural: "Planificatori echipe" + label_team_planner_plural: "Planificare echipă" label_assignees: "Responsabili" upsale: title: "Planificator echipă" diff --git a/modules/two_factor_authentication/config/locales/crowdin/ro.yml b/modules/two_factor_authentication/config/locales/crowdin/ro.yml index e103b606d4f..e6ca7a61979 100644 --- a/modules/two_factor_authentication/config/locales/crowdin/ro.yml +++ b/modules/two_factor_authentication/config/locales/crowdin/ro.yml @@ -79,7 +79,7 @@ ro: text_2fa_disabled: "The user did not set up a 2FA device through their 'My account page'" only_sms_allowed: "Only SMS delivery can be set up for other users." upsale: - title: "Autentificare cu doi factori" + title: "Autentificare doi factori" description: "Consolidarea securității instanței OpenProject prin oferirea (sau impunerea) autentificării cu doi factori pentru toți membrii proiectului." backup_codes: none_found: Nu există coduri de rezervă pentru acest cont. From e498f67203ce58fdcbf6958276c25c5bb37d50ee Mon Sep 17 00:00:00 2001 From: jjabari-op <122434454+jjabari-op@users.noreply.github.com> Date: Mon, 18 Nov 2024 09:21:21 +0100 Subject: [PATCH 2/4] Bug/59281 split screen activity tab does not scroll to the right position from list view (#17210) https://community.openproject.org/work_packages/59281 --- .../work-packages/activities-tab/index.controller.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 cf2cc74fa9b..c3a7cd3a307 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 @@ -398,7 +398,7 @@ export default class IndexController extends Controller { } private getScrollableContainer():HTMLElement | null { - if (this.isWithinNotificationCenter()) { + if (this.isWithinNotificationCenter() || this.isWithinSplitScreen()) { // valid for both mobile and desktop return document.querySelector('.work-package-details-tab') as HTMLElement; } @@ -419,6 +419,10 @@ export default class IndexController extends Controller { return window.location.pathname.includes(this.notificationCenterPathNameValue); } + private isWithinSplitScreen():boolean { + return window.location.pathname.includes('work_packages/details'); + } + private addEventListenersToCkEditorInstance() { this.onSubmitBound = () => { void this.onSubmit(); }; this.adjustMarginBound = () => { void this.adjustJournalContainerMargin(); }; From 79fe6f772a734484ce958128e1a37c26fd1e881c Mon Sep 17 00:00:00 2001 From: jjabari-op <122434454+jjabari-op@users.noreply.github.com> Date: Mon, 18 Nov 2024 09:30:10 +0100 Subject: [PATCH 3/4] Bug/59279 the comment box remains open and does not blur on submitting comment (#17209) https://community.openproject.org/work_packages/59279 --- .../activities-tab/index.controller.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) 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 c3a7cd3a307..8f8a7d36015 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 @@ -647,8 +647,8 @@ export default class IndexController extends Controller { if (!this.journalsContainerTarget) return; this.clearEditor(); - this.handleEditorVisibility(); - this.adjustJournalsContainer(); + this.hideEditor(); + this.resetJournalsContainerMargins(); setTimeout(() => { if (this.isMobile() && !this.isWithinNotificationCenter()) { @@ -667,19 +667,11 @@ export default class IndexController extends Controller { this.saveInProgress = false; } - private handleEditorVisibility():void { - if (this.isMobile()) { - this.hideEditorIfEmpty(); - } else { - this.focusEditor(); - } - } - - private adjustJournalsContainer():void { + private resetJournalsContainerMargins():void { if (!this.journalsContainerTarget) return; this.journalsContainerTarget.style.marginBottom = ''; - this.journalsContainerTarget.classList.add('work-packages-activities-tab-index-component--journals-container_with-input-compensation'); + this.journalsContainerTarget.classList.add('work-packages-activities-tab-index-component--journals-container_with-initial-input-compensation'); } private setLastServerTimestampViaHeaders(headers:Headers) { From 3d0401e8b22c484734cad96a57fe9738df5e3f0c Mon Sep 17 00:00:00 2001 From: jjabari-op <122434454+jjabari-op@users.noreply.github.com> Date: Mon, 18 Nov 2024 09:50:02 +0100 Subject: [PATCH 4/4] Bug/59065 error toast giving a 500 error without relevant details (#17207) https://community.openproject.org/work_packages/59065 --- .../activities_tab_controller.rb | 71 +++++------ .../app/core/turbo/turbo-requests.service.ts | 15 ++- .../activities-tab/index.controller.ts | 2 +- .../work_package/activities_spec.rb | 110 ++++++++++++++++++ .../components/work_packages/activities.rb | 10 +- 5 files changed, 165 insertions(+), 43 deletions(-) diff --git a/app/controllers/work_packages/activities_tab_controller.rb b/app/controllers/work_packages/activities_tab_controller.rb index cfe19ecc31c..93ac7ba6cde 100644 --- a/app/controllers/work_packages/activities_tab_controller.rb +++ b/app/controllers/work_packages/activities_tab_controller.rb @@ -106,28 +106,33 @@ class WorkPackages::ActivitiesTabController < ApplicationController end def create - call = create_journal_service_call + begin + 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 - @turbo_status = :bad_request + if call.success? && call.result + set_last_server_timestamp_to_headers + handle_successful_create_call(call) + else + handle_failed_create_or_update_call(call) + end + rescue StandardError => e + handle_internal_server_error(e) end respond_with_turbo_streams end def update - call = Journals::UpdateService.new(model: @journal, user: User.current).call( - notes: journal_params[:notes] - ) + begin + call = update_journal_service_call - if call.success? && call.result - update_item_show_component(journal: call.result, grouped_emoji_reactions: grouped_emoji_reactions_for_journal) - else - handle_failed_update_call(call) + if call.success? && call.result + update_item_show_component(journal: call.result, grouped_emoji_reactions: grouped_emoji_reactions_for_journal) + else + handle_failed_create_or_update_call(call) + end + rescue StandardError => e + handle_internal_server_error(e) end respond_with_turbo_streams @@ -182,19 +187,11 @@ class WorkPackages::ActivitiesTabController < ApplicationController # turbo_stream requests (tab is already rendered and an error occured in subsequent requests) are handled below format.turbo_stream do @turbo_status = :not_found - render_error_banner_via_turbo_stream(error_message) + render_error_flash_message_via_turbo_stream(message: error_message) end end end - def render_error_banner_via_turbo_stream(error_message) - update_via_turbo_stream( - component: WorkPackages::ActivitiesTab::ErrorStreamComponent.new( - error_message: - ) - ) - end - def find_work_package @work_package = WorkPackage.find(params[:work_package_id]) rescue ActiveRecord::RecordNotFound @@ -259,22 +256,22 @@ class WorkPackages::ActivitiesTabController < ApplicationController end end - def handle_failed_create_call(call) - update_via_turbo_stream( - component: WorkPackages::ActivitiesTab::Journals::NewComponent.new( - work_package: @work_package, - journal: call.result, - form_hidden_initially: false - ) - ) - end - - def handle_failed_update_call(call) + def handle_failed_create_or_update_call(call) @turbo_status = if call.errors&.first&.type == :error_unauthorized :forbidden else :bad_request end + render_error_flash_message_via_turbo_stream( + message: call.errors&.full_messages&.join(", ") + ) + end + + def handle_internal_server_error(error) + @turbo_status = :internal_server_error + render_error_flash_message_via_turbo_stream( + message: error.message + ) end def replace_whole_tab @@ -306,6 +303,12 @@ class WorkPackages::ActivitiesTabController < ApplicationController ### end + def update_journal_service_call + Journals::UpdateService.new(model: @journal, user: User.current).call( + notes: journal_params[:notes] + ) + end + def generate_time_based_update_streams(last_update_timestamp) journals = @work_package .journals diff --git a/frontend/src/app/core/turbo/turbo-requests.service.ts b/frontend/src/app/core/turbo/turbo-requests.service.ts index da2ecacc074..fdef9024175 100644 --- a/frontend/src/app/core/turbo/turbo-requests.service.ts +++ b/frontend/src/app/core/turbo/turbo-requests.service.ts @@ -13,19 +13,26 @@ export class TurboRequestsService { public request(url:string, init:RequestInit = {}, suppressErrorToast = false):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().then((html) => ({ html, headers: response.headers, + response, })); }) .then((result) => { + // the result may contain a primer error banner if any server side error appeared + // thus we need to render the html even for non-ok responses renderStreamMessage(result.html); - return result; + // after rendering the html, check if the response and throw an error if it's not ok + if (!result.response.ok) { + throw new Error(result.response.statusText); + } else { + // enable further processing of the html and headers in the calling function + return { html: result.html, headers: result.headers }; + } }) .catch((error) => { + // this should only catch errors happening in the client side parsing in the above .then() calls if (!suppressErrorToast) { this.toast.addError(error as string); } else { 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 8f8a7d36015..ef02a67e404 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 @@ -637,7 +637,7 @@ export default class IndexController extends Controller { headers: { 'X-CSRF-Token': (document.querySelector('meta[name="csrf-token"]') as HTMLMetaElement).content, }, - }); + }, true); } private handleSuccessfulSubmission(html:string, headers:Headers) { diff --git a/spec/features/activities/work_package/activities_spec.rb b/spec/features/activities/work_package/activities_spec.rb index 5428863f8fd..9bf18426720 100644 --- a/spec/features/activities/work_package/activities_spec.rb +++ b/spec/features/activities/work_package/activities_spec.rb @@ -27,8 +27,11 @@ #++ require "spec_helper" +require "support/flash/expectations" RSpec.describe "Work package activity", :js, :with_cuprite, with_flag: { primerized_work_package_activities: true } do + include Flash::Expectations + let(:project) { create(:project) } let(:admin) { create(:admin) } let(:member_role) do @@ -1244,4 +1247,111 @@ RSpec.describe "Work package activity", :js, :with_cuprite, with_flag: { primeri end end end + + describe "error handling" do + let(:work_package) { create(:work_package, project:, author: admin) } + + current_user { admin } + + before do + wp_page.visit! + wp_page.wait_for_activity_tab + end + + context "when adding a comment" do + context "when the creation call raises an unknown server error" do + before do + allow_any_instance_of(WorkPackages::ActivitiesTabController) # rubocop:disable RSpec/AnyInstance + .to receive(:create_journal_service_call) + .and_raise(StandardError.new("Test error")) + end + + it "shows an error banner when the server returns an error" do + activity_tab.add_comment(text: "First comment by admin", save: false) + + page.find_test_selector("op-submit-work-package-journal-form").click + + expect_flash(message: "Test error", type: :error) + + # expect the editor content not to be lost + within_test_selector("op-work-package-journal-form-element") do + editor = FormFields::Primerized::EditorFormField.new("notes", selector: "#work-package-journal-form-element") + editor.expect_value("First comment by admin") + end + end + end + + context "when the creation call fails with a validation error" do + before do + allow_any_instance_of(AddWorkPackageNoteService) # rubocop:disable RSpec/AnyInstance + .to receive(:call) + .and_return( + ServiceResult.failure(errors: ActiveModel::Errors.new(Journal.new).tap do |e| + e.add(:notes, "Validation error") + end) + ) + end + + it "shows a validation error banner" do + activity_tab.add_comment(text: "First comment by admin", save: false) + + page.find_test_selector("op-submit-work-package-journal-form").click + + expect_flash(message: "Validation error", type: :error) + + # expect the editor content not to be lost + within_test_selector("op-work-package-journal-form-element") do + editor = FormFields::Primerized::EditorFormField.new("notes", selector: "#work-package-journal-form-element") + editor.expect_value("First comment by admin") + end + end + end + end + + context "when editing a comment" do + let!(:first_comment_by_admin) do + create(:work_package_journal, user: admin, notes: "First comment by admin", journable: work_package, version: 2) + end + + context "when the update call raises an unknown server error" do + before do + allow_any_instance_of(WorkPackages::ActivitiesTabController) # rubocop:disable RSpec/AnyInstance + .to receive(:update_journal_service_call) + .and_raise(StandardError.new("Test error")) + end + + it "shows an error banner" do + activity_tab.edit_comment(first_comment_by_admin, text: "First comment by admin edited", save: false) + + page.within_test_selector("op-work-package-journal-form-element") do + page.find_test_selector("op-submit-work-package-journal-form").click + end + + expect_flash(message: "Test error", type: :error) + end + end + + context "when the update call fails with a validation error" do + before do + allow_any_instance_of(Journals::UpdateService) # rubocop:disable RSpec/AnyInstance + .to receive(:call) + .and_return( + ServiceResult.failure(errors: ActiveModel::Errors.new(Journal.new).tap do |e| + e.add(:notes, "Validation error") + end) + ) + end + + it "shows a validation error banner" do + activity_tab.edit_comment(first_comment_by_admin, text: "First comment by admin edited", save: false) + + page.within_test_selector("op-work-package-journal-form-element") do + page.find_test_selector("op-submit-work-package-journal-form").click + end + + expect_flash(message: "Validation error", type: :error) + end + end + end + end end diff --git a/spec/support/components/work_packages/activities.rb b/spec/support/components/work_packages/activities.rb index 822808fea40..7bc79b6a123 100644 --- a/spec/support/components/work_packages/activities.rb +++ b/spec/support/components/work_packages/activities.rb @@ -175,18 +175,20 @@ module Components end end - def edit_comment(journal, text: nil) + def edit_comment(journal, text: nil, save: true) 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}-edit").click page.within_test_selector("op-work-package-journal-form-element") do FormFields::Primerized::EditorFormField.new("notes", selector: "#work-package-journal-form-element").set_value(text) - page.find_test_selector("op-submit-work-package-journal-form").click + page.find_test_selector("op-submit-work-package-journal-form").click if save end - # wait for the comment to be loaded - wait_for { page }.to have_test_selector("op-journal-notes-body", text:) + if save + # wait for the comment to be loaded + wait_for { page }.to have_test_selector("op-journal-notes-body", text:) + end end end