From 3a370e18182e1cdc124f93d70dea50d372a50d67 Mon Sep 17 00:00:00 2001 From: Caleb Roseland Date: Fri, 16 Sep 2022 15:35:02 -0500 Subject: [PATCH 01/47] add PeopleProduct flag --- model/feature_flags.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/model/feature_flags.go b/model/feature_flags.go index 209e7a9f99..3a256749cb 100644 --- a/model/feature_flags.go +++ b/model/feature_flags.go @@ -75,6 +75,8 @@ type FeatureFlags struct { PlanUpgradeButtonText string PostPriority bool + + PeopleProduct bool } func (f *FeatureFlags) SetDefaults() { @@ -103,6 +105,7 @@ func (f *FeatureFlags) SetDefaults() { f.BoardsProduct = false f.PlanUpgradeButtonText = "upgrade" f.PostPriority = false + f.PeopleProduct = false } func (f *FeatureFlags) Plugins() map[string]string { From 2080ac08711573265383f854c7fe064ea7fad341 Mon Sep 17 00:00:00 2001 From: jprusch Date: Wed, 12 Oct 2022 15:11:53 +0200 Subject: [PATCH 02/47] Translated using Weblate (German) Currently translated at 100.0% (2386 of 2386 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/de/ Translated using Weblate (German) Currently translated at 100.0% (2385 of 2385 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/de/ Translated using Weblate (German) Currently translated at 100.0% (2385 of 2385 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/de/ Translated using Weblate (German) Currently translated at 100.0% (2384 of 2384 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/de/ Translated using Weblate (German) Currently translated at 100.0% (2382 of 2382 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/de/ --- i18n/de.json | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/i18n/de.json b/i18n/de.json index ae6d93035c..167ae48b99 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -4801,11 +4801,11 @@ }, { "id": "web.error.unsupported_browser.min_browser_version.edge", - "translation": "Version 44+" + "translation": "Version 95+" }, { "id": "web.error.unsupported_browser.min_browser_version.chrome", - "translation": "Version 100+" + "translation": "Version 102+" }, { "id": "web.error.unsupported_browser.learn_more", @@ -8380,7 +8380,7 @@ }, { "id": "app.notification.body.dm.subTitle", - "translation": "Währen du weg warst, hat dir {{.SenderName}} eine neue Direktnachricht gesendet." + "translation": "Während du weg warst, hat dir {{.SenderName}} eine neue Direktnachricht gesendet." }, { "id": "app.license.generate_renewal_token.no_license", @@ -9537,5 +9537,21 @@ { "id": "app.user.get_badge_count.app_error", "translation": "Wir konnten den Nachrichtenzähler für den Benutzer nicht abfragen." + }, + { + "id": "app.job.error", + "translation": "Fehler bei der Auftragsausführung." + }, + { + "id": "app.last_accessible_file.app_error", + "translation": "Fehler beim Abrufen der letzten zugänglichen Datei" + }, + { + "id": "app.file.cloud.get.app_error", + "translation": "Die Datei kann nicht abgerufen werden, da sie das Limit des Cloud-Plans überschritten hat." + }, + { + "id": "model.group.name.reserved_name.app_error", + "translation": "Gruppenname existiert bereits als reservierter Name" } ] From be3171276927afa14b25de5dd246df73ead0d6c4 Mon Sep 17 00:00:00 2001 From: Tom De Moor Date: Wed, 12 Oct 2022 15:11:53 +0200 Subject: [PATCH 03/47] Translated using Weblate (Dutch) Currently translated at 100.0% (2386 of 2386 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/nl/ Translated using Weblate (Dutch) Currently translated at 100.0% (2382 of 2382 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/nl/ --- i18n/nl.json | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/i18n/nl.json b/i18n/nl.json index 6f883eaa1a..031dca4c09 100644 --- a/i18n/nl.json +++ b/i18n/nl.json @@ -4325,7 +4325,7 @@ }, { "id": "model.post.is_valid.create_at.app_error", - "translation": "Aangemaakt op moet een geldige tijd bevatten" + "translation": "Aangemaakt op moet een geldige tijd bevatten." }, { "id": "model.post.is_valid.file_ids.app_error", @@ -9537,5 +9537,33 @@ { "id": "api.templates.delinquency_90.title", "translation": "Jouw Mattermost-werkruimte werd gedowngraded" + }, + { + "id": "app.job.error", + "translation": "Fout tijdens het uitvoeren van de opdracht." + }, + { + "id": "model.group.name.reserved_name.app_error", + "translation": "groepsnaam bestaat al als een gereserveerde naam" + }, + { + "id": "app.plugin.product_mode.app_error", + "translation": "Plugin {{.Name}} kan niet worden ingeschakeld in de productmodus." + }, + { + "id": "app.last_accessible_file.app_error", + "translation": "Fout bij het ophalen van het laatst toegankelijke bestand" + }, + { + "id": "app.file.cloud.get.app_error", + "translation": "Kan het bestand niet ophalen omdat dit bestand over de limiet van het cloud plan is." + }, + { + "id": "api.team.invite_guests_to_channels.license.error", + "translation": "Jouw licentie ondersteunt geen gastaccounts" + }, + { + "id": "api.team.invite_guests_to_channels.disabled.error", + "translation": "Gastaccounts zijn uitgeschakeld" } ] From a9cc73140d9c63bea49aff22c39c3ed6bd1d62ff Mon Sep 17 00:00:00 2001 From: Kaya Zeren Date: Wed, 12 Oct 2022 15:11:54 +0200 Subject: [PATCH 04/47] Translated using Weblate (Turkish) Currently translated at 100.0% (2386 of 2386 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/tr/ Translated using Weblate (Turkish) Currently translated at 100.0% (2385 of 2385 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/tr/ Translated using Weblate (Turkish) Currently translated at 100.0% (2384 of 2384 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/tr/ Translated using Weblate (Turkish) Currently translated at 100.0% (2382 of 2382 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/tr/ --- i18n/tr.json | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/i18n/tr.json b/i18n/tr.json index 563015338b..bdb2ca0902 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -2725,7 +2725,7 @@ }, { "id": "app.import.validate_team_import_data.name_reserved.error", - "translation": "Takım adında ayrılmış bir sözcük var." + "translation": "Takım adında sistem kullanımına ayrılmış bir sözcük var." }, { "id": "app.import.validate_team_import_data.scheme_invalid.error", @@ -5577,7 +5577,7 @@ }, { "id": "api.ldap_groups.existing_reserved_name_error", - "translation": "aynı adlı bir grup zaten ayrılmış bir ad olarak var" + "translation": "aynı adlı bir grup zaten sistem kullanımına ayrılmış bir ad olarak var" }, { "id": "api.ldap_groups.existing_user_name_error", @@ -5849,11 +5849,11 @@ }, { "id": "web.error.unsupported_browser.min_browser_version.chrome", - "translation": "Sürüm 100+" + "translation": "Sürüm 106+" }, { "id": "web.error.unsupported_browser.min_browser_version.edge", - "translation": "Sürüm 44+" + "translation": "Sürüm 95+" }, { "id": "web.error.unsupported_browser.min_browser_version.firefox", @@ -9537,5 +9537,33 @@ { "id": "app.user.get_badge_count.app_error", "translation": "Kullanıcının nişan sayısını alamadık." + }, + { + "id": "app.job.error", + "translation": "Görev yürütülürken sorun çıktı." + }, + { + "id": "app.last_accessible_file.app_error", + "translation": "Erişilebilen son dosya alınırken sorun çıktı" + }, + { + "id": "app.file.cloud.get.app_error", + "translation": "Cloud tarifesinin sınırlarını aştığından dosya alınamadı." + }, + { + "id": "model.group.name.reserved_name.app_error", + "translation": "aynı adlı bir grup zaten sistem kullanımına ayrılmış bir ad olarak var" + }, + { + "id": "app.plugin.product_mode.app_error", + "translation": "{{.Name}} uygulama eki ürün kipinde etkinleştirilemez." + }, + { + "id": "api.team.invite_guests_to_channels.license.error", + "translation": "Lisansınız konuk hesaplarının kullanılmasını desteklemiyor" + }, + { + "id": "api.team.invite_guests_to_channels.disabled.error", + "translation": "Konuk hesapları devre dışı bırakılmış" } ] From bc6388b93d109f55a0c2a884c1e467b2ffc8061c Mon Sep 17 00:00:00 2001 From: Dennis Cheng Date: Wed, 12 Oct 2022 15:11:54 +0200 Subject: [PATCH 05/47] Translated using Weblate (Chinese (Traditional)) Currently translated at 75.7% (1805 of 2382 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/zh_Hant/ --- i18n/zh-TW.json | 58 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/i18n/zh-TW.json b/i18n/zh-TW.json index a24b40c159..0f3fda947e 100644 --- a/i18n/zh-TW.json +++ b/i18n/zh-TW.json @@ -421,7 +421,7 @@ }, { "id": "api.command_channel_header.update_channel.app_error", - "translation": "更新當前頻道時錯誤。" + "translation": "更新當前頻道名稱時錯誤。" }, { "id": "api.command_channel_purpose.channel.app_error", @@ -453,7 +453,7 @@ }, { "id": "api.command_channel_purpose.update_channel.app_error", - "translation": "更新當前頻道時錯誤。" + "translation": "更新當前頻道用途時錯誤。" }, { "id": "api.command_channel_remove.channel.app_error", @@ -619,7 +619,7 @@ }, { "id": "api.command_help.desc", - "translation": "開啟 Mattermost 說明頁面" + "translation": "顯示 Mattermost 說明頁面" }, { "id": "api.command_help.name", @@ -7302,5 +7302,57 @@ { "id": "api.cloud.teams_limit_reached.create", "translation": "無法建立團隊,已達到團隊數量上限" + }, + { + "id": "api.command_custom_status.success", + "translation": "你的狀態已設為 “{{.EmojiName}} {{.StatusMessage}}”。您可以從頻道側邊欄中的狀態彈出視窗更改您的狀態。" + }, + { + "id": "api.command_custom_status.name", + "translation": "狀態" + }, + { + "id": "api.command_custom_status.hint", + "translation": "[:emoji_name:] [status_message] 或清除" + }, + { + "id": "api.command_custom_status.desc", + "translation": "設定或清除您的狀態" + }, + { + "id": "api.command_custom_status.clear.success", + "translation": "狀態已清除。" + }, + { + "id": "api.command_custom_status.clear.app_error", + "translation": "清除狀態錯誤。" + }, + { + "id": "api.command_custom_status.app_error", + "translation": "設定狀態錯誤。" + }, + { + "id": "api.command_channel_purpose.update_channel.max_length", + "translation": "輸入的文本超出字數限制。頻道用途不得超過 {{.MaxLength}} 個字數。" + }, + { + "id": "api.command_channel_header.update_channel.max_length", + "translation": "輸入的文本超出字數限制。頻道標題不得超過 {{.MaxLength}} 個字數。" + }, + { + "id": "api.cloud.delinquency_email.missing_email_to_trigger", + "translation": "缺少發送拖欠電子郵件的必要欄位。" + }, + { + "id": "api.cloud.cws_webhook_event_missing_error", + "translation": "Webhook 事件未處理。事件可能遺失或無效。" + }, + { + "id": "api.channel.create_channel.direct_channel.team_restricted_error", + "translation": "無法在這些使用者間創建直連頻道因為他們不屬於同一個團隊。" + }, + { + "id": "api.admin.saml.failure_reset_authdata_to_email.app_error", + "translation": "無法寄出重設AuthData欄位失敗的電子郵件。" } ] From a0a22fc9da6f417522729e57513ecc403b1481cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=B3th=20Csaba=20//=20Online=20ERP=20Hungary=20Kft?= Date: Wed, 12 Oct 2022 15:11:54 +0200 Subject: [PATCH 06/47] Translated using Weblate (Hungarian) Currently translated at 98.3% (2346 of 2385 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/hu/ Translated using Weblate (Hungarian) Currently translated at 98.2% (2343 of 2385 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/hu/ Translated using Weblate (Hungarian) Currently translated at 98.3% (2342 of 2382 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/hu/ --- i18n/hu.json | 48 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/i18n/hu.json b/i18n/hu.json index b10693e9f8..2f7e270a62 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -3125,7 +3125,7 @@ }, { "id": "oauth.gitlab.tos.error", - "translation": "A GitLab Általános Szerződési Feltételei frissültek. Kérjük, látogassa meg a gitlab.com webhelyet, hogy elfogadja őket, majd próbáljon meg újra bejelentkezni a Mattermost-ba." + "translation": "A GitLab Általános Szerződési Feltételei frissültek. Kérjük, látogassa meg a {{.URL}} webhelyet, hogy elfogadja őket, majd próbáljon meg újra bejelentkezni a Mattermost-ba." }, { "id": "model.websocket_client.connect_fail.app_error", @@ -5469,15 +5469,15 @@ }, { "id": "api.templates.payment_failed.title", - "translation": "Sikertelen fizetés" + "translation": "A fizetési művelet nem sikerült" }, { "id": "api.templates.payment_failed.subject", - "translation": "Művelet szükséges: Sikertelen fizetés a Mattermost Cloudra" + "translation": "Művelet szükséges: Sikertelen fizetés a Mattermost {{.Plan}} csomagra" }, { "id": "api.templates.payment_failed.info3", - "translation": "A Mattermost Cloud zavartalan előfizetésének biztosítása érdekében kérjük, vegye fel a kapcsolatot a pénzintézetével a probléma megoldása érdekében, vagy frissítse fizetési adatait. A fizetési adatok frissítése után a Mattermost megpróbálja rendezni a fennmaradó egyenleget." + "translation": "A Mattermost {{.Plan}} zavartalan elérésének biztosítása érdekében kérjük, vegye fel a kapcsolatot a pénzintézetével a probléma megoldása érdekében, vagy frissítse fizetési adatait. A fizetési adatok frissítése után a Mattermost megpróbálja rendezni a fennmaradó egyenleget." }, { "id": "api.templates.payment_failed.info2", @@ -9354,5 +9354,45 @@ { "id": "api.templates.delinquency_30.subject", "translation": "Lépjen most, hogy megtarthassa a Mattermost {{.Plan}} funkcióit" + }, + { + "id": "app.notify_admin.send_notification_post.app_error", + "translation": "Nem sikerült elküldeni az értesítési bejegyzést." + }, + { + "id": "app.notify_admin.save.app_error", + "translation": "Nem sikerült elmenteni az értesítési beállításokat." + }, + { + "id": "app.job.error", + "translation": "Hiba a művelet végrehajtása közben." + }, + { + "id": "app.cloud.get_current_plan_name.app_error", + "translation": "Nem sikerült lekérni a jelenlegi előfizetési csomag nevét" + }, + { + "id": "api.templates.delinquency_30.button", + "translation": "Fizetés frissítése" + }, + { + "id": "api.templates.delinquency_30.bullet.plugins", + "translation": "Aktív bővítmények és integrációk" + }, + { + "id": "api.cloud.delinquency_email.missing_email_to_trigger", + "translation": "Hiányzó kötelező mezők a késedelmes e-mail küldéséhez." + }, + { + "id": "app.file.cloud.get.app_error", + "translation": "Nem sikerült lekérni a fájlt, mivel az meghaladta a felhőcsomag határértékét." + }, + { + "id": "app.user.get_badge_count.app_error", + "translation": "Nem sikerült lekérni a felhasználó jelvény számlálóját." + }, + { + "id": "model.group.name.reserved_name.app_error", + "translation": "csoport név létezik mint lefoglalt név" } ] From a256ff49836fd434d8c8c9011c0ec684e900555a Mon Sep 17 00:00:00 2001 From: Tilto_ Date: Wed, 12 Oct 2022 15:11:55 +0200 Subject: [PATCH 07/47] Translated using Weblate (Korean) Currently translated at 83.3% (1986 of 2384 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/ko/ --- i18n/ko.json | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/i18n/ko.json b/i18n/ko.json index 6efc6c5e61..79e6d396d5 100644 --- a/i18n/ko.json +++ b/i18n/ko.json @@ -619,7 +619,7 @@ }, { "id": "api.command_help.desc", - "translation": "Mattermost 도움말 페이지 열기" + "translation": "Mattermost 도움말 보기" }, { "id": "api.command_help.name", @@ -7934,5 +7934,41 @@ { "id": "api.cloud.teams_limit_reached.create", "translation": "팀 제한에 도달했기 때문에 팀을 만들 수 없습니다" + }, + { + "id": "api.command_marketplace.desc", + "translation": "마켓플레이스 열기" + }, + { + "id": "api.command_help.success", + "translation": "Mattermost는 도구와 팀을 넘나드는 안전한 소통, 협업, 작업 조율을 위한 오픈소스 플랫폼입니다.\nMattermost는 세가지 핵심 도구를 갖고 있습니다:\n\n**채널** - 1:1 및 그룹 채팅을 통해 팀과의 소통을 유지하세요.\n**[플레이북](/playbooks)** - 반복가능한 작업들을 구성하여 구체적이고 예측 가능한 결과를 만드세요.\n**[보드](/boards)** - 팀의 핵심 마일스톤에 도달하기 위해 칸반 보드에서 작업을 만들고 프로젝트를 관리하세요.\n\n[문서 및 가이드 살펴보기]({{.HelpLink}})" + }, + { + "id": "api.cloud.delinquency_email.missing_email_to_trigger", + "translation": "연체 이메일을 보내기 위한 필수 항목이 누락되었습니다." + }, + { + "id": "api.error_set_first_admin_complete_setup", + "translation": "스토어에서 첫 번째 관리자의 설정을 저장하는 동안 오류가 발생했습니다." + }, + { + "id": "api.error_get_first_admin_visit_marketplace_status", + "translation": "스토어에서 첫 번째 관리자의 마켓플레이스 방문 상태를 검색하는 동안 오류가 발생했습니다." + }, + { + "id": "api.error_get_first_admin_complete_setup", + "translation": "스토어에서 첫 번째 관리자 설정을 검색하는 동안 오류가 발생했습니다." + }, + { + "id": "api.command_remote.invite_summary", + "translation": "AES-256으로 암호화된 초대를 비밀번호와 함께 외부 Mattermost 시스템 관리자에게 보냅니다. 초대를 받기 위해 `{{.Command}}` 슬래시 명령어를 사용합니다.\n\n```\n{{.Invitation}}\n```\n\n**보안 연결이 다음을 통해 Mattermost에 액세스할 수 있는지 확인하십시오** {{.SiteURL}}" + }, + { + "id": "api.command_marketplace.unsupported.app_error", + "translation": "마켓플레이스 명령어는 현재 기기에서 지원하지 않습니다." + }, + { + "id": "api.command_marketplace.name", + "translation": "마켓플레이스" } ] From 280859f457e4095389a2eed7373d70ee528cb21f Mon Sep 17 00:00:00 2001 From: master7 Date: Wed, 12 Oct 2022 15:11:55 +0200 Subject: [PATCH 08/47] Translated using Weblate (Polish) Currently translated at 100.0% (2386 of 2386 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/pl/ Translated using Weblate (Polish) Currently translated at 100.0% (2386 of 2386 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/pl/ Translated using Weblate (Polish) Currently translated at 100.0% (2385 of 2385 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/pl/ Translated using Weblate (Polish) Currently translated at 100.0% (2384 of 2384 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/pl/ --- i18n/pl.json | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/i18n/pl.json b/i18n/pl.json index de2e7d8a68..48fe1d9b82 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -4761,11 +4761,11 @@ }, { "id": "web.error.unsupported_browser.min_browser_version.edge", - "translation": "Wersja 44+" + "translation": "Wersja 95+" }, { "id": "web.error.unsupported_browser.min_browser_version.chrome", - "translation": "Wersja 100+" + "translation": "Wersja 106+" }, { "id": "web.error.unsupported_browser.learn_more", @@ -9542,5 +9542,29 @@ { "id": "app.job.error", "translation": "Błąd podczas wykonywania zadania." + }, + { + "id": "app.last_accessible_file.app_error", + "translation": "Błąd pobierania ostatniego dostępnego pliku" + }, + { + "id": "app.file.cloud.get.app_error", + "translation": "Nie można pobrać pliku, ponieważ przekroczony został limit planu chmury." + }, + { + "id": "model.group.name.reserved_name.app_error", + "translation": "nazwa grupy już istnieje jako nazwa zastrzeżona" + }, + { + "id": "app.plugin.product_mode.app_error", + "translation": "Wtyczka {{.Name}} nie może być włączona w trybie produktu." + }, + { + "id": "api.team.invite_guests_to_channels.license.error", + "translation": "Twoja licencja nie wspiera kont gości" + }, + { + "id": "api.team.invite_guests_to_channels.disabled.error", + "translation": "Konta gości są wyłączone" } ] From c3e3d06f661865c717acc281e4eb8edab8fcc293 Mon Sep 17 00:00:00 2001 From: Dmitriy Danilov Date: Wed, 12 Oct 2022 15:11:56 +0200 Subject: [PATCH 09/47] Translated using Weblate (Ukrainian) Currently translated at 70.7% (1687 of 2385 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/uk/ --- i18n/uk.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/i18n/uk.json b/i18n/uk.json index 21f427b84f..4632894522 100644 --- a/i18n/uk.json +++ b/i18n/uk.json @@ -6858,5 +6858,17 @@ { "id": "api.admin.add_certificate.parseform.app_error", "translation": "Помилка синтаксісу багато-форматного запиту" + }, + { + "id": "api.channel.patch_channel_moderations.cache_invalidation.error", + "translation": "Помилка валідації кешу" + }, + { + "id": "api.back_to_app", + "translation": "Повернутися до {{.SiteName}}" + }, + { + "id": "Channels", + "translation": "Канали" } ] From 9018daba01054132e89a016fa223cd80c926330e Mon Sep 17 00:00:00 2001 From: Matthew Williams Date: Wed, 12 Oct 2022 15:11:56 +0200 Subject: [PATCH 10/47] Translated using Weblate (English (Australia)) Currently translated at 100.0% (2385 of 2385 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/en_AU/ --- i18n/en_AU.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/i18n/en_AU.json b/i18n/en_AU.json index e1dc7cb1c0..026157936b 100644 --- a/i18n/en_AU.json +++ b/i18n/en_AU.json @@ -9537,5 +9537,21 @@ { "id": "app.cloud.get_current_plan_name.app_error", "translation": "Unable to get current plan name" + }, + { + "id": "model.group.name.reserved_name.app_error", + "translation": "group name already exists as a reserved name" + }, + { + "id": "app.last_accessible_file.app_error", + "translation": "Error fetching last accessible file" + }, + { + "id": "app.job.error", + "translation": "Error during job execution." + }, + { + "id": "app.file.cloud.get.app_error", + "translation": "Unable to retrieve file: cloud storage limit exceeded." } ] From ef699ff0a286a62d20297b4879e900bad7cb8a69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20V=C3=ADtor=20Cruz=20de=20Souza?= Date: Wed, 12 Oct 2022 15:11:56 +0200 Subject: [PATCH 11/47] Translated using Weblate (Portuguese (Brazil)) Currently translated at 90.6% (2161 of 2385 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/pt_BR/ Translated using Weblate (Portuguese (Brazil)) Currently translated at 90.5% (2160 of 2385 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/pt_BR/ --- i18n/pt-BR.json | 104 ++++++++++++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 48 deletions(-) diff --git a/i18n/pt-BR.json b/i18n/pt-BR.json index 94b78b3e58..1d35e397f0 100644 --- a/i18n/pt-BR.json +++ b/i18n/pt-BR.json @@ -621,7 +621,7 @@ }, { "id": "api.command_help.desc", - "translation": "Abrir a página de ajuda do Mattermost" + "translation": "Exibir descrição de ajuda do Mattermost" }, { "id": "api.command_help.name", @@ -1173,7 +1173,7 @@ }, { "id": "api.marshal_error", - "translation": "marshal error" + "translation": "Falha para marshal." }, { "id": "api.oauth.allow_oauth.redirect_callback.app_error", @@ -1789,7 +1789,7 @@ }, { "id": "api.templates.email_footer", - "translation": "Para alterar suas preferencias de notificação, faça login no site da sua equipe e vá para Configurações de Conta > Notificações." + "translation": "Para alterar suas preferencias de notificação, faça login no site da sua equipe e vá para Configurações > Notificações." }, { "id": "api.templates.email_info1", @@ -1813,7 +1813,7 @@ }, { "id": "api.templates.invite_body.button", - "translation": "Entrar na Equipe" + "translation": "Entrar agora" }, { "id": "api.templates.invite_body.title", @@ -1857,7 +1857,7 @@ }, { "id": "api.templates.post_body.button", - "translation": "Ir Para a Publicação" + "translation": "Responder no Mattermost" }, { "id": "api.templates.reset_body.button", @@ -1865,7 +1865,7 @@ }, { "id": "api.templates.reset_body.title", - "translation": "Você solicitou uma redefinição de senha" + "translation": "Redefina sua senha" }, { "id": "api.templates.reset_subject", @@ -4333,11 +4333,11 @@ }, { "id": "model.post.is_valid.file_ids.app_error", - "translation": "Ids do arquivo inválido. Observe que os uploads estão limitados a cinco arquivos no máximo. Por favor, use publicações adicionais para mais arquivos." + "translation": "Ids do arquivo inválido. Observe que os uploads estão limitados a 10 arquivos no máximo. Por favor, use publicações adicionais para mais arquivos." }, { "id": "model.post.is_valid.filenames.app_error", - "translation": "Nomes de arquivo inválidos" + "translation": "Nomes de arquivo inválidos." }, { "id": "model.post.is_valid.hashtags.app_error", @@ -4573,7 +4573,7 @@ }, { "id": "oauth.gitlab.tos.error", - "translation": "Os Termos de Serviço do GitLab foram atualizados. Por favor vá até gitlab.com para aceitar os termos e depois tente se logar novamente no Mattermost." + "translation": "Os Termos de Serviço do GitLab foram atualizados. Por favor vá até {{.URL}} para aceitar os termos e depois tente se logar novamente no Mattermost." }, { "id": "plugin.api.update_user_status.bad_status", @@ -4745,19 +4745,19 @@ }, { "id": "web.error.unsupported_browser.min_os_version.windows", - "translation": "Windows 7+" + "translation": "Windows 8.1+" }, { "id": "web.error.unsupported_browser.min_os_version.mac", - "translation": "macOS 10.9+" + "translation": "macOS 10.14+" }, { "id": "web.error.unsupported_browser.min_browser_version.safari", - "translation": "Versão 12+" + "translation": "Versão 14.1+" }, { "id": "web.error.unsupported_browser.min_browser_version.firefox", - "translation": "Versão 60+" + "translation": "Versão 91+" }, { "id": "web.error.unsupported_browser.min_browser_version.edge", @@ -4765,7 +4765,7 @@ }, { "id": "web.error.unsupported_browser.min_browser_version.chrome", - "translation": "Versão 61+" + "translation": "Version 100+" }, { "id": "web.error.unsupported_browser.learn_more", @@ -5945,7 +5945,7 @@ }, { "id": "api.push_notifications.session.expired", - "translation": "Sessão expirada: faça login para continuar recebendo notificações. As sessões para {{.siteName}} estão configuradas pelo Administrador do Sistema para expirar a cada {{.daysCount}} dia(s)." + "translation": "Sessão expirada: faça login para continuar recebendo notificações. As sessões para {{.siteName}} estão configuradas pelo Administrador do Sistema para expirar a cada {{.hoursCount}} hour(s)." }, { "id": "api.post.error_get_post_id.pending", @@ -6189,7 +6189,7 @@ }, { "id": "app.preference.permanent_delete_by_user.app_error", - "translation": "Encontramos um erro enquanto excluía as preferências." + "translation": "Encontramos um erro enquanto excluiamos as preferências." }, { "id": "app.preference.get_category.app_error", @@ -6897,7 +6897,7 @@ }, { "id": "model.upload_session.is_valid.user_id.app_error", - "translation": "Valor inválido para user_id." + "translation": "Valor inválido para UserId" }, { "id": "model.upload_session.is_valid.type.app_error", @@ -6905,15 +6905,15 @@ }, { "id": "model.upload_session.is_valid.path.app_error", - "translation": "Valor inválido para path." + "translation": "Valor inválido para path" }, { "id": "model.upload_session.is_valid.id.app_error", - "translation": "Valor inválido para id." + "translation": "Valor inválido para id" }, { "id": "model.upload_session.is_valid.create_at.app_error", - "translation": "Valor inválido para create_at." + "translation": "Valor inválido para Create_At" }, { "id": "app.upload.upload_data.large_image.app_error", @@ -7601,7 +7601,7 @@ }, { "id": "api.templates.copyright", - "translation": "© 2020 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301" + "translation": "© 2021 Mattermost, Inc. 530 Lytton Avenue, Second floor, Palo Alto, CA, 94301" }, { "id": "api.roles.patch_roles.not_allowed_permission.error", @@ -7645,15 +7645,15 @@ }, { "id": "api.templates.payment_failed.title", - "translation": "Pagamento Falhou" + "translation": "O Pagamento não foi bem sucedido" }, { "id": "api.templates.payment_failed.subject", - "translation": "Ação necessária: Falha no pagamento para Mattermost Cloud" + "translation": "Ação necessária: Falha no pagamento para Mattermost {{.Plan}}" }, { "id": "api.templates.payment_failed.info3", - "translation": "Para garantir a assinatura ininterrupta do Mattermost Cloud, entre em contato com sua instituição financeira para corrigir o problema subjacente ou atualize suas informações de pagamento. Assim que as informações de pagamento forem atualizadas, a Mattermost tentará liquidar o saldo pendente." + "translation": "Para garantir a acesso ininterrupto do Mattermost {{.Plan}}, entre em contato com sua instituição financeira para corrigir o problema subjacente ou atualize suas informações de pagamento. Assim que as informações de pagamento forem atualizadas, a Mattermost tentará liquidar o saldo pendente." }, { "id": "api.templates.payment_failed.info2", @@ -8069,7 +8069,7 @@ }, { "id": "api.user.get_authorization_code.endpoint.app_error", - "translation": "Erro ao recuperar o endpoint da Descoberta de Documento." + "translation": "Erro ao recuperar o endpoint do Documento de Descoberta." }, { "id": "api.templates.welcome_body.subTitle2", @@ -8117,11 +8117,11 @@ }, { "id": "api.templates.questions_footer.info", - "translation": "Envie-nos um email a qualquer momento em " + "translation": "Precisa de ajuda ou tem dúvidas? Envie-nos um e-mail em " }, { "id": "api.templates.email_footer_v2", - "translation": "© 2020 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301" + "translation": "© 2022 Mattermost, Inc. 530 Lytton Avenue, Second floor, Palo Alto, CA, 94301" }, { "id": "api.post.search_files.invalid_body.app_error", @@ -8333,7 +8333,7 @@ }, { "id": "api.context.remote_id_invalid.app_error", - "translation": "Não foi possível encontrar o id do cluster remoto {{.RemoteId}}." + "translation": "Não foi possível encontrar o ID de uma conexão segura {{.RemoteId}}." }, { "id": "api.context.json_encoding.app_error", @@ -8357,15 +8357,15 @@ }, { "id": "api.command_share.remote_uninvited", - "translation": "Remoto `{{.RemoteId}}` sem convite." + "translation": "Conexão segura `{{.RemoteId}}` sem convite." }, { "id": "api.command_share.remote_id_invalid.error", - "translation": "O id do cluster remoto é inválido: {{.Error}}" + "translation": "O ID de conexão segura é inválido: {{.Error}}" }, { "id": "api.command_share.remote_id.help", - "translation": "Id de uma instância remota existente. Veja o comando `remote` para adicionar uma instância remota." + "translation": "ID de uma conexão segura existente. Veja o comando `remote` para adicionar uma conexão segura." }, { "id": "api.command_share.permission_required", @@ -8437,7 +8437,7 @@ }, { "id": "api.templates.cloud_welcome_email.signin_sub_info2", - "translation": "para a melhor experiência em PC, Mac, iOS e Android." + "translation": "para a melhor experiência em Windows, Linux, Mac, iOS e Android." }, { "id": "api.templates.cloud_welcome_email.signin_sub_info", @@ -8469,7 +8469,7 @@ }, { "id": "api.templates.cloud_welcome_email.add_apps_sub_info", - "translation": "Otimize seu trabalho com ferramentas como Github, Google Agenda e Chrome. Explore todas as integrações que temos" + "translation": "Otimize seu trabalho com ferramentas como GitHub, Jira e Zoom. Explore todas as integrações que temos em nosso" }, { "id": "api.templates.cloud_welcome_email.add_apps_info", @@ -8481,11 +8481,11 @@ }, { "id": "api.remote_cluster.update_not_unique.app_error", - "translation": "Já existe um cluster remoto com a mesmo url." + "translation": "Já existe uma conexão segura com a mesmo url." }, { "id": "api.remote_cluster.update.app_error", - "translation": "Encontramos um erro ao atualizar o cluster remoto." + "translation": "Encontramos um erro ao atualizar a conexão segura." }, { "id": "api.remote_cluster.service_not_enabled.app_error", @@ -8493,11 +8493,11 @@ }, { "id": "api.remote_cluster.save_not_unique.app_error", - "translation": "O cluster remoto já foi adicionado." + "translation": "A conexão segura já foi adicionada." }, { "id": "api.remote_cluster.save.app_error", - "translation": "Encontramos um erro ao salvar o cluster remoto." + "translation": "Encontramos um erro ao salvar a conexão segura." }, { "id": "api.remote_cluster.invalid_topic.app_error", @@ -8509,11 +8509,11 @@ }, { "id": "api.remote_cluster.get.app_error", - "translation": "Encontramos um erro ao recuperar um cluster remoto." + "translation": "Encontramos um erro ao recuperar uma conexão segura." }, { "id": "api.remote_cluster.delete.app_error", - "translation": "Encontramos um erro ao excluir o cluster remoto." + "translation": "Encontramos um erro ao excluir uma conexão segura." }, { "id": "api.job.unable_to_create_job.incorrect_job_type", @@ -8533,11 +8533,11 @@ }, { "id": "api.context.remote_id_missing.app_error", - "translation": "Falta a id do cluster remoto." + "translation": "Falta a ID de conexão sergura." }, { "id": "api.context.remote_id_mismatch.app_error", - "translation": "Incompatibilidade de id do cluster remoto." + "translation": "Incompatibilidade de ID da conexão segura." }, { "id": "api.context.invitation_expired.error", @@ -8549,11 +8549,11 @@ }, { "id": "api.command_share.uninvite_remote_id.help", - "translation": "Id da instância remota para desconvidar." + "translation": "ID de uma conexão segura para desconvidar." }, { "id": "api.command_share.uninvite_remote.help", - "translation": "Cancela o convite de uma instância remota deste canal compartilhado" + "translation": "Cancela o convite de uma conexão segura deste canal compartilhado" }, { "id": "api.command_share.shared_channel_unavailable", @@ -8569,11 +8569,11 @@ }, { "id": "api.command_share.remote_not_valid", - "translation": "Deve especificar um cluster remoto válido para cancelar o convite" + "translation": "Deve especificar uma ID de conexão segura válida para cancelar o convite" }, { "id": "api.command_share.remote_already_invited", - "translation": "O cluster remoto já foi convidado." + "translation": "A conexão segura já foi convidada." }, { "id": "api.command_share.not_shared_channel_unshare", @@ -8581,15 +8581,15 @@ }, { "id": "api.command_share.no_remote_invited", - "translation": "Nenhum remoto foi convidado para este canal compartilhado." + "translation": "Nenhuma conexão remota foi convidada a este canal." }, { "id": "api.command_share.name", - "translation": "compartilhar" + "translation": "compartilhar-canal" }, { "id": "api.command_share.must_specify_valid_remote", - "translation": "Deve especificar um ID de conjunto remoto válido para convidar." + "translation": "Deve especificar um ID de conjunto válido seguro para convidar." }, { "id": "api.command_share.missing_action", @@ -8650,5 +8650,13 @@ { "id": "Boards", "translation": "Quadros" + }, + { + "id": "Playbooks", + "translation": "Playbooks" + }, + { + "id": "api.cloud.delinquency_email.missing_email_to_trigger", + "translation": "Campos faltando para envio de email." } ] From 4eb4316402a4a96daec6ded7a321bc23eab31fed Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 12 Oct 2022 15:11:57 +0200 Subject: [PATCH 12/47] Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/ Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/ --- i18n/bg.json | 8 -------- i18n/de.json | 8 -------- i18n/en_AU.json | 8 -------- i18n/es.json | 8 -------- i18n/fa.json | 8 -------- i18n/fr.json | 8 -------- i18n/hu.json | 8 -------- i18n/it.json | 8 -------- i18n/ja.json | 8 -------- i18n/ko.json | 8 -------- i18n/nl.json | 8 -------- i18n/pl.json | 8 -------- i18n/pt-BR.json | 8 -------- i18n/ro.json | 8 -------- i18n/ru.json | 8 -------- i18n/sv.json | 4 ---- i18n/tr.json | 8 -------- i18n/uk.json | 8 -------- i18n/zh-CN.json | 8 -------- i18n/zh-TW.json | 8 -------- 20 files changed, 156 deletions(-) diff --git a/i18n/bg.json b/i18n/bg.json index 7913a2775f..4031bf4bf6 100644 --- a/i18n/bg.json +++ b/i18n/bg.json @@ -4267,14 +4267,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "Каналите в поканата трябва да са част от канещият екип." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "Вашият лиценз не поддържа профили за гости" - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "Профилите за гости са забранени" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "Грешка при анулиране на поканите по ел.поща." diff --git a/i18n/de.json b/i18n/de.json index 167ae48b99..dd3742e5f8 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -5623,14 +5623,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "Die Kanäle der Einladung müssen Teil des Teams der Einladung sein." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "Deine Lizenz unterstützt Gastkonten nicht" - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "Gastkonten sind deaktiviert" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "Fehler beim Annulieren von E-Mail-Einladungen." diff --git a/i18n/en_AU.json b/i18n/en_AU.json index 026157936b..6f6f177f60 100644 --- a/i18n/en_AU.json +++ b/i18n/en_AU.json @@ -5459,14 +5459,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "The channels of the invite must be part of the team of the invite." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "Your licence does not support guest accounts" - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "Guest accounts are disabled" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "Error invalidating email invites." diff --git a/i18n/es.json b/i18n/es.json index 97cec02eba..5a29542a5c 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -5543,14 +5543,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "Los canales de la invitación deben ser parte del equipo al cual fue invitado." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "La licencia actual no es compatible con cuentas de huéspedes" - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "Las cuentas de huéspedes están deshabilitadas" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "Error al invalidar las invitaciones por correo electrónico." diff --git a/i18n/fa.json b/i18n/fa.json index b71235d64b..88186a1d34 100644 --- a/i18n/fa.json +++ b/i18n/fa.json @@ -5159,14 +5159,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "کانال های دعوت باید بخشی از تیم دعوت باشند." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "مجوز شما از حساب های مهمان پشتیبانی نمی کند" - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "حساب های مهمان غیرفعال شده است" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "خطا در عدم اعتبار دعوت نامه های ایمیل." diff --git a/i18n/fr.json b/i18n/fr.json index 043cda6743..c94978c9da 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -5583,14 +5583,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "Les canaux présents dans l'invitation doivent faire partie de la même équipe pour laquelle vous êtes invité." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "Votre licence ne prend pas en charge les comptes invités." - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "Les comptes invités sont désactivés" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "Une erreur s'est produite lors de l'invalidation des e-mails d'invitation." diff --git a/i18n/hu.json b/i18n/hu.json index 2f7e270a62..930d7fd5d3 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -5795,14 +5795,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "A meghívás csatornáinak a meghívó csapat részének kell lenniük." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "Az Ön licence nem támogatja a vendég fiókokat" - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "A vendég fiókok le vannak tiltva" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "Hiba történt az e-mail meghívók érvénytelenítésekor." diff --git a/i18n/it.json b/i18n/it.json index 2b4114bad9..f9c6ae0911 100644 --- a/i18n/it.json +++ b/i18n/it.json @@ -5211,10 +5211,6 @@ "id": "api.team.add_members.user_denied", "translation": "Questa squadra è gestita dai gruppi. Questo utente non appartiene a un gruppo sincronizzato con questa squadra." }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "Gli account ospite sono disattivati" - }, { "id": "api.team.remove_member.group_constrained.app_error", "translation": "Impossibile rimuovere un utente da una squadra con vincoli di gruppo." @@ -5643,10 +5639,6 @@ "id": "api.team.invalidate_all_email_invites.app_error", "translation": "Errore invalidando gli inviti email." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "La tua licenza non supporta gli account ospite" - }, { "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "I canali dell'invito devono far parte della squadra dell'invito." diff --git a/i18n/ja.json b/i18n/ja.json index 5918ca411b..2b9e6beead 100644 --- a/i18n/ja.json +++ b/i18n/ja.json @@ -5579,14 +5579,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "招待チャンネルは招待チーム内のチャンネルでなくてはなりません。" }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "現在のライセンスではゲストアカウントはサポートされていません" - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "ゲストアカウントは無効化されています" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "電子メール招待の無効化でエラーが発生しました。" diff --git a/i18n/ko.json b/i18n/ko.json index 79e6d396d5..7d4b36ccb3 100644 --- a/i18n/ko.json +++ b/i18n/ko.json @@ -5575,14 +5575,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "초대 채널은 초대한 팀의 채널이어야 합니다." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "게스트 계정을 지원하지 않는 라이선스입니다" - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "게스트 계정이 비활성화되어 있습니다" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "전자우편 초대에서 유효하지 않는 오류가 발생했습니다." diff --git a/i18n/nl.json b/i18n/nl.json index 031dca4c09..b3c4ec8a45 100644 --- a/i18n/nl.json +++ b/i18n/nl.json @@ -5607,14 +5607,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "De kanalen van de uitgenodigde moet een kanaal dat deel uitmaakt van het team." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "Je licentie ondersteunt geen gastgebruikers" - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "Gast accounts zijn uitgeschakeld" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "Fout bij afkeuren e-mailuitnodigingen." diff --git a/i18n/pl.json b/i18n/pl.json index 48fe1d9b82..f62b244a3d 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -5583,14 +5583,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "Kanały do których chcesz zaprosić, muszą być częścią zespołu do którego chcesz zaprosić." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "Twoja licencja nie obsługuje kont gości" - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "Konta gości są wyłączone" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "Błąd unieważniania zaproszenia E-Mail." diff --git a/i18n/pt-BR.json b/i18n/pt-BR.json index 1d35e397f0..2873fe7a1d 100644 --- a/i18n/pt-BR.json +++ b/i18n/pt-BR.json @@ -5587,14 +5587,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "Os canais do convite devem ser parte da equipe do convite." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "Sua licença não suporta contas convidado" - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "Contas convidado estão desativadas" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "Erro ao invalidar convites por email." diff --git a/i18n/ro.json b/i18n/ro.json index c51b4666e0..6c9fe29085 100644 --- a/i18n/ro.json +++ b/i18n/ro.json @@ -5587,14 +5587,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "Canalele invitației trebuie să facă parte din echipa invitației." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "Licența dvs. nu acceptă conturile de invitați" - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "Conturile oaspeților sunt dezactivate" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "Eroare la invalidarea invitațiilor de e-mail." diff --git a/i18n/ru.json b/i18n/ru.json index 3d371f6a36..0b4fd7d438 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -5235,10 +5235,6 @@ "id": "api.team.demote_user_to_guest.license.error", "translation": "Ваша лицензия не поддерживает гостевые учётные записи" }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "Ваша лицензия не поддерживает гостевые учётные записи" - }, { "id": "api.team.remove_member.group_constrained.app_error", "translation": "Невозможно удалить пользователя из управляемой группами команды." @@ -5675,10 +5671,6 @@ "id": "api.team.invalidate_all_email_invites.app_error", "translation": "Ошибка аннулирования e-mail приглашений." }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "Гостевые учётные записи отключены" - }, { "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "Каналы в приглашении должны быть частью команды в приглашении." diff --git a/i18n/sv.json b/i18n/sv.json index 2374ce62ad..82bd8030ec 100644 --- a/i18n/sv.json +++ b/i18n/sv.json @@ -5275,10 +5275,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "De kanaler som ingår i inbjudan måste tillhöra de team som inbjudan gäller för." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "Din licens tillåter inte gäståtkomst" - }, { "id": "api.team.invate_guests_to_channels.disabled.error", "translation": "Gäståtkomst har inaktiverats" diff --git a/i18n/tr.json b/i18n/tr.json index bdb2ca0902..cbe04ebdb2 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -5655,14 +5655,6 @@ "id": "api.team.invalidate_all_email_invites.app_error", "translation": "E-posta çağrıları geçersiz kılınırken sorun çıktı." }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "Konuk hesapları kullanılmıyor" - }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "Lisansınızda konuk hesaplarını kullanma özelliği yok" - }, { "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "Çağrılan kanallar çağrı yapılan takımın bir parçası olmalıdır." diff --git a/i18n/uk.json b/i18n/uk.json index 4632894522..c05c7895b3 100644 --- a/i18n/uk.json +++ b/i18n/uk.json @@ -5583,14 +5583,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "The channels of the invite must be part of the team of the invite." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "Редакція не підтримує Elasticsearch. " - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "Guest accounts are disabled" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "Error invalidating email invites." diff --git a/i18n/zh-CN.json b/i18n/zh-CN.json index 4dba1375ff..cecf6c9252 100644 --- a/i18n/zh-CN.json +++ b/i18n/zh-CN.json @@ -5611,10 +5611,6 @@ "id": "api.team.invalidate_all_email_invites.app_error", "translation": "作废邮件邀请错误。" }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "您的许可证不支持访客帐号" - }, { "id": "api.team.remove_member.group_constrained.app_error", "translation": "无法从组受限团队移除用户。" @@ -5627,10 +5623,6 @@ "id": "api.team.search_teams.pagination_not_implemented.public_team_search", "translation": "仅搜索公共团队不支持分页。" }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "访客帐号已停用" - }, { "id": "api.templates.remove_expired_license.subject", "translation": "Mattermost 企业许可证已停用。" diff --git a/i18n/zh-TW.json b/i18n/zh-TW.json index 0f3fda947e..11b8fcb9ef 100644 --- a/i18n/zh-TW.json +++ b/i18n/zh-TW.json @@ -5579,14 +5579,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "邀請的頻道必須屬於邀請的團隊" }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "授權不支援訪客帳號" - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "訪客帳號已停用" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "使電子郵件邀請無效時失敗。" From 1d814f27ae6400249f8b6a477dec9da9449ceb17 Mon Sep 17 00:00:00 2001 From: MArtin Johnson Date: Wed, 12 Oct 2022 15:11:58 +0200 Subject: [PATCH 13/47] Translated using Weblate (Swedish) Currently translated at 100.0% (2385 of 2385 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/sv/ --- i18n/sv.json | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/i18n/sv.json b/i18n/sv.json index 82bd8030ec..290ff4f7e5 100644 --- a/i18n/sv.json +++ b/i18n/sv.json @@ -5275,10 +5275,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "De kanaler som ingår i inbjudan måste tillhöra de team som inbjudan gäller för." }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "Gäståtkomst har inaktiverats" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "Fel vid ogiltiggörande av e-postinbjudan." @@ -9537,5 +9533,25 @@ { "id": "app.job.error", "translation": "Fel under utförandet av jobbet." + }, + { + "id": "model.group.name.reserved_name.app_error", + "translation": "gruppnamnet finns redan som ett reserverat namn" + }, + { + "id": "app.last_accessible_file.app_error", + "translation": "Fel vid hämtning av den senast tillgängliga filen" + }, + { + "id": "app.file.cloud.get.app_error", + "translation": "Ditt abonnemang begränsar möjligheten att hämta filen." + }, + { + "id": "api.team.invite_guests_to_channels.license.error", + "translation": "Din licens tillåter inte gäståtkomst" + }, + { + "id": "api.team.invite_guests_to_channels.disabled.error", + "translation": "Gäståtkomst har inaktiverats" } ] From 188a87315ca6725f65450e46cfe7ec6e69fba084 Mon Sep 17 00:00:00 2001 From: JtheBAB Date: Wed, 12 Oct 2022 15:11:58 +0200 Subject: [PATCH 14/47] Translated using Weblate (German) Currently translated at 100.0% (2386 of 2386 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/de/ Translated using Weblate (German) Currently translated at 100.0% (2386 of 2386 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/de/ --- i18n/de.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/i18n/de.json b/i18n/de.json index dd3742e5f8..50de9de46d 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -4805,7 +4805,7 @@ }, { "id": "web.error.unsupported_browser.min_browser_version.chrome", - "translation": "Version 102+" + "translation": "Version 106+" }, { "id": "web.error.unsupported_browser.learn_more", @@ -9545,5 +9545,17 @@ { "id": "model.group.name.reserved_name.app_error", "translation": "Gruppenname existiert bereits als reservierter Name" + }, + { + "id": "app.plugin.product_mode.app_error", + "translation": "Plugin {{.Name}} kann im Produktmodus nicht aktiviert werden." + }, + { + "id": "api.team.invite_guests_to_channels.license.error", + "translation": "Deine Lizenz unterstützt Gastkonten nicht" + }, + { + "id": "api.team.invite_guests_to_channels.disabled.error", + "translation": "Gastkonten sind deaktiviert" } ] From 350b67271457c4162dfc64ecbab848a59a9d5c78 Mon Sep 17 00:00:00 2001 From: Said Atrahouch Date: Fri, 14 Oct 2022 16:33:56 +0200 Subject: [PATCH 15/47] [MM-46549] Allow update the cloud customer --- model/cloud.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/model/cloud.go b/model/cloud.go index a9e8a2a4cf..b80b0b7c62 100644 --- a/model/cloud.go +++ b/model/cloud.go @@ -107,11 +107,12 @@ type ValidateBusinessEmailResponse struct { // CloudCustomerInfo represents editable info of a customer. type CloudCustomerInfo struct { - Name string `json:"name"` - Email string `json:"email,omitempty"` - ContactFirstName string `json:"contact_first_name,omitempty"` - ContactLastName string `json:"contact_last_name,omitempty"` - NumEmployees int `json:"num_employees"` + Name string `json:"name"` + Email string `json:"email,omitempty"` + ContactFirstName string `json:"contact_first_name,omitempty"` + ContactLastName string `json:"contact_last_name,omitempty"` + NumEmployees int `json:"num_employees"` + MonthlySubscriptionIntentWireTransfer *string `json:"monthly_subscription_intent_wire_transfer"` } // Address model represents a customer's address. From 439048561e96b601a60fa0f06b6670a0164a8b02 Mon Sep 17 00:00:00 2001 From: Said Atrahouch Date: Fri, 14 Oct 2022 17:07:04 +0200 Subject: [PATCH 16/47] Fix model --- model/cloud.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/model/cloud.go b/model/cloud.go index b80b0b7c62..88d50b6001 100644 --- a/model/cloud.go +++ b/model/cloud.go @@ -107,12 +107,12 @@ type ValidateBusinessEmailResponse struct { // CloudCustomerInfo represents editable info of a customer. type CloudCustomerInfo struct { - Name string `json:"name"` - Email string `json:"email,omitempty"` - ContactFirstName string `json:"contact_first_name,omitempty"` - ContactLastName string `json:"contact_last_name,omitempty"` - NumEmployees int `json:"num_employees"` - MonthlySubscriptionIntentWireTransfer *string `json:"monthly_subscription_intent_wire_transfer"` + Name string `json:"name"` + Email string `json:"email,omitempty"` + ContactFirstName string `json:"contact_first_name,omitempty"` + ContactLastName string `json:"contact_last_name,omitempty"` + NumEmployees int `json:"num_employees"` + MonthlySubscriptionIntentWireTransfer string `json:"monthly_subscription_intent_wire_transfer"` } // Address model represents a customer's address. From 21bf5b5dd5914b585dc545da1d5c09e7cab1599c Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Sat, 15 Oct 2022 01:41:45 +0530 Subject: [PATCH 17/47] MM-47465: Skip flaky TestDeleteChannel (#21392) https://mattermost.atlassian.net/browse/MM-47465 ```release-note NONE ``` --- api4/channel_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/api4/channel_test.go b/api4/channel_test.go index 16e432bf0d..1e1ea4b2e1 100644 --- a/api4/channel_test.go +++ b/api4/channel_test.go @@ -1779,6 +1779,7 @@ func TestSearchGroupChannels(t *testing.T) { } func TestDeleteChannel(t *testing.T) { + t.Skip("MM-47465") th := Setup(t).InitBasic() defer th.TearDown() c := th.Client From 9a857b47b43e73db4e44d574fe81f3c35eea1d95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Boros?= Date: Sat, 15 Oct 2022 06:32:14 +0200 Subject: [PATCH 18/47] feat: nested config value setting using env vars (#21049) Automatic Merge --- config/environment.go | 6 +++ config/environment_test.go | 76 +++++++++++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/config/environment.go b/config/environment.go index 12e6727484..4ded8d7399 100644 --- a/config/environment.go +++ b/config/environment.go @@ -4,6 +4,7 @@ package config import ( + "encoding/json" "os" "reflect" "strconv" @@ -73,6 +74,11 @@ func applyEnvKey(key, value string, rValueSubject reflect.Value) { } case reflect.SliceOf(reflect.TypeOf("")).Kind(): rFieldValue.Set(reflect.ValueOf(strings.Split(value, " "))) + case reflect.Map: + target := reflect.New(rFieldValue.Type()).Interface() + if err := json.Unmarshal([]byte(value), target); err == nil { + rFieldValue.Set(reflect.ValueOf(target).Elem()) + } } } diff --git a/config/environment_test.go b/config/environment_test.go index fb4b3261ed..e86bcc36e9 100644 --- a/config/environment_test.go +++ b/config/environment_test.go @@ -31,15 +31,54 @@ func TestRemoveEnvOverrides(t *testing.T) { expectedConfig *model.Config }{ { - name: "basic override", + name: "config override", inputConfig: modifiedDefault(func(in *model.Config) { *in.ServiceSettings.TLSMinVer = "1.4" + in.PluginSettings.PluginStates = map[string]*model.PluginState{ + "plugin1": { + Enable: false, + }, + } + in.PluginSettings.Plugins = map[string]map[string]interface{}{ + "com.mattermost.plugin-1": { + "key1": "value1", + }, + "com_mattermost_plugin-2": { + "key2": "value2", + }, + } }), env: map[string]string{ "MM_SERVICESETTINGS_TLSMINVER": "1.5", + "MM_PLUGINSETTINGS_PLUGINSTATES": `{ + "plugin1": { + "Enable": true + } + }`, + "MM_PLUGINSETTINGS_PLUGINS": `{ + "com.mattermost.plugin-1": { + "key1": "other-value" + }, + "com_mattermost_plugin-2": { + "key2": "other-value" + } + }`, }, expectedConfig: modifiedDefault(func(in *model.Config) { *in.ServiceSettings.TLSMinVer = "1.5" + in.PluginSettings.PluginStates = map[string]*model.PluginState{ + "plugin1": { + Enable: true, + }, + } + in.PluginSettings.Plugins = map[string]map[string]interface{}{ + "com.mattermost.plugin-1": { + "key1": "other-value", + }, + "com_mattermost_plugin-2": { + "key2": "other-value", + }, + } }), }, { @@ -102,6 +141,41 @@ func TestRemoveEnvOverrides(t *testing.T) { in.SqlSettings.DataSourceReplicas = []string{"otherthing", "alsothis"} }), }, + { + name: "complex env settings", + inputConfig: modifiedDefault(func(in *model.Config) { + }), + env: map[string]string{ + "MM_PLUGINSETTINGS_PLUGINSTATES": `{ + "com.mattermost.plugin-1": { + "enable": true + } + }`, + "MM_PLUGINSETTINGS_PLUGINS": `{ + "com.mattermost.plugin-1": { + "key": { + "key": "(?PKEY)-(?P\\d{1,6})(?P[,;]*)", + "value": "[$key-$id](https://example.com/?$project-$id)$comma" + } + } + }`, + }, + expectedConfig: modifiedDefault(func(in *model.Config) { + in.PluginSettings.PluginStates = map[string]*model.PluginState{ + "com.mattermost.plugin-1": { + Enable: true, + }, + } + in.PluginSettings.Plugins = map[string]map[string]interface{}{ + "com.mattermost.plugin-1": { + "key": map[string]interface{}{ + "key": "(?PKEY)-(?P\\d{1,6})(?P[,;]*)", + "value": "[$key-$id](https://example.com/?$project-$id)$comma", + }, + }, + } + }), + }, { name: "bad env", inputConfig: modifiedDefault(func(in *model.Config) { From 9e72ca18672419367d7ebf4a9b7f1772cdaf24f5 Mon Sep 17 00:00:00 2001 From: Rohitesh Gupta <1429138+srkgupta@users.noreply.github.com> Date: Tue, 18 Oct 2022 12:44:19 +0530 Subject: [PATCH 19/47] Updated CodeQL to use latest version (#21436) * Update CodeQL to latest version --- .github/codeql/codeql-config.yml | 14 ++++++++++++++ .github/workflows/codeql-analysis.yml | 21 ++++++++++++++------- 2 files changed, 28 insertions(+), 7 deletions(-) create mode 100644 .github/codeql/codeql-config.yml diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 0000000000..3c24100d19 --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,14 @@ +name: "CodeQL config" + +query-filters: + - exclude: + problem.severity: + - warning + - recommendation + - exclude: + id: go/log-injection + +paths-ignore: + - templates + - tests + - 'api4/*_local.go' diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 20c839211d..791a45ee95 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -6,7 +6,7 @@ on: branches: [ master ] schedule: - cron: '30 5,17 * * *' - + permissions: contents: read @@ -20,17 +20,24 @@ jobs: strategy: fail-fast: false matrix: - language: [ 'go', 'javascript' ] + language: [ 'go' ] steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} - debug: true + debug: false + config-file: ./.github/codeql/codeql-config.yml + + # Autobuild attempts to build any compiled languages - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v2 + + # Perform Analysis - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 From 6aec81eb6e3d8788a328695afe35b1464985f695 Mon Sep 17 00:00:00 2001 From: Ibrahim Serdar Acikgoz Date: Tue, 18 Oct 2022 13:30:22 +0300 Subject: [PATCH 20/47] add nil check for license manager (#21364) --- api4/license.go | 8 +++++++- api4/license_test.go | 34 +++++++++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/api4/license.go b/api4/license.go index e98f859910..4a56ca74ed 100644 --- a/api4/license.go +++ b/api4/license.go @@ -106,7 +106,13 @@ func addLicense(c *Context, w http.ResponseWriter, r *http.Request) { // skip the restrictions if license is a sanctioned trial if !license.IsSanctionedTrial() && license.IsTrialLicense() { - canStartTrialLicense, err := c.App.Srv().Platform().LicenseManager().CanStartTrial() + lm := c.App.Srv().Platform().LicenseManager() + if lm == nil { + c.Err = model.NewAppError("addLicense", "api.license.upgrade_needed.app_error", nil, "", http.StatusInternalServerError) + return + } + + canStartTrialLicense, err := lm.CanStartTrial() if err != nil { c.Err = model.NewAppError("addLicense", "api.license.add_license.open.app_error", nil, "", http.StatusInternalServerError) return diff --git a/api4/license_test.go b/api4/license_test.go index 05acd6378c..dbf0e62bfc 100644 --- a/api4/license_test.go +++ b/api4/license_test.go @@ -91,9 +91,6 @@ func TestUploadLicenseFile(t *testing.T) { mockLicenseValidator := mocks2.LicenseValidatorIface{} defer testutils.ResetLicenseValidator() - //startTimestamp, err := time.Parse("2 Jan 2006 3:04 pm", "1 Jan 2021 12:00 am") - //require.Nil(t, err) - userCount := 100 mills := model.GetMillis() @@ -125,6 +122,37 @@ func TestUploadLicenseFile(t *testing.T) { require.Equal(t, http.StatusBadRequest, resp.StatusCode) }) + t.Run("try to get gone through trial, with TE build", func(t *testing.T) { + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = false }) + th.App.Srv().Platform().SetLicenseManager(nil) + + mockLicenseValidator := mocks2.LicenseValidatorIface{} + defer testutils.ResetLicenseValidator() + + license := model.License{ + Id: model.NewId(), + Features: &model.Features{ + Users: model.NewInt(100), + }, + Customer: &model.Customer{ + Name: "Test", + }, + StartsAt: model.GetMillis() + 100, + ExpiresAt: model.GetMillis() + 100 + (30*(time.Hour*24) + (time.Hour * 8)).Milliseconds(), + } + + mockLicenseValidator.On("LicenseFromBytes", mock.Anything).Return(&license, nil).Once() + licenseBytes, err := json.Marshal(license) + require.NoError(t, err) + + mockLicenseValidator.On("ValidateLicense", mock.Anything).Return(true, string(licenseBytes)) + utils.LicenseValidator = &mockLicenseValidator + + resp, err := th.SystemAdminClient.UploadLicenseFile([]byte("")) + CheckErrorID(t, err, "api.license.upgrade_needed.app_error") + require.Equal(t, http.StatusInternalServerError, resp.StatusCode) + }) + t.Run("allow uploading sanctioned trials even if server already gone through trial", func(t *testing.T) { mockLicenseValidator := mocks2.LicenseValidatorIface{} defer testutils.ResetLicenseValidator() From b24440c2bdd16cd7f63b9116e6bf08e95cbe4064 Mon Sep 17 00:00:00 2001 From: mattermod Date: Tue, 18 Oct 2022 11:32:34 +0000 Subject: [PATCH 21/47] Update latest version to 7.4.0 --- build/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Dockerfile b/build/Dockerfile index ab99fe0dcd..55183179d0 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -8,7 +8,7 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"] ENV PATH="/mattermost/bin:${PATH}" ARG PUID=2000 ARG PGID=2000 -ARG MM_PACKAGE="https://releases.mattermost.com/7.3.0/mattermost-7.3.0-linux-amd64.tar.gz?src=docker" +ARG MM_PACKAGE="https://releases.mattermost.com/7.4.0/mattermost-7.4.0-linux-amd64.tar.gz?src=docker" # # Install needed packages and indirect dependencies RUN apt-get update \ From 3946c6397072ca9e658fa1050514e2e2d9f5fa28 Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Tue, 18 Oct 2022 19:37:40 +0530 Subject: [PATCH 22/47] MM-47612: Skip flaky test TestPluginAPIDeleteUserPreferences (#21425) https://mattermost.atlassian.net/browse/MM-47612 ```release-note NONE ``` --- app/plugin_api_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/app/plugin_api_test.go b/app/plugin_api_test.go index 1068adaa64..d36c4fadc5 100644 --- a/app/plugin_api_test.go +++ b/app/plugin_api_test.go @@ -196,6 +196,7 @@ func TestPluginAPIGetUserPreferences(t *testing.T) { } func TestPluginAPIDeleteUserPreferences(t *testing.T) { + t.Skip("MM-47612") th := Setup(t) defer th.TearDown() api := th.SetupPluginAPI() From 28698abb9f54f0cb173a8226fd378863a5b3ba44 Mon Sep 17 00:00:00 2001 From: Shivashis Padhi Date: Wed, 19 Oct 2022 09:50:41 +0530 Subject: [PATCH 23/47] Apply role filters for /get/users in channel (#21251) --- store/sqlstore/user_store.go | 2 ++ store/storetest/user_store.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/store/sqlstore/user_store.go b/store/sqlstore/user_store.go index bd89779355..e41d047157 100644 --- a/store/sqlstore/user_store.go +++ b/store/sqlstore/user_store.go @@ -742,6 +742,8 @@ func (us SqlUserStore) GetProfilesInChannel(options *model.UserGetOptions) ([]*m query = query.Where("u.DeleteAt = 0") } + query = applyMultiRoleFilters(query, options.Roles, options.TeamRoles, options.ChannelRoles, us.DriverName() == model.DatabaseDriverPostgres) + queryString, args, err := query.ToSql() if err != nil { return nil, errors.Wrap(err, "get_profiles_in_channel_tosql") diff --git a/store/storetest/user_store.go b/store/storetest/user_store.go index 6d704854b4..3dd02d495a 100644 --- a/store/storetest/user_store.go +++ b/store/storetest/user_store.go @@ -1003,6 +1003,36 @@ func testUserStoreGetProfilesInChannel(t *testing.T, ss store.Store) { require.NoError(t, err) assert.Equal(t, []*model.User{sanitized(u1)}, users) }) + + t.Run("Filter by channel members and channel admins", func(t *testing.T) { + // save admin for c1 + user2Admin, err := ss.User().Save(&model.User{ + Email: MakeEmail(), + Username: "bbb" + model.NewId(), + }) + require.NoError(t, err) + defer func() { require.NoError(t, ss.User().PermanentDelete(user2Admin.Id)) }() + _, nErr = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: user2Admin.Id}, -1) + require.NoError(t, nErr) + + _, nErr = ss.Channel().SaveMember(&model.ChannelMember{ + ChannelId: c1.Id, + UserId: user2Admin.Id, + NotifyProps: model.GetDefaultChannelNotifyProps(), + ExplicitRoles: "channel_admin", + }) + require.NoError(t, nErr) + ss.Channel().UpdateMembersRole(c1.Id, []string{user2Admin.Id}) + + users, err := ss.User().GetProfilesInChannel(&model.UserGetOptions{ + InChannelId: c1.Id, + ChannelRoles: []string{model.ChannelAdminRoleId}, + Page: 0, + PerPage: 5, + }) + require.NoError(t, err) + assert.Equal(t, user2Admin.Id, users[0].Id) + }) } func testUserStoreGetProfilesInChannelByAdmin(t *testing.T, ss store.Store, s SqlStore) { From 10a627fac1938d3d91d510082e750b47ccb3b016 Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Wed, 19 Oct 2022 20:51:00 +0530 Subject: [PATCH 24/47] MM-45723: Include DM/GM channels for user indexing (#21391) There were 2 separate issues here: 1. We were excluding DM/GM channels for user indexing. Not sure why. This prevented users to be shown as channel members in autocomplete results in both Bleve and Elasticsearch. 2. We were incorrectly deciding to search across the team based on if the current channel is in the list of allowed channels or not. But we were incorrectly wiping the list of allowed channels even if a channelid was passed. This makes it work same as a DB search. One catch is that for the correct results to appear, a re-indexing is required. ```release-note Autocompletion results using Elasticsearch or Bleve will correctly show a user as a channel member in DM/GM channels. To force this change to appear, a re-indexing will be necessary. ``` --- store/searchlayer/user_layer.go | 23 +++++++++++++++++++---- store/sqlstore/user_store.go | 11 ++++++++++- store/storetest/user_store.go | 25 +++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/store/searchlayer/user_layer.go b/store/searchlayer/user_layer.go index 2c63dabd3a..268b18cacc 100644 --- a/store/searchlayer/user_layer.go +++ b/store/searchlayer/user_layer.go @@ -37,7 +37,7 @@ func (s *SearchUserStore) deleteUserIndex(user *model.User) { func (s *SearchUserStore) Search(teamId, term string, options *model.UserSearchOptions) ([]*model.User, error) { for _, engine := range s.rootStore.searchEngine.GetActiveEngines() { if engine.IsSearchEnabled() { - listOfAllowedChannels, nErr := s.getListOfAllowedChannelsForTeam(teamId, options.ViewRestrictions) + listOfAllowedChannels, nErr := s.getListOfAllowedChannels(teamId, "", options.ViewRestrictions) if nErr != nil { mlog.Warn("Encountered error on Search.", mlog.String("search_engine", engine.GetName()), mlog.Err(nErr)) continue @@ -148,7 +148,7 @@ func (s *SearchUserStore) autocompleteUsersInChannelByEngine(engine searchengine return autocomplete, nil } -// getListOfAllowedChannelsForTeam return the list of allowed channels to search user based on the +// getListOfAllowedChannels return the list of allowed channels to search user based on the // next scenarios: // - If there isn't view restrictions (team or channel) and no team id to filter them, then all // channels are allowed (nil return) @@ -159,7 +159,7 @@ func (s *SearchUserStore) autocompleteUsersInChannelByEngine(engine searchengine // - If we receive channels restrictions we get: // - If we don't have team id, we get those restricted channels (guest accounts and quick search) // - If we have a team id then we only return those restricted channels that belongs to that team -func (s *SearchUserStore) getListOfAllowedChannelsForTeam(teamId string, viewRestrictions *model.ViewUsersRestrictions) ([]string, error) { +func (s *SearchUserStore) getListOfAllowedChannels(teamId, channelId string, viewRestrictions *model.ViewUsersRestrictions) ([]string, error) { var listOfAllowedChannels []string if viewRestrictions == nil && teamId == "" { // nil return without error means all channels are allowed @@ -174,6 +174,20 @@ func (s *SearchUserStore) getListOfAllowedChannelsForTeam(teamId string, viewRes for _, channel := range channels { listOfAllowedChannels = append(listOfAllowedChannels, channel.Id) } + + if channelId != "" { + ch, err := s.rootStore.Channel().Get(channelId, true) + if err != nil { + return nil, errors.Wrapf(err, "failed to get channel with id: %s", channelId) + } + // Check if DM/GM channel, and add to the list. + // This is because GetTeamChannels does not return DM/GM channels. + // And since the channelId is passed from the API layer, it is already + // auth checked to confirm that the user has permission. + if ch.IsGroupOrDirect() { + listOfAllowedChannels = append(listOfAllowedChannels, channelId) + } + } return listOfAllowedChannels, nil } @@ -196,7 +210,7 @@ func (s *SearchUserStore) getListOfAllowedChannelsForTeam(teamId string, viewRes func (s *SearchUserStore) AutocompleteUsersInChannel(teamId, channelId, term string, options *model.UserSearchOptions) (*model.UserAutocompleteInChannel, error) { for _, engine := range s.rootStore.searchEngine.GetActiveEngines() { if engine.IsAutocompletionEnabled() { - listOfAllowedChannels, nErr := s.getListOfAllowedChannelsForTeam(teamId, options.ViewRestrictions) + listOfAllowedChannels, nErr := s.getListOfAllowedChannels(teamId, channelId, options.ViewRestrictions) if nErr != nil { mlog.Warn("Encountered error on AutocompleteUsersInChannel.", mlog.String("search_engine", engine.GetName()), mlog.Err(nErr)) continue @@ -205,6 +219,7 @@ func (s *SearchUserStore) AutocompleteUsersInChannel(teamId, channelId, term str return &model.UserAutocompleteInChannel{}, nil } options.ListOfAllowedChannels = listOfAllowedChannels + autocomplete, nErr := s.autocompleteUsersInChannelByEngine(engine, teamId, channelId, term, options) if nErr != nil { mlog.Warn("Encountered error on AutocompleteUsersInChannel.", mlog.String("search_engine", engine.GetName()), mlog.Err(nErr)) diff --git a/store/sqlstore/user_store.go b/store/sqlstore/user_store.go index e41d047157..9eb8535d90 100644 --- a/store/sqlstore/user_store.go +++ b/store/sqlstore/user_store.go @@ -1805,7 +1805,16 @@ func (us SqlUserStore) GetUsersBatchForIndexing(startTime int64, startFileID str `). From("ChannelMembers cm"). Join("Channels c ON cm.ChannelId = c.Id"). - Where(sq.Eq{"c.Type": model.ChannelTypeOpen, "cm.UserId": userIds}). + Where(sq.And{ + sq.Eq{ + "cm.UserId": userIds, + }, + sq.Or{ + sq.Eq{"c.Type": model.ChannelTypeOpen}, + sq.Eq{"c.Type": model.ChannelTypeDirect}, + sq.Eq{"c.Type": model.ChannelTypeGroup}, + }, + }). ToSql() if err != nil { return nil, errors.Wrap(err, "GetUsersBatchForIndexing_ToSql2") diff --git a/store/storetest/user_store.go b/store/storetest/user_store.go index 3dd02d495a..c938056249 100644 --- a/store/storetest/user_store.go +++ b/store/storetest/user_store.go @@ -4912,10 +4912,35 @@ func testUserStoreGetUsersBatchForIndexing(t *testing.T, ss store.Store) { }) require.NoError(t, err) + cDM := &model.Channel{ + Name: model.NewId() + "__" + model.NewId(), + Type: model.ChannelTypeDirect, + } + cm1 := &model.ChannelMember{ + UserId: u3.Id, + ChannelId: cDM.Id, + NotifyProps: model.GetDefaultChannelNotifyProps(), + } + cm2 := &model.ChannelMember{ + UserId: u2.Id, + ChannelId: cDM.Id, + NotifyProps: model.GetDefaultChannelNotifyProps(), + } + cDM, nErr = ss.Channel().SaveDirectChannel(cDM, cm1, cm2) + require.NoError(t, nErr) + // Getting all users res1List, err := ss.User().GetUsersBatchForIndexing(u1.CreateAt-1, "", 100) require.NoError(t, err) assert.Len(t, res1List, 3) + for _, user := range res1List { + switch user.Id { + case u2.Id: + assert.ElementsMatch(t, user.ChannelsIds, []string{cPub1.Id, cPub2.Id, cDM.Id}) + case u3.Id: + assert.ElementsMatch(t, user.ChannelsIds, []string{cPub2.Id, cDM.Id}) + } + } // Testing pagination res2List, err := ss.User().GetUsersBatchForIndexing(u1.CreateAt-1, "", 1) From 5730d3da6e971934e26d898df12da885e3986c80 Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Thu, 20 Oct 2022 00:53:20 +0530 Subject: [PATCH 25/47] MM-47250: Add new system console section (#21333) * MM-47250: Add new system console section We add a new section in system console for products. ```release-note Added a new section in system console for products. For now, it only contains boards specific settings. ``` * fix unit tests ```release-note NONE ``` * change comment ```release-note NONE ``` --- app/permissions_migrations.go | 22 ++++++++++++++++++++++ model/config.go | 12 ++++++++++++ model/migration.go | 1 + model/permission.go | 18 ++++++++++++++++++ model/role.go | 3 +++ services/telemetry/telemetry.go | 5 +++++ testlib/store.go | 1 + 7 files changed, 62 insertions(+) diff --git a/app/permissions_migrations.go b/app/permissions_migrations.go index 613adc927f..2b638be162 100644 --- a/app/permissions_migrations.go +++ b/app/permissions_migrations.go @@ -991,6 +991,27 @@ func (a *App) getPlaybooksPermissionsAddManageRoles() (permissionsMap, error) { return transformations, nil } +func (a *App) getProductsBoardsPermissions() (permissionsMap, error) { + transformations := []permissionTransformation{} + + permissionsProductsRead := []string{model.PermissionSysconsoleReadProductsBoards.Id} + permissionsProductsWrite := []string{model.PermissionSysconsoleWriteProductsBoards.Id} + + // Give the new subsection READ permissions to any user with SYSTEM_MANAGER + transformations = append(transformations, permissionTransformation{ + On: permissionOr(isRole(model.SystemManagerRoleId)), + Add: permissionsProductsRead, + }) + + // Give the new subsection WRITE permissions to any user with SYSTEM_ADMIN + transformations = append(transformations, permissionTransformation{ + On: permissionOr(isRole(model.SystemAdminRoleId)), + Add: permissionsProductsWrite, + }) + + return transformations, nil +} + // DoPermissionsMigrations execute all the permissions migrations need by the current version. func (a *App) DoPermissionsMigrations() error { return a.Srv().doPermissionsMigrations() @@ -1032,6 +1053,7 @@ func (s *Server) doPermissionsMigrations() error { {Key: model.MigrationKeyAddPlaybooksPermissions, Migration: a.getAddPlaybooksPermissions}, {Key: model.MigrationKeyAddCustomUserGroupsPermissions, Migration: a.getAddCustomUserGroupsPermissions}, {Key: model.MigrationKeyAddPlayboosksManageRolesPermissions, Migration: a.getPlaybooksPermissionsAddManageRoles}, + {Key: model.MigrationKeyAddProductsBoardsPermissions, Migration: a.getProductsBoardsPermissions}, } roles, err := s.Store().Role().GetAll() diff --git a/model/config.go b/model/config.go index 886b7e5820..bccc3904a1 100644 --- a/model/config.go +++ b/model/config.go @@ -2747,6 +2747,16 @@ func (s *CloudSettings) SetDefaults() { } } +type ProductSettings struct { + EnablePublicSharedBoards *bool +} + +func (s *ProductSettings) SetDefaults() { + if s.EnablePublicSharedBoards == nil { + s.EnablePublicSharedBoards = NewBool(false) + } +} + type PluginState struct { Enable bool } @@ -3136,6 +3146,7 @@ type Config struct { DataRetentionSettings DataRetentionSettings MessageExportSettings MessageExportSettings JobSettings JobSettings + ProductSettings ProductSettings PluginSettings PluginSettings DisplaySettings DisplaySettings GuestAccountsSettings GuestAccountsSettings @@ -3235,6 +3246,7 @@ func (o *Config) SetDefaults() { o.ThemeSettings.SetDefaults() o.ClusterSettings.SetDefaults() o.PluginSettings.SetDefaults(o.LogSettings) + o.ProductSettings.SetDefaults() o.AnalyticsSettings.SetDefaults() o.ComplianceSettings.SetDefaults() o.LocalizationSettings.SetDefaults() diff --git a/model/migration.go b/model/migration.go index 4958f80e85..e0e9ae2267 100644 --- a/model/migration.go +++ b/model/migration.go @@ -38,4 +38,5 @@ const ( MigrationKeyAddPlaybooksPermissions = "playbooks_permissions" MigrationKeyAddCustomUserGroupsPermissions = "custom_groups_permissions" MigrationKeyAddPlayboosksManageRolesPermissions = "playbooks_manage_roles" + MigrationKeyAddProductsBoardsPermissions = "products_boards" ) diff --git a/model/permission.go b/model/permission.go index 65f8edaca8..76cf07c872 100644 --- a/model/permission.go +++ b/model/permission.go @@ -354,6 +354,9 @@ var PermissionRunManageProperties *Permission var PermissionRunManageMembers *Permission var PermissionRunView *Permission +var PermissionSysconsoleReadProductsBoards *Permission +var PermissionSysconsoleWriteProductsBoards *Permission + // General permission that encompasses all system admin functions // in the future this could be broken up to allow access to some // admin functions but not others @@ -2070,6 +2073,19 @@ func initializePermissions() { PermissionScopeRun, } + PermissionSysconsoleReadProductsBoards = &Permission{ + "sysconsole_read_products_boards", + "", + "", + PermissionScopeSystem, + } + PermissionSysconsoleWriteProductsBoards = &Permission{ + "sysconsole_write_products_boards", + "", + "", + PermissionScopeSystem, + } + SysconsoleReadPermissions = []*Permission{ PermissionSysconsoleReadAboutEditionAndLicense, PermissionSysconsoleReadBilling, @@ -2125,6 +2141,7 @@ func initializePermissions() { PermissionSysconsoleReadExperimentalFeatures, PermissionSysconsoleReadExperimentalFeatureFlags, PermissionSysconsoleReadExperimentalBleve, + PermissionSysconsoleReadProductsBoards, } SysconsoleWritePermissions = []*Permission{ @@ -2182,6 +2199,7 @@ func initializePermissions() { PermissionSysconsoleWriteExperimentalFeatures, PermissionSysconsoleWriteExperimentalFeatureFlags, PermissionSysconsoleWriteExperimentalBleve, + PermissionSysconsoleWriteProductsBoards, } SystemScopedPermissionsMinusSysconsole := []*Permission{ diff --git a/model/role.go b/model/role.go index 7823f133b4..ac3fa3204e 100644 --- a/model/role.go +++ b/model/role.go @@ -261,6 +261,7 @@ func init() { PermissionSysconsoleReadExperimentalFeatures.Id, PermissionSysconsoleReadExperimentalFeatureFlags.Id, PermissionSysconsoleReadExperimentalBleve.Id, + PermissionSysconsoleReadProductsBoards.Id, } SystemManagerDefaultPermissions = []string{ @@ -339,6 +340,8 @@ func init() { PermissionSysconsoleWriteIntegrationsBotAccounts.Id, PermissionSysconsoleWriteIntegrationsGif.Id, PermissionSysconsoleWriteIntegrationsCors.Id, + PermissionSysconsoleReadProductsBoards.Id, + PermissionSysconsoleWriteProductsBoards.Id, } SystemCustomGroupAdminDefaultPermissions = []string{ diff --git a/services/telemetry/telemetry.go b/services/telemetry/telemetry.go index f19e5a4188..821b4c8a91 100644 --- a/services/telemetry/telemetry.go +++ b/services/telemetry/telemetry.go @@ -67,6 +67,7 @@ const ( TrackConfigBleve = "config_bleve" TrackConfigExport = "config_export" TrackFeatureFlags = "config_feature_flags" + TrackConfigProducts = "products" TrackPermissionsGeneral = "permissions_general" TrackPermissionsSystemScheme = "permissions_system_scheme" TrackPermissionsTeamSchemes = "permissions_team_schemes" @@ -827,6 +828,10 @@ func (ts *TelemetryService) trackConfig() { "retention_days": *cfg.ExportSettings.RetentionDays, }) + ts.SendTelemetry(TrackConfigProducts, map[string]any{ + "enable_public_shared_boards": *cfg.ProductSettings.EnablePublicSharedBoards, + }) + // Convert feature flags to map[string]any for sending flags := cfg.FeatureFlags.ToMap() interfaceFlags := make(map[string]any) diff --git a/testlib/store.go b/testlib/store.go index 5566989362..800764da07 100644 --- a/testlib/store.go +++ b/testlib/store.go @@ -69,6 +69,7 @@ func GetMockStoreForSetupFunctions() *mocks.Store { systemStore.On("GetByName", model.MigrationKeyAddCustomUserGroupsPermissions).Return(&model.System{Name: model.MigrationKeyAddCustomUserGroupsPermissions, Value: "true"}, nil) systemStore.On("GetByName", model.MigrationKeyAddPlayboosksManageRolesPermissions).Return(&model.System{Name: model.MigrationKeyAddPlayboosksManageRolesPermissions, Value: "true"}, nil) systemStore.On("GetByName", "CustomGroupAdminRoleCreationMigrationComplete").Return(&model.System{Name: model.MigrationKeyAddPlayboosksManageRolesPermissions, Value: "true"}, nil) + systemStore.On("GetByName", "products_boards").Return(&model.System{Name: "products_boards", Value: "true"}, nil) systemStore.On("InsertIfExists", mock.AnythingOfType("*model.System")).Return(&model.System{}, nil).Once() systemStore.On("Save", mock.AnythingOfType("*model.System")).Return(nil) From 5b2b2be753a7544c08b600cbc31af34a3ef38f90 Mon Sep 17 00:00:00 2001 From: jprusch Date: Wed, 19 Oct 2022 06:20:55 +0200 Subject: [PATCH 26/47] Translated using Weblate (German) Currently translated at 100.0% (2386 of 2386 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/de/ Translated using Weblate (German) Currently translated at 100.0% (2386 of 2386 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/de/ Translated using Weblate (German) Currently translated at 100.0% (2385 of 2385 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/de/ Translated using Weblate (German) Currently translated at 100.0% (2385 of 2385 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/de/ Translated using Weblate (German) Currently translated at 100.0% (2384 of 2384 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/de/ Translated using Weblate (German) Currently translated at 100.0% (2382 of 2382 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/de/ --- i18n/de.json | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/i18n/de.json b/i18n/de.json index ae6d93035c..923ae5eedd 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -1777,7 +1777,7 @@ }, { "id": "api.templates.email_change_verify_body.title", - "translation": "Du hast deine E-Mail-Adresse geändert" + "translation": "Du hast deine E-Mail-Adresse erfolgreich geändert" }, { "id": "api.templates.email_change_verify_subject", @@ -4801,11 +4801,11 @@ }, { "id": "web.error.unsupported_browser.min_browser_version.edge", - "translation": "Version 44+" + "translation": "Version 95+" }, { "id": "web.error.unsupported_browser.min_browser_version.chrome", - "translation": "Version 100+" + "translation": "Version 102+" }, { "id": "web.error.unsupported_browser.learn_more", @@ -8380,7 +8380,7 @@ }, { "id": "app.notification.body.dm.subTitle", - "translation": "Währen du weg warst, hat dir {{.SenderName}} eine neue Direktnachricht gesendet." + "translation": "Während du weg warst, hat dir {{.SenderName}} eine neue Direktnachricht gesendet." }, { "id": "app.license.generate_renewal_token.no_license", @@ -9537,5 +9537,21 @@ { "id": "app.user.get_badge_count.app_error", "translation": "Wir konnten den Nachrichtenzähler für den Benutzer nicht abfragen." + }, + { + "id": "app.job.error", + "translation": "Fehler bei der Auftragsausführung." + }, + { + "id": "app.last_accessible_file.app_error", + "translation": "Fehler beim Abrufen der letzten zugänglichen Datei" + }, + { + "id": "app.file.cloud.get.app_error", + "translation": "Die Datei kann nicht abgerufen werden, da sie das Limit des Cloud-Plans überschritten hat." + }, + { + "id": "model.group.name.reserved_name.app_error", + "translation": "Gruppenname existiert bereits als reservierter Name" } ] From 46dde366d913d0ee0e10d7fd95c330311b487007 Mon Sep 17 00:00:00 2001 From: Tom De Moor Date: Wed, 19 Oct 2022 06:20:55 +0200 Subject: [PATCH 27/47] Translated using Weblate (Dutch) Currently translated at 99.9% (2384 of 2386 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/nl/ Translated using Weblate (Dutch) Currently translated at 100.0% (2386 of 2386 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/nl/ Translated using Weblate (Dutch) Currently translated at 100.0% (2382 of 2382 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/nl/ --- i18n/nl.json | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/i18n/nl.json b/i18n/nl.json index 6f883eaa1a..2695bddadb 100644 --- a/i18n/nl.json +++ b/i18n/nl.json @@ -1777,7 +1777,7 @@ }, { "id": "api.templates.email_change_verify_body.title", - "translation": "Je hebt je e-mailadres bijgewerkt" + "translation": "Je hebt je e-mailadres succesvol bijgewerkt" }, { "id": "api.templates.email_change_verify_subject", @@ -4325,7 +4325,7 @@ }, { "id": "model.post.is_valid.create_at.app_error", - "translation": "Aangemaakt op moet een geldige tijd bevatten" + "translation": "Aangemaakt op moet een geldige tijd bevatten." }, { "id": "model.post.is_valid.file_ids.app_error", @@ -4781,11 +4781,11 @@ }, { "id": "web.error.unsupported_browser.min_browser_version.edge", - "translation": "Versie 44+" + "translation": "Versie 95+" }, { "id": "web.error.unsupported_browser.min_browser_version.chrome", - "translation": "Versie 100+" + "translation": "Versie 106+" }, { "id": "web.error.unsupported_browser.learn_more", @@ -9537,5 +9537,33 @@ { "id": "api.templates.delinquency_90.title", "translation": "Jouw Mattermost-werkruimte werd gedowngraded" + }, + { + "id": "app.job.error", + "translation": "Fout tijdens het uitvoeren van de opdracht." + }, + { + "id": "model.group.name.reserved_name.app_error", + "translation": "groepsnaam bestaat al als een gereserveerde naam" + }, + { + "id": "app.plugin.product_mode.app_error", + "translation": "Plugin {{.Name}} kan niet worden ingeschakeld in de productmodus." + }, + { + "id": "app.last_accessible_file.app_error", + "translation": "Fout bij het ophalen van het laatst toegankelijke bestand" + }, + { + "id": "app.file.cloud.get.app_error", + "translation": "Kan het bestand niet ophalen omdat dit bestand over de limiet van het cloud plan is." + }, + { + "id": "api.team.invite_guests_to_channels.license.error", + "translation": "Jouw licentie ondersteunt geen gastaccounts" + }, + { + "id": "api.team.invite_guests_to_channels.disabled.error", + "translation": "Gastaccounts zijn uitgeschakeld" } ] From 1ee8f17f8e7058f35ec907263c913b1d0cc1215e Mon Sep 17 00:00:00 2001 From: Kaya Zeren Date: Wed, 19 Oct 2022 06:20:55 +0200 Subject: [PATCH 28/47] Translated using Weblate (Turkish) Currently translated at 100.0% (2386 of 2386 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/tr/ Translated using Weblate (Turkish) Currently translated at 100.0% (2385 of 2385 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/tr/ Translated using Weblate (Turkish) Currently translated at 100.0% (2384 of 2384 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/tr/ Translated using Weblate (Turkish) Currently translated at 100.0% (2382 of 2382 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/tr/ --- i18n/tr.json | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/i18n/tr.json b/i18n/tr.json index 563015338b..bdb2ca0902 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -2725,7 +2725,7 @@ }, { "id": "app.import.validate_team_import_data.name_reserved.error", - "translation": "Takım adında ayrılmış bir sözcük var." + "translation": "Takım adında sistem kullanımına ayrılmış bir sözcük var." }, { "id": "app.import.validate_team_import_data.scheme_invalid.error", @@ -5577,7 +5577,7 @@ }, { "id": "api.ldap_groups.existing_reserved_name_error", - "translation": "aynı adlı bir grup zaten ayrılmış bir ad olarak var" + "translation": "aynı adlı bir grup zaten sistem kullanımına ayrılmış bir ad olarak var" }, { "id": "api.ldap_groups.existing_user_name_error", @@ -5849,11 +5849,11 @@ }, { "id": "web.error.unsupported_browser.min_browser_version.chrome", - "translation": "Sürüm 100+" + "translation": "Sürüm 106+" }, { "id": "web.error.unsupported_browser.min_browser_version.edge", - "translation": "Sürüm 44+" + "translation": "Sürüm 95+" }, { "id": "web.error.unsupported_browser.min_browser_version.firefox", @@ -9537,5 +9537,33 @@ { "id": "app.user.get_badge_count.app_error", "translation": "Kullanıcının nişan sayısını alamadık." + }, + { + "id": "app.job.error", + "translation": "Görev yürütülürken sorun çıktı." + }, + { + "id": "app.last_accessible_file.app_error", + "translation": "Erişilebilen son dosya alınırken sorun çıktı" + }, + { + "id": "app.file.cloud.get.app_error", + "translation": "Cloud tarifesinin sınırlarını aştığından dosya alınamadı." + }, + { + "id": "model.group.name.reserved_name.app_error", + "translation": "aynı adlı bir grup zaten sistem kullanımına ayrılmış bir ad olarak var" + }, + { + "id": "app.plugin.product_mode.app_error", + "translation": "{{.Name}} uygulama eki ürün kipinde etkinleştirilemez." + }, + { + "id": "api.team.invite_guests_to_channels.license.error", + "translation": "Lisansınız konuk hesaplarının kullanılmasını desteklemiyor" + }, + { + "id": "api.team.invite_guests_to_channels.disabled.error", + "translation": "Konuk hesapları devre dışı bırakılmış" } ] From bab91b41f8f511398c0ed001cc423efc786498b8 Mon Sep 17 00:00:00 2001 From: Dennis Cheng Date: Wed, 19 Oct 2022 06:20:56 +0200 Subject: [PATCH 29/47] Translated using Weblate (Chinese (Traditional)) Currently translated at 75.7% (1805 of 2382 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/zh_Hant/ --- i18n/zh-TW.json | 58 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/i18n/zh-TW.json b/i18n/zh-TW.json index a24b40c159..0f3fda947e 100644 --- a/i18n/zh-TW.json +++ b/i18n/zh-TW.json @@ -421,7 +421,7 @@ }, { "id": "api.command_channel_header.update_channel.app_error", - "translation": "更新當前頻道時錯誤。" + "translation": "更新當前頻道名稱時錯誤。" }, { "id": "api.command_channel_purpose.channel.app_error", @@ -453,7 +453,7 @@ }, { "id": "api.command_channel_purpose.update_channel.app_error", - "translation": "更新當前頻道時錯誤。" + "translation": "更新當前頻道用途時錯誤。" }, { "id": "api.command_channel_remove.channel.app_error", @@ -619,7 +619,7 @@ }, { "id": "api.command_help.desc", - "translation": "開啟 Mattermost 說明頁面" + "translation": "顯示 Mattermost 說明頁面" }, { "id": "api.command_help.name", @@ -7302,5 +7302,57 @@ { "id": "api.cloud.teams_limit_reached.create", "translation": "無法建立團隊,已達到團隊數量上限" + }, + { + "id": "api.command_custom_status.success", + "translation": "你的狀態已設為 “{{.EmojiName}} {{.StatusMessage}}”。您可以從頻道側邊欄中的狀態彈出視窗更改您的狀態。" + }, + { + "id": "api.command_custom_status.name", + "translation": "狀態" + }, + { + "id": "api.command_custom_status.hint", + "translation": "[:emoji_name:] [status_message] 或清除" + }, + { + "id": "api.command_custom_status.desc", + "translation": "設定或清除您的狀態" + }, + { + "id": "api.command_custom_status.clear.success", + "translation": "狀態已清除。" + }, + { + "id": "api.command_custom_status.clear.app_error", + "translation": "清除狀態錯誤。" + }, + { + "id": "api.command_custom_status.app_error", + "translation": "設定狀態錯誤。" + }, + { + "id": "api.command_channel_purpose.update_channel.max_length", + "translation": "輸入的文本超出字數限制。頻道用途不得超過 {{.MaxLength}} 個字數。" + }, + { + "id": "api.command_channel_header.update_channel.max_length", + "translation": "輸入的文本超出字數限制。頻道標題不得超過 {{.MaxLength}} 個字數。" + }, + { + "id": "api.cloud.delinquency_email.missing_email_to_trigger", + "translation": "缺少發送拖欠電子郵件的必要欄位。" + }, + { + "id": "api.cloud.cws_webhook_event_missing_error", + "translation": "Webhook 事件未處理。事件可能遺失或無效。" + }, + { + "id": "api.channel.create_channel.direct_channel.team_restricted_error", + "translation": "無法在這些使用者間創建直連頻道因為他們不屬於同一個團隊。" + }, + { + "id": "api.admin.saml.failure_reset_authdata_to_email.app_error", + "translation": "無法寄出重設AuthData欄位失敗的電子郵件。" } ] From c9103687de1b0b17b50fee877bcfb32227cda2e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=B3th=20Csaba=20//=20Online=20ERP=20Hungary=20Kft?= Date: Wed, 19 Oct 2022 06:20:56 +0200 Subject: [PATCH 30/47] Translated using Weblate (Hungarian) Currently translated at 98.3% (2346 of 2385 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/hu/ Translated using Weblate (Hungarian) Currently translated at 98.2% (2343 of 2385 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/hu/ Translated using Weblate (Hungarian) Currently translated at 98.3% (2342 of 2382 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/hu/ --- i18n/hu.json | 48 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/i18n/hu.json b/i18n/hu.json index b10693e9f8..2f7e270a62 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -3125,7 +3125,7 @@ }, { "id": "oauth.gitlab.tos.error", - "translation": "A GitLab Általános Szerződési Feltételei frissültek. Kérjük, látogassa meg a gitlab.com webhelyet, hogy elfogadja őket, majd próbáljon meg újra bejelentkezni a Mattermost-ba." + "translation": "A GitLab Általános Szerződési Feltételei frissültek. Kérjük, látogassa meg a {{.URL}} webhelyet, hogy elfogadja őket, majd próbáljon meg újra bejelentkezni a Mattermost-ba." }, { "id": "model.websocket_client.connect_fail.app_error", @@ -5469,15 +5469,15 @@ }, { "id": "api.templates.payment_failed.title", - "translation": "Sikertelen fizetés" + "translation": "A fizetési művelet nem sikerült" }, { "id": "api.templates.payment_failed.subject", - "translation": "Művelet szükséges: Sikertelen fizetés a Mattermost Cloudra" + "translation": "Művelet szükséges: Sikertelen fizetés a Mattermost {{.Plan}} csomagra" }, { "id": "api.templates.payment_failed.info3", - "translation": "A Mattermost Cloud zavartalan előfizetésének biztosítása érdekében kérjük, vegye fel a kapcsolatot a pénzintézetével a probléma megoldása érdekében, vagy frissítse fizetési adatait. A fizetési adatok frissítése után a Mattermost megpróbálja rendezni a fennmaradó egyenleget." + "translation": "A Mattermost {{.Plan}} zavartalan elérésének biztosítása érdekében kérjük, vegye fel a kapcsolatot a pénzintézetével a probléma megoldása érdekében, vagy frissítse fizetési adatait. A fizetési adatok frissítése után a Mattermost megpróbálja rendezni a fennmaradó egyenleget." }, { "id": "api.templates.payment_failed.info2", @@ -9354,5 +9354,45 @@ { "id": "api.templates.delinquency_30.subject", "translation": "Lépjen most, hogy megtarthassa a Mattermost {{.Plan}} funkcióit" + }, + { + "id": "app.notify_admin.send_notification_post.app_error", + "translation": "Nem sikerült elküldeni az értesítési bejegyzést." + }, + { + "id": "app.notify_admin.save.app_error", + "translation": "Nem sikerült elmenteni az értesítési beállításokat." + }, + { + "id": "app.job.error", + "translation": "Hiba a művelet végrehajtása közben." + }, + { + "id": "app.cloud.get_current_plan_name.app_error", + "translation": "Nem sikerült lekérni a jelenlegi előfizetési csomag nevét" + }, + { + "id": "api.templates.delinquency_30.button", + "translation": "Fizetés frissítése" + }, + { + "id": "api.templates.delinquency_30.bullet.plugins", + "translation": "Aktív bővítmények és integrációk" + }, + { + "id": "api.cloud.delinquency_email.missing_email_to_trigger", + "translation": "Hiányzó kötelező mezők a késedelmes e-mail küldéséhez." + }, + { + "id": "app.file.cloud.get.app_error", + "translation": "Nem sikerült lekérni a fájlt, mivel az meghaladta a felhőcsomag határértékét." + }, + { + "id": "app.user.get_badge_count.app_error", + "translation": "Nem sikerült lekérni a felhasználó jelvény számlálóját." + }, + { + "id": "model.group.name.reserved_name.app_error", + "translation": "csoport név létezik mint lefoglalt név" } ] From 62626d31e328703156e1bed8746a5e3be6f24f33 Mon Sep 17 00:00:00 2001 From: Tilto_ Date: Wed, 19 Oct 2022 06:20:56 +0200 Subject: [PATCH 31/47] Translated using Weblate (Korean) Currently translated at 83.3% (1986 of 2384 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/ko/ --- i18n/ko.json | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/i18n/ko.json b/i18n/ko.json index 6efc6c5e61..79e6d396d5 100644 --- a/i18n/ko.json +++ b/i18n/ko.json @@ -619,7 +619,7 @@ }, { "id": "api.command_help.desc", - "translation": "Mattermost 도움말 페이지 열기" + "translation": "Mattermost 도움말 보기" }, { "id": "api.command_help.name", @@ -7934,5 +7934,41 @@ { "id": "api.cloud.teams_limit_reached.create", "translation": "팀 제한에 도달했기 때문에 팀을 만들 수 없습니다" + }, + { + "id": "api.command_marketplace.desc", + "translation": "마켓플레이스 열기" + }, + { + "id": "api.command_help.success", + "translation": "Mattermost는 도구와 팀을 넘나드는 안전한 소통, 협업, 작업 조율을 위한 오픈소스 플랫폼입니다.\nMattermost는 세가지 핵심 도구를 갖고 있습니다:\n\n**채널** - 1:1 및 그룹 채팅을 통해 팀과의 소통을 유지하세요.\n**[플레이북](/playbooks)** - 반복가능한 작업들을 구성하여 구체적이고 예측 가능한 결과를 만드세요.\n**[보드](/boards)** - 팀의 핵심 마일스톤에 도달하기 위해 칸반 보드에서 작업을 만들고 프로젝트를 관리하세요.\n\n[문서 및 가이드 살펴보기]({{.HelpLink}})" + }, + { + "id": "api.cloud.delinquency_email.missing_email_to_trigger", + "translation": "연체 이메일을 보내기 위한 필수 항목이 누락되었습니다." + }, + { + "id": "api.error_set_first_admin_complete_setup", + "translation": "스토어에서 첫 번째 관리자의 설정을 저장하는 동안 오류가 발생했습니다." + }, + { + "id": "api.error_get_first_admin_visit_marketplace_status", + "translation": "스토어에서 첫 번째 관리자의 마켓플레이스 방문 상태를 검색하는 동안 오류가 발생했습니다." + }, + { + "id": "api.error_get_first_admin_complete_setup", + "translation": "스토어에서 첫 번째 관리자 설정을 검색하는 동안 오류가 발생했습니다." + }, + { + "id": "api.command_remote.invite_summary", + "translation": "AES-256으로 암호화된 초대를 비밀번호와 함께 외부 Mattermost 시스템 관리자에게 보냅니다. 초대를 받기 위해 `{{.Command}}` 슬래시 명령어를 사용합니다.\n\n```\n{{.Invitation}}\n```\n\n**보안 연결이 다음을 통해 Mattermost에 액세스할 수 있는지 확인하십시오** {{.SiteURL}}" + }, + { + "id": "api.command_marketplace.unsupported.app_error", + "translation": "마켓플레이스 명령어는 현재 기기에서 지원하지 않습니다." + }, + { + "id": "api.command_marketplace.name", + "translation": "마켓플레이스" } ] From df62328291756d443be588d29b5d2691e809bedf Mon Sep 17 00:00:00 2001 From: master7 Date: Wed, 19 Oct 2022 06:20:57 +0200 Subject: [PATCH 32/47] Translated using Weblate (Polish) Currently translated at 100.0% (2386 of 2386 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/pl/ Translated using Weblate (Polish) Currently translated at 100.0% (2386 of 2386 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/pl/ Translated using Weblate (Polish) Currently translated at 100.0% (2385 of 2385 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/pl/ Translated using Weblate (Polish) Currently translated at 100.0% (2384 of 2384 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/pl/ --- i18n/pl.json | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/i18n/pl.json b/i18n/pl.json index de2e7d8a68..48fe1d9b82 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -4761,11 +4761,11 @@ }, { "id": "web.error.unsupported_browser.min_browser_version.edge", - "translation": "Wersja 44+" + "translation": "Wersja 95+" }, { "id": "web.error.unsupported_browser.min_browser_version.chrome", - "translation": "Wersja 100+" + "translation": "Wersja 106+" }, { "id": "web.error.unsupported_browser.learn_more", @@ -9542,5 +9542,29 @@ { "id": "app.job.error", "translation": "Błąd podczas wykonywania zadania." + }, + { + "id": "app.last_accessible_file.app_error", + "translation": "Błąd pobierania ostatniego dostępnego pliku" + }, + { + "id": "app.file.cloud.get.app_error", + "translation": "Nie można pobrać pliku, ponieważ przekroczony został limit planu chmury." + }, + { + "id": "model.group.name.reserved_name.app_error", + "translation": "nazwa grupy już istnieje jako nazwa zastrzeżona" + }, + { + "id": "app.plugin.product_mode.app_error", + "translation": "Wtyczka {{.Name}} nie może być włączona w trybie produktu." + }, + { + "id": "api.team.invite_guests_to_channels.license.error", + "translation": "Twoja licencja nie wspiera kont gości" + }, + { + "id": "api.team.invite_guests_to_channels.disabled.error", + "translation": "Konta gości są wyłączone" } ] From 62152212ccf4bc013b1c3954ab84449f8248743d Mon Sep 17 00:00:00 2001 From: Dmitriy Danilov Date: Wed, 19 Oct 2022 06:20:57 +0200 Subject: [PATCH 33/47] Translated using Weblate (Ukrainian) Currently translated at 70.7% (1687 of 2385 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/uk/ --- i18n/uk.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/i18n/uk.json b/i18n/uk.json index 21f427b84f..4632894522 100644 --- a/i18n/uk.json +++ b/i18n/uk.json @@ -6858,5 +6858,17 @@ { "id": "api.admin.add_certificate.parseform.app_error", "translation": "Помилка синтаксісу багато-форматного запиту" + }, + { + "id": "api.channel.patch_channel_moderations.cache_invalidation.error", + "translation": "Помилка валідації кешу" + }, + { + "id": "api.back_to_app", + "translation": "Повернутися до {{.SiteName}}" + }, + { + "id": "Channels", + "translation": "Канали" } ] From 4c1bcc9e3b9dde8ef94bf5d481098c0659a131d6 Mon Sep 17 00:00:00 2001 From: Matthew Williams Date: Wed, 19 Oct 2022 06:20:58 +0200 Subject: [PATCH 34/47] Translated using Weblate (English (Australia)) Currently translated at 100.0% (2385 of 2385 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/en_AU/ --- i18n/en_AU.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/i18n/en_AU.json b/i18n/en_AU.json index e1dc7cb1c0..026157936b 100644 --- a/i18n/en_AU.json +++ b/i18n/en_AU.json @@ -9537,5 +9537,21 @@ { "id": "app.cloud.get_current_plan_name.app_error", "translation": "Unable to get current plan name" + }, + { + "id": "model.group.name.reserved_name.app_error", + "translation": "group name already exists as a reserved name" + }, + { + "id": "app.last_accessible_file.app_error", + "translation": "Error fetching last accessible file" + }, + { + "id": "app.job.error", + "translation": "Error during job execution." + }, + { + "id": "app.file.cloud.get.app_error", + "translation": "Unable to retrieve file: cloud storage limit exceeded." } ] From b0c39341d6d62ab4ceb563c982caa4e620135b75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20V=C3=ADtor=20Cruz=20de=20Souza?= Date: Wed, 19 Oct 2022 06:20:58 +0200 Subject: [PATCH 35/47] Translated using Weblate (Portuguese (Brazil)) Currently translated at 90.6% (2161 of 2385 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/pt_BR/ Translated using Weblate (Portuguese (Brazil)) Currently translated at 90.5% (2160 of 2385 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/pt_BR/ --- i18n/pt-BR.json | 104 ++++++++++++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 48 deletions(-) diff --git a/i18n/pt-BR.json b/i18n/pt-BR.json index 94b78b3e58..1d35e397f0 100644 --- a/i18n/pt-BR.json +++ b/i18n/pt-BR.json @@ -621,7 +621,7 @@ }, { "id": "api.command_help.desc", - "translation": "Abrir a página de ajuda do Mattermost" + "translation": "Exibir descrição de ajuda do Mattermost" }, { "id": "api.command_help.name", @@ -1173,7 +1173,7 @@ }, { "id": "api.marshal_error", - "translation": "marshal error" + "translation": "Falha para marshal." }, { "id": "api.oauth.allow_oauth.redirect_callback.app_error", @@ -1789,7 +1789,7 @@ }, { "id": "api.templates.email_footer", - "translation": "Para alterar suas preferencias de notificação, faça login no site da sua equipe e vá para Configurações de Conta > Notificações." + "translation": "Para alterar suas preferencias de notificação, faça login no site da sua equipe e vá para Configurações > Notificações." }, { "id": "api.templates.email_info1", @@ -1813,7 +1813,7 @@ }, { "id": "api.templates.invite_body.button", - "translation": "Entrar na Equipe" + "translation": "Entrar agora" }, { "id": "api.templates.invite_body.title", @@ -1857,7 +1857,7 @@ }, { "id": "api.templates.post_body.button", - "translation": "Ir Para a Publicação" + "translation": "Responder no Mattermost" }, { "id": "api.templates.reset_body.button", @@ -1865,7 +1865,7 @@ }, { "id": "api.templates.reset_body.title", - "translation": "Você solicitou uma redefinição de senha" + "translation": "Redefina sua senha" }, { "id": "api.templates.reset_subject", @@ -4333,11 +4333,11 @@ }, { "id": "model.post.is_valid.file_ids.app_error", - "translation": "Ids do arquivo inválido. Observe que os uploads estão limitados a cinco arquivos no máximo. Por favor, use publicações adicionais para mais arquivos." + "translation": "Ids do arquivo inválido. Observe que os uploads estão limitados a 10 arquivos no máximo. Por favor, use publicações adicionais para mais arquivos." }, { "id": "model.post.is_valid.filenames.app_error", - "translation": "Nomes de arquivo inválidos" + "translation": "Nomes de arquivo inválidos." }, { "id": "model.post.is_valid.hashtags.app_error", @@ -4573,7 +4573,7 @@ }, { "id": "oauth.gitlab.tos.error", - "translation": "Os Termos de Serviço do GitLab foram atualizados. Por favor vá até gitlab.com para aceitar os termos e depois tente se logar novamente no Mattermost." + "translation": "Os Termos de Serviço do GitLab foram atualizados. Por favor vá até {{.URL}} para aceitar os termos e depois tente se logar novamente no Mattermost." }, { "id": "plugin.api.update_user_status.bad_status", @@ -4745,19 +4745,19 @@ }, { "id": "web.error.unsupported_browser.min_os_version.windows", - "translation": "Windows 7+" + "translation": "Windows 8.1+" }, { "id": "web.error.unsupported_browser.min_os_version.mac", - "translation": "macOS 10.9+" + "translation": "macOS 10.14+" }, { "id": "web.error.unsupported_browser.min_browser_version.safari", - "translation": "Versão 12+" + "translation": "Versão 14.1+" }, { "id": "web.error.unsupported_browser.min_browser_version.firefox", - "translation": "Versão 60+" + "translation": "Versão 91+" }, { "id": "web.error.unsupported_browser.min_browser_version.edge", @@ -4765,7 +4765,7 @@ }, { "id": "web.error.unsupported_browser.min_browser_version.chrome", - "translation": "Versão 61+" + "translation": "Version 100+" }, { "id": "web.error.unsupported_browser.learn_more", @@ -5945,7 +5945,7 @@ }, { "id": "api.push_notifications.session.expired", - "translation": "Sessão expirada: faça login para continuar recebendo notificações. As sessões para {{.siteName}} estão configuradas pelo Administrador do Sistema para expirar a cada {{.daysCount}} dia(s)." + "translation": "Sessão expirada: faça login para continuar recebendo notificações. As sessões para {{.siteName}} estão configuradas pelo Administrador do Sistema para expirar a cada {{.hoursCount}} hour(s)." }, { "id": "api.post.error_get_post_id.pending", @@ -6189,7 +6189,7 @@ }, { "id": "app.preference.permanent_delete_by_user.app_error", - "translation": "Encontramos um erro enquanto excluía as preferências." + "translation": "Encontramos um erro enquanto excluiamos as preferências." }, { "id": "app.preference.get_category.app_error", @@ -6897,7 +6897,7 @@ }, { "id": "model.upload_session.is_valid.user_id.app_error", - "translation": "Valor inválido para user_id." + "translation": "Valor inválido para UserId" }, { "id": "model.upload_session.is_valid.type.app_error", @@ -6905,15 +6905,15 @@ }, { "id": "model.upload_session.is_valid.path.app_error", - "translation": "Valor inválido para path." + "translation": "Valor inválido para path" }, { "id": "model.upload_session.is_valid.id.app_error", - "translation": "Valor inválido para id." + "translation": "Valor inválido para id" }, { "id": "model.upload_session.is_valid.create_at.app_error", - "translation": "Valor inválido para create_at." + "translation": "Valor inválido para Create_At" }, { "id": "app.upload.upload_data.large_image.app_error", @@ -7601,7 +7601,7 @@ }, { "id": "api.templates.copyright", - "translation": "© 2020 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301" + "translation": "© 2021 Mattermost, Inc. 530 Lytton Avenue, Second floor, Palo Alto, CA, 94301" }, { "id": "api.roles.patch_roles.not_allowed_permission.error", @@ -7645,15 +7645,15 @@ }, { "id": "api.templates.payment_failed.title", - "translation": "Pagamento Falhou" + "translation": "O Pagamento não foi bem sucedido" }, { "id": "api.templates.payment_failed.subject", - "translation": "Ação necessária: Falha no pagamento para Mattermost Cloud" + "translation": "Ação necessária: Falha no pagamento para Mattermost {{.Plan}}" }, { "id": "api.templates.payment_failed.info3", - "translation": "Para garantir a assinatura ininterrupta do Mattermost Cloud, entre em contato com sua instituição financeira para corrigir o problema subjacente ou atualize suas informações de pagamento. Assim que as informações de pagamento forem atualizadas, a Mattermost tentará liquidar o saldo pendente." + "translation": "Para garantir a acesso ininterrupto do Mattermost {{.Plan}}, entre em contato com sua instituição financeira para corrigir o problema subjacente ou atualize suas informações de pagamento. Assim que as informações de pagamento forem atualizadas, a Mattermost tentará liquidar o saldo pendente." }, { "id": "api.templates.payment_failed.info2", @@ -8069,7 +8069,7 @@ }, { "id": "api.user.get_authorization_code.endpoint.app_error", - "translation": "Erro ao recuperar o endpoint da Descoberta de Documento." + "translation": "Erro ao recuperar o endpoint do Documento de Descoberta." }, { "id": "api.templates.welcome_body.subTitle2", @@ -8117,11 +8117,11 @@ }, { "id": "api.templates.questions_footer.info", - "translation": "Envie-nos um email a qualquer momento em " + "translation": "Precisa de ajuda ou tem dúvidas? Envie-nos um e-mail em " }, { "id": "api.templates.email_footer_v2", - "translation": "© 2020 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301" + "translation": "© 2022 Mattermost, Inc. 530 Lytton Avenue, Second floor, Palo Alto, CA, 94301" }, { "id": "api.post.search_files.invalid_body.app_error", @@ -8333,7 +8333,7 @@ }, { "id": "api.context.remote_id_invalid.app_error", - "translation": "Não foi possível encontrar o id do cluster remoto {{.RemoteId}}." + "translation": "Não foi possível encontrar o ID de uma conexão segura {{.RemoteId}}." }, { "id": "api.context.json_encoding.app_error", @@ -8357,15 +8357,15 @@ }, { "id": "api.command_share.remote_uninvited", - "translation": "Remoto `{{.RemoteId}}` sem convite." + "translation": "Conexão segura `{{.RemoteId}}` sem convite." }, { "id": "api.command_share.remote_id_invalid.error", - "translation": "O id do cluster remoto é inválido: {{.Error}}" + "translation": "O ID de conexão segura é inválido: {{.Error}}" }, { "id": "api.command_share.remote_id.help", - "translation": "Id de uma instância remota existente. Veja o comando `remote` para adicionar uma instância remota." + "translation": "ID de uma conexão segura existente. Veja o comando `remote` para adicionar uma conexão segura." }, { "id": "api.command_share.permission_required", @@ -8437,7 +8437,7 @@ }, { "id": "api.templates.cloud_welcome_email.signin_sub_info2", - "translation": "para a melhor experiência em PC, Mac, iOS e Android." + "translation": "para a melhor experiência em Windows, Linux, Mac, iOS e Android." }, { "id": "api.templates.cloud_welcome_email.signin_sub_info", @@ -8469,7 +8469,7 @@ }, { "id": "api.templates.cloud_welcome_email.add_apps_sub_info", - "translation": "Otimize seu trabalho com ferramentas como Github, Google Agenda e Chrome. Explore todas as integrações que temos" + "translation": "Otimize seu trabalho com ferramentas como GitHub, Jira e Zoom. Explore todas as integrações que temos em nosso" }, { "id": "api.templates.cloud_welcome_email.add_apps_info", @@ -8481,11 +8481,11 @@ }, { "id": "api.remote_cluster.update_not_unique.app_error", - "translation": "Já existe um cluster remoto com a mesmo url." + "translation": "Já existe uma conexão segura com a mesmo url." }, { "id": "api.remote_cluster.update.app_error", - "translation": "Encontramos um erro ao atualizar o cluster remoto." + "translation": "Encontramos um erro ao atualizar a conexão segura." }, { "id": "api.remote_cluster.service_not_enabled.app_error", @@ -8493,11 +8493,11 @@ }, { "id": "api.remote_cluster.save_not_unique.app_error", - "translation": "O cluster remoto já foi adicionado." + "translation": "A conexão segura já foi adicionada." }, { "id": "api.remote_cluster.save.app_error", - "translation": "Encontramos um erro ao salvar o cluster remoto." + "translation": "Encontramos um erro ao salvar a conexão segura." }, { "id": "api.remote_cluster.invalid_topic.app_error", @@ -8509,11 +8509,11 @@ }, { "id": "api.remote_cluster.get.app_error", - "translation": "Encontramos um erro ao recuperar um cluster remoto." + "translation": "Encontramos um erro ao recuperar uma conexão segura." }, { "id": "api.remote_cluster.delete.app_error", - "translation": "Encontramos um erro ao excluir o cluster remoto." + "translation": "Encontramos um erro ao excluir uma conexão segura." }, { "id": "api.job.unable_to_create_job.incorrect_job_type", @@ -8533,11 +8533,11 @@ }, { "id": "api.context.remote_id_missing.app_error", - "translation": "Falta a id do cluster remoto." + "translation": "Falta a ID de conexão sergura." }, { "id": "api.context.remote_id_mismatch.app_error", - "translation": "Incompatibilidade de id do cluster remoto." + "translation": "Incompatibilidade de ID da conexão segura." }, { "id": "api.context.invitation_expired.error", @@ -8549,11 +8549,11 @@ }, { "id": "api.command_share.uninvite_remote_id.help", - "translation": "Id da instância remota para desconvidar." + "translation": "ID de uma conexão segura para desconvidar." }, { "id": "api.command_share.uninvite_remote.help", - "translation": "Cancela o convite de uma instância remota deste canal compartilhado" + "translation": "Cancela o convite de uma conexão segura deste canal compartilhado" }, { "id": "api.command_share.shared_channel_unavailable", @@ -8569,11 +8569,11 @@ }, { "id": "api.command_share.remote_not_valid", - "translation": "Deve especificar um cluster remoto válido para cancelar o convite" + "translation": "Deve especificar uma ID de conexão segura válida para cancelar o convite" }, { "id": "api.command_share.remote_already_invited", - "translation": "O cluster remoto já foi convidado." + "translation": "A conexão segura já foi convidada." }, { "id": "api.command_share.not_shared_channel_unshare", @@ -8581,15 +8581,15 @@ }, { "id": "api.command_share.no_remote_invited", - "translation": "Nenhum remoto foi convidado para este canal compartilhado." + "translation": "Nenhuma conexão remota foi convidada a este canal." }, { "id": "api.command_share.name", - "translation": "compartilhar" + "translation": "compartilhar-canal" }, { "id": "api.command_share.must_specify_valid_remote", - "translation": "Deve especificar um ID de conjunto remoto válido para convidar." + "translation": "Deve especificar um ID de conjunto válido seguro para convidar." }, { "id": "api.command_share.missing_action", @@ -8650,5 +8650,13 @@ { "id": "Boards", "translation": "Quadros" + }, + { + "id": "Playbooks", + "translation": "Playbooks" + }, + { + "id": "api.cloud.delinquency_email.missing_email_to_trigger", + "translation": "Campos faltando para envio de email." } ] From 32a941b79dc5d1df2b402e92a668e0cf3bd5997b Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 19 Oct 2022 06:20:59 +0200 Subject: [PATCH 36/47] Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/ Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/ --- i18n/bg.json | 8 -------- i18n/de.json | 8 -------- i18n/en_AU.json | 8 -------- i18n/es.json | 8 -------- i18n/fa.json | 8 -------- i18n/fr.json | 8 -------- i18n/hu.json | 8 -------- i18n/it.json | 8 -------- i18n/ja.json | 8 -------- i18n/ko.json | 8 -------- i18n/nl.json | 8 -------- i18n/pl.json | 8 -------- i18n/pt-BR.json | 8 -------- i18n/ro.json | 8 -------- i18n/ru.json | 8 -------- i18n/sv.json | 4 ---- i18n/tr.json | 8 -------- i18n/uk.json | 8 -------- i18n/zh-CN.json | 8 -------- i18n/zh-TW.json | 8 -------- 20 files changed, 156 deletions(-) diff --git a/i18n/bg.json b/i18n/bg.json index 7913a2775f..4031bf4bf6 100644 --- a/i18n/bg.json +++ b/i18n/bg.json @@ -4267,14 +4267,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "Каналите в поканата трябва да са част от канещият екип." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "Вашият лиценз не поддържа профили за гости" - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "Профилите за гости са забранени" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "Грешка при анулиране на поканите по ел.поща." diff --git a/i18n/de.json b/i18n/de.json index 923ae5eedd..01c6dbcf7f 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -5623,14 +5623,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "Die Kanäle der Einladung müssen Teil des Teams der Einladung sein." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "Deine Lizenz unterstützt Gastkonten nicht" - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "Gastkonten sind deaktiviert" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "Fehler beim Annulieren von E-Mail-Einladungen." diff --git a/i18n/en_AU.json b/i18n/en_AU.json index 026157936b..6f6f177f60 100644 --- a/i18n/en_AU.json +++ b/i18n/en_AU.json @@ -5459,14 +5459,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "The channels of the invite must be part of the team of the invite." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "Your licence does not support guest accounts" - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "Guest accounts are disabled" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "Error invalidating email invites." diff --git a/i18n/es.json b/i18n/es.json index 97cec02eba..5a29542a5c 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -5543,14 +5543,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "Los canales de la invitación deben ser parte del equipo al cual fue invitado." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "La licencia actual no es compatible con cuentas de huéspedes" - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "Las cuentas de huéspedes están deshabilitadas" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "Error al invalidar las invitaciones por correo electrónico." diff --git a/i18n/fa.json b/i18n/fa.json index b71235d64b..88186a1d34 100644 --- a/i18n/fa.json +++ b/i18n/fa.json @@ -5159,14 +5159,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "کانال های دعوت باید بخشی از تیم دعوت باشند." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "مجوز شما از حساب های مهمان پشتیبانی نمی کند" - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "حساب های مهمان غیرفعال شده است" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "خطا در عدم اعتبار دعوت نامه های ایمیل." diff --git a/i18n/fr.json b/i18n/fr.json index 043cda6743..c94978c9da 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -5583,14 +5583,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "Les canaux présents dans l'invitation doivent faire partie de la même équipe pour laquelle vous êtes invité." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "Votre licence ne prend pas en charge les comptes invités." - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "Les comptes invités sont désactivés" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "Une erreur s'est produite lors de l'invalidation des e-mails d'invitation." diff --git a/i18n/hu.json b/i18n/hu.json index 2f7e270a62..930d7fd5d3 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -5795,14 +5795,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "A meghívás csatornáinak a meghívó csapat részének kell lenniük." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "Az Ön licence nem támogatja a vendég fiókokat" - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "A vendég fiókok le vannak tiltva" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "Hiba történt az e-mail meghívók érvénytelenítésekor." diff --git a/i18n/it.json b/i18n/it.json index 2b4114bad9..f9c6ae0911 100644 --- a/i18n/it.json +++ b/i18n/it.json @@ -5211,10 +5211,6 @@ "id": "api.team.add_members.user_denied", "translation": "Questa squadra è gestita dai gruppi. Questo utente non appartiene a un gruppo sincronizzato con questa squadra." }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "Gli account ospite sono disattivati" - }, { "id": "api.team.remove_member.group_constrained.app_error", "translation": "Impossibile rimuovere un utente da una squadra con vincoli di gruppo." @@ -5643,10 +5639,6 @@ "id": "api.team.invalidate_all_email_invites.app_error", "translation": "Errore invalidando gli inviti email." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "La tua licenza non supporta gli account ospite" - }, { "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "I canali dell'invito devono far parte della squadra dell'invito." diff --git a/i18n/ja.json b/i18n/ja.json index 5918ca411b..2b9e6beead 100644 --- a/i18n/ja.json +++ b/i18n/ja.json @@ -5579,14 +5579,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "招待チャンネルは招待チーム内のチャンネルでなくてはなりません。" }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "現在のライセンスではゲストアカウントはサポートされていません" - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "ゲストアカウントは無効化されています" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "電子メール招待の無効化でエラーが発生しました。" diff --git a/i18n/ko.json b/i18n/ko.json index 79e6d396d5..7d4b36ccb3 100644 --- a/i18n/ko.json +++ b/i18n/ko.json @@ -5575,14 +5575,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "초대 채널은 초대한 팀의 채널이어야 합니다." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "게스트 계정을 지원하지 않는 라이선스입니다" - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "게스트 계정이 비활성화되어 있습니다" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "전자우편 초대에서 유효하지 않는 오류가 발생했습니다." diff --git a/i18n/nl.json b/i18n/nl.json index 2695bddadb..c23bb81c98 100644 --- a/i18n/nl.json +++ b/i18n/nl.json @@ -5607,14 +5607,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "De kanalen van de uitgenodigde moet een kanaal dat deel uitmaakt van het team." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "Je licentie ondersteunt geen gastgebruikers" - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "Gast accounts zijn uitgeschakeld" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "Fout bij afkeuren e-mailuitnodigingen." diff --git a/i18n/pl.json b/i18n/pl.json index 48fe1d9b82..f62b244a3d 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -5583,14 +5583,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "Kanały do których chcesz zaprosić, muszą być częścią zespołu do którego chcesz zaprosić." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "Twoja licencja nie obsługuje kont gości" - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "Konta gości są wyłączone" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "Błąd unieważniania zaproszenia E-Mail." diff --git a/i18n/pt-BR.json b/i18n/pt-BR.json index 1d35e397f0..2873fe7a1d 100644 --- a/i18n/pt-BR.json +++ b/i18n/pt-BR.json @@ -5587,14 +5587,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "Os canais do convite devem ser parte da equipe do convite." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "Sua licença não suporta contas convidado" - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "Contas convidado estão desativadas" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "Erro ao invalidar convites por email." diff --git a/i18n/ro.json b/i18n/ro.json index c51b4666e0..6c9fe29085 100644 --- a/i18n/ro.json +++ b/i18n/ro.json @@ -5587,14 +5587,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "Canalele invitației trebuie să facă parte din echipa invitației." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "Licența dvs. nu acceptă conturile de invitați" - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "Conturile oaspeților sunt dezactivate" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "Eroare la invalidarea invitațiilor de e-mail." diff --git a/i18n/ru.json b/i18n/ru.json index 3d371f6a36..0b4fd7d438 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -5235,10 +5235,6 @@ "id": "api.team.demote_user_to_guest.license.error", "translation": "Ваша лицензия не поддерживает гостевые учётные записи" }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "Ваша лицензия не поддерживает гостевые учётные записи" - }, { "id": "api.team.remove_member.group_constrained.app_error", "translation": "Невозможно удалить пользователя из управляемой группами команды." @@ -5675,10 +5671,6 @@ "id": "api.team.invalidate_all_email_invites.app_error", "translation": "Ошибка аннулирования e-mail приглашений." }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "Гостевые учётные записи отключены" - }, { "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "Каналы в приглашении должны быть частью команды в приглашении." diff --git a/i18n/sv.json b/i18n/sv.json index 2374ce62ad..82bd8030ec 100644 --- a/i18n/sv.json +++ b/i18n/sv.json @@ -5275,10 +5275,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "De kanaler som ingår i inbjudan måste tillhöra de team som inbjudan gäller för." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "Din licens tillåter inte gäståtkomst" - }, { "id": "api.team.invate_guests_to_channels.disabled.error", "translation": "Gäståtkomst har inaktiverats" diff --git a/i18n/tr.json b/i18n/tr.json index bdb2ca0902..cbe04ebdb2 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -5655,14 +5655,6 @@ "id": "api.team.invalidate_all_email_invites.app_error", "translation": "E-posta çağrıları geçersiz kılınırken sorun çıktı." }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "Konuk hesapları kullanılmıyor" - }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "Lisansınızda konuk hesaplarını kullanma özelliği yok" - }, { "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "Çağrılan kanallar çağrı yapılan takımın bir parçası olmalıdır." diff --git a/i18n/uk.json b/i18n/uk.json index 4632894522..c05c7895b3 100644 --- a/i18n/uk.json +++ b/i18n/uk.json @@ -5583,14 +5583,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "The channels of the invite must be part of the team of the invite." }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "Редакція не підтримує Elasticsearch. " - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "Guest accounts are disabled" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "Error invalidating email invites." diff --git a/i18n/zh-CN.json b/i18n/zh-CN.json index 4dba1375ff..cecf6c9252 100644 --- a/i18n/zh-CN.json +++ b/i18n/zh-CN.json @@ -5611,10 +5611,6 @@ "id": "api.team.invalidate_all_email_invites.app_error", "translation": "作废邮件邀请错误。" }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "您的许可证不支持访客帐号" - }, { "id": "api.team.remove_member.group_constrained.app_error", "translation": "无法从组受限团队移除用户。" @@ -5627,10 +5623,6 @@ "id": "api.team.search_teams.pagination_not_implemented.public_team_search", "translation": "仅搜索公共团队不支持分页。" }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "访客帐号已停用" - }, { "id": "api.templates.remove_expired_license.subject", "translation": "Mattermost 企业许可证已停用。" diff --git a/i18n/zh-TW.json b/i18n/zh-TW.json index 0f3fda947e..11b8fcb9ef 100644 --- a/i18n/zh-TW.json +++ b/i18n/zh-TW.json @@ -5579,14 +5579,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "邀請的頻道必須屬於邀請的團隊" }, - { - "id": "api.team.invate_guests_to_channels.license.error", - "translation": "授權不支援訪客帳號" - }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "訪客帳號已停用" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "使電子郵件邀請無效時失敗。" From b9bbe5c447613944e7d1b307ad77da8037f00255 Mon Sep 17 00:00:00 2001 From: MArtin Johnson Date: Wed, 19 Oct 2022 06:20:59 +0200 Subject: [PATCH 37/47] Translated using Weblate (Swedish) Currently translated at 100.0% (2385 of 2385 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/sv/ --- i18n/sv.json | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/i18n/sv.json b/i18n/sv.json index 82bd8030ec..290ff4f7e5 100644 --- a/i18n/sv.json +++ b/i18n/sv.json @@ -5275,10 +5275,6 @@ "id": "api.team.invite_guests.channel_in_invalid_team.app_error", "translation": "De kanaler som ingår i inbjudan måste tillhöra de team som inbjudan gäller för." }, - { - "id": "api.team.invate_guests_to_channels.disabled.error", - "translation": "Gäståtkomst har inaktiverats" - }, { "id": "api.team.invalidate_all_email_invites.app_error", "translation": "Fel vid ogiltiggörande av e-postinbjudan." @@ -9537,5 +9533,25 @@ { "id": "app.job.error", "translation": "Fel under utförandet av jobbet." + }, + { + "id": "model.group.name.reserved_name.app_error", + "translation": "gruppnamnet finns redan som ett reserverat namn" + }, + { + "id": "app.last_accessible_file.app_error", + "translation": "Fel vid hämtning av den senast tillgängliga filen" + }, + { + "id": "app.file.cloud.get.app_error", + "translation": "Ditt abonnemang begränsar möjligheten att hämta filen." + }, + { + "id": "api.team.invite_guests_to_channels.license.error", + "translation": "Din licens tillåter inte gäståtkomst" + }, + { + "id": "api.team.invite_guests_to_channels.disabled.error", + "translation": "Gäståtkomst har inaktiverats" } ] From ee718b04a7317352712231dd03117d1f811046cd Mon Sep 17 00:00:00 2001 From: JtheBAB Date: Wed, 19 Oct 2022 06:21:00 +0200 Subject: [PATCH 38/47] Translated using Weblate (German) Currently translated at 100.0% (2386 of 2386 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/de/ Translated using Weblate (German) Currently translated at 100.0% (2386 of 2386 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/de/ --- i18n/de.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/i18n/de.json b/i18n/de.json index 01c6dbcf7f..f39f0d304e 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -4805,7 +4805,7 @@ }, { "id": "web.error.unsupported_browser.min_browser_version.chrome", - "translation": "Version 102+" + "translation": "Version 106+" }, { "id": "web.error.unsupported_browser.learn_more", @@ -9545,5 +9545,17 @@ { "id": "model.group.name.reserved_name.app_error", "translation": "Gruppenname existiert bereits als reservierter Name" + }, + { + "id": "app.plugin.product_mode.app_error", + "translation": "Plugin {{.Name}} kann im Produktmodus nicht aktiviert werden." + }, + { + "id": "api.team.invite_guests_to_channels.license.error", + "translation": "Deine Lizenz unterstützt Gastkonten nicht" + }, + { + "id": "api.team.invite_guests_to_channels.disabled.error", + "translation": "Gastkonten sind deaktiviert" } ] From 78ad750665371231d9e6d1a869fe713d4361290b Mon Sep 17 00:00:00 2001 From: Angel Mendez Cano Date: Wed, 19 Oct 2022 06:21:00 +0200 Subject: [PATCH 39/47] Translated using Weblate (Spanish) Currently translated at 99.7% (2381 of 2386 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/es/ Translated using Weblate (Spanish) Currently translated at 98.2% (2344 of 2386 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/es/ --- i18n/es.json | 338 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 317 insertions(+), 21 deletions(-) diff --git a/i18n/es.json b/i18n/es.json index 5a29542a5c..91e6c8d027 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -621,7 +621,7 @@ }, { "id": "api.command_help.desc", - "translation": "Abrir la página de ayuda de Mattermost" + "translation": "Muestra el mensaje de ayuda de Mattermost" }, { "id": "api.command_help.name", @@ -1781,7 +1781,7 @@ }, { "id": "api.templates.email_change_verify_body.title", - "translation": "Haz actualizado tu correo electrónico" + "translation": "Haz actualizado satisfactoriamente tu correo electrónico" }, { "id": "api.templates.email_change_verify_subject", @@ -1857,7 +1857,7 @@ }, { "id": "api.templates.post_body.button", - "translation": "Ver mensaje" + "translation": "Responder en Mattermost" }, { "id": "api.templates.reset_body.button", @@ -3305,7 +3305,7 @@ }, { "id": "ent.migration.migratetoldap.duplicate_field", - "translation": "No se pueden migrar los usuarios de AD/LDAP con el campo especificado. Se ha detectado un entrada. Por favor, retire todos los duplicados e inténtalo de nuevo." + "translation": "No se pueden migrar los usuarios de AD/LDAP con el campo especificado. Se ha detectado un entrada duplicada. Por favor, elimina todos los duplicados e inténtalo de nuevo." }, { "id": "ent.migration.migratetoldap.user_not_found", @@ -4573,7 +4573,7 @@ }, { "id": "oauth.gitlab.tos.error", - "translation": "Los Términos de Servicio de GitLab se han actualizado. Por favor, vaya a gitlab.com a aceptar y, a continuación, intente iniciar sesión en Mattermost de nuevo." + "translation": "Los Términos de Servicio de GitLab se han actualizado. Por favor, visita {{.URL}} para aceptarlos y, a continuación, intenta iniciar sesión en Mattermost nuevamente." }, { "id": "plugin.api.update_user_status.bad_status", @@ -4753,7 +4753,7 @@ }, { "id": "web.error.unsupported_browser.min_os_version.windows", - "translation": "Windows 7+" + "translation": "Windows 8.1+" }, { "id": "web.error.unsupported_browser.min_os_version.mac", @@ -4761,19 +4761,19 @@ }, { "id": "web.error.unsupported_browser.min_browser_version.safari", - "translation": "Versión 12+" + "translation": "Versión 14.1+" }, { "id": "web.error.unsupported_browser.min_browser_version.firefox", - "translation": "Versión 78+" + "translation": "Versión 91+" }, { "id": "web.error.unsupported_browser.min_browser_version.edge", - "translation": "Versión 44+" + "translation": "Versión 95+" }, { "id": "web.error.unsupported_browser.min_browser_version.chrome", - "translation": "Versión 89+" + "translation": "Versión 106+" }, { "id": "web.error.unsupported_browser.learn_more", @@ -5937,7 +5937,7 @@ }, { "id": "api.push_notifications.session.expired", - "translation": "Sesión caducada: Inicie sesión para continuar recibiendo notificaciones. El administrador del sistema configura las sesiones para {{.siteName}} para que caduquen cada {{.daysCount}} día (s)." + "translation": "Sesión vencida: Inicie sesión para continuar recibiendo notificaciones. El administrador del sistema configura las sesiones para {{.siteName}} para que caduquen cada {{.hoursCount}} hora(s)." }, { "id": "api.post.error_get_post_id.pending", @@ -7613,7 +7613,7 @@ }, { "id": "api.user.get_authorization_code.endpoint.app_error", - "translation": "Error all obtener el endpoint para Documento de Descubrimiento." + "translation": "Error al obtener el endpoint desde Documento de Descubrimiento." }, { "id": "api.templates.payment_failed_no_card.title", @@ -7637,15 +7637,15 @@ }, { "id": "api.templates.payment_failed.title", - "translation": "Pago fallido" + "translation": "El pago ha fallado" }, { "id": "api.templates.payment_failed.subject", - "translation": "Se requiere una acción: El pago de Mattermost Cloud no se ha realizado" + "translation": "Se requiere una acción: El pago de {{.Plan}} Mattermost Cloud no se ha realizado" }, { "id": "api.templates.payment_failed.info3", - "translation": "Para asegurar una suscripción ininterrumpida a Mattermost Cloud, por favor, póngase en contacto con su institución financiera para arreglar el problema subyacente o actualizar su información de pago. Una vez actualizada la información de pago, Mattermost intentará liquidar cualquier saldo pendiente." + "translation": "Para asegurar un acceso ininterrumpido a {{.Plan}} Mattermost Cloud, por favor, póngase en contacto con su institución financiera para resolver el problema subyacente o actualizar su información de pago. Una vez actualizada la información de pago, Mattermost intentará liquidar cualquier saldo pendiente." }, { "id": "api.templates.payment_failed.info2", @@ -7973,7 +7973,7 @@ }, { "id": "api.templates.questions_footer.info", - "translation": "Envíenos un correo electrónico en cualquier momento a " + "translation": "¿Necesitas ayuda o tienes alguna pregunta? Envíanos un correo electrónico a " }, { "id": "api.templates.license_up_for_renewal_title", @@ -8017,11 +8017,11 @@ }, { "id": "api.templates.email_footer_v2", - "translation": "© 2021 Mattermost, Inc. 530 Lytton Avenue, Second floor, Palo Alto, CA, 94301" + "translation": "© 2022 Mattermost, Inc. 530 Lytton Avenue, Second floor, Palo Alto, CA, 94301" }, { "id": "api.templates.cloud_welcome_email.title", - "translation": "¡El período de prueba de 14 días de tu espacio de trabajo {{.WorkSpace}} está listo!" + "translation": "¡Tu espacio de trabajo ya está listo para usar!" }, { "id": "api.templates.cloud_welcome_email.subtitle_info", @@ -8081,7 +8081,7 @@ }, { "id": "api.templates.cloud_welcome_email.add_apps_sub_info", - "translation": "Agiliza tu trabajo con herramientas como Github, Jira y Zoom. Explora todas las integraciones que tenemos en nuestro" + "translation": "Agiliza tu trabajo con herramientas como GitHub, Jira y Zoom. Explora todas las integraciones que tenemos en nuestro" }, { "id": "api.command_share.unknown_action", @@ -9145,7 +9145,7 @@ }, { "id": "api.templates.invite_team_and_channel_subject", - "translation": " " + "translation": "[{{ .SiteName }}] {{ .SenderName }} te invitó a unirte al Canal {{ .ChannelName }} en el Equipo {{ .TeamDisplayName }}" }, { "id": "api.templates.invite_team_and_channels_body.title", @@ -9229,7 +9229,7 @@ }, { "id": "api.templates.invite_team_and_channel_body.title", - "translation": " " + "translation": "{{ .SenderName }} te invitó a unirte al Canal {{ .ChannelName }} en el Equipo {{ .TeamDisplayName}}" }, { "id": "api.templates.server_inactivity_info_bullet", @@ -9262,5 +9262,301 @@ { "id": "api.user.authorize_oauth_user.saml_response_too_long.app_error", "translation": " " + }, + { + "id": "app.cloud.get_subscription_delinquency_date.app_error", + "translation": "La suscripción no es morosa" + }, + { + "id": "app.cloud.get_subscription.app_error", + "translation": "No se pudo recuperar la suscripción de la nube" + }, + { + "id": "app.cloud.get_current_plan_name.app_error", + "translation": "No se puede obtener el nombre del plan actual" + }, + { + "id": "app.cloud.get_cloud_products.app_error", + "translation": "No se pudieron obtener los productos de la nube" + }, + { + "id": "api.templates.delinquency_90.title", + "translation": "Tu workspace Mattermost ha sido degradado" + }, + { + "id": "api.templates.delinquency_90.subtitle3", + "translation": "Para desarchivar tus datos y mantener las características de pago, actualiza tu información de pago." + }, + { + "id": "api.templates.delinquency_90.subtitle2", + "translation": "Además, tus datos pueden haber sido archivados debido a las limitaciones de Cloud Starter." + }, + { + "id": "api.templates.delinquency_90.subtitle1", + "translation": "Si usas características Cloud Professional o Enterprise para operaciones importantes de negocio, ya no estarán activas y experimentarás un rendimiento degradado." + }, + { + "id": "api.templates.delinquency_90.subject", + "translation": "Tu workspace Mattermost Cloud ha sido degradado" + }, + { + "id": "api.templates.delinquency_90.secondary_action_button", + "translation": "Ver Planes y Precios" + }, + { + "id": "api.templates.delinquency_90.button", + "translation": "Actualizar pago" + }, + { + "id": "api.templates.delinquency_75.title", + "translation": "Tu workspace será degradado en 15 días" + }, + { + "id": "api.templates.delinquency_75.subtitle3", + "translation": "Actualiza ahora tu información de pago, o degrada a Cloud Starter." + }, + { + "id": "api.templates.delinquency_75.subtitle2", + "translation": "Tu workspace será degradado a Cloud Starter. Las características de tu {{.Plan}} serán bloqueadas y algunos de los datos de tu workspace podrían ser archivados hasta que liquides completamente tu saldo pendiente." + }, + { + "id": "api.templates.delinquency_75.subtitle1", + "translation": "Este es un aviso final de que no hemos recibido el pago por tu workspace Mattermost Cloud desde {{.DelinquencyDate}}" + }, + { + "id": "api.templates.delinquency_75.subject", + "translation": "Tu {{.Plan}} Mattermost será degradado en 15 días" + }, + { + "id": "api.templates.delinquency_75.downgrade_to_starter", + "translation": "Degradar a Cloud Starter" + }, + { + "id": "api.templates.delinquency_75.button", + "translation": "Actualizar pago" + }, + { + "id": "api.templates.delinquency_7.title", + "translation": "Tu pago no se completó" + }, + { + "id": "api.templates.delinquency_7.subtitle2", + "translation": "Para mantener tu plan {{.Plan}} activo, por favor contacta a tu institución financiera tan pronto como sea posible, Luego, actualiza tus detalles de pago según sea necesario." + }, + { + "id": "api.templates.delinquency_7.subtitle1", + "translation": "No pudimos procesar tu pago más reciente" + }, + { + "id": "api.templates.delinquency_7.button", + "translation": "Actualizar pago" + }, + { + "id": "api.templates.delinquency_60.title", + "translation": "Tu workspace Mattermost será degradado en 30 días" + }, + { + "id": "api.templates.delinquency_60.subtitle3", + "translation": "Actualiza tu información de pago ahora o degrada a Cloud Starter abajo." + }, + { + "id": "api.templates.delinquency_60.subtitle2", + "translation": "Degradaremos tu workspace automáticamente en 30 días si no somos capaces de procesar tu pago." + }, + { + "id": "api.templates.delinquency_60.subtitle1", + "translation": "Por favor actualiza pronto tu información de pago para procesar tus facturas pendientes." + }, + { + "id": "api.templates.delinquency_60.subject", + "translation": "Acción requerida: El workspace será degradado en 30 días" + }, + { + "id": "api.templates.delinquency_60.downgrade_to_starter", + "translation": "Degradar a Cloud Starter" + }, + { + "id": "api.templates.delinquency_60.button", + "translation": "Actualizar pago" + }, + { + "id": "api.templates.delinquency_45.title", + "translation": "Tu workspace será degradado pronto" + }, + { + "id": "api.templates.delinquency_45.subtitle3", + "translation": "Actualiza la información de tu tarjeta de crédito ahora." + }, + { + "id": "api.templates.delinquency_45.subtitle2", + "translation": "Un workspace degradado podría afectar negativamente los flujos de trabajo críticos, integraciones y otras actividades críticas de negocio realizadas en tu workspace." + }, + { + "id": "api.templates.delinquency_45.subtitle1", + "translation": "No fuimos capaces de cobrar el pago para las facturas con fecha {{.DelinquencyDate}}. Tu workspace está en riesgo de ser degradado." + }, + { + "id": "api.templates.delinquency_45.subject", + "translation": "Aviso: Tu {{.Plan}} Mattermost será degradado pronto" + }, + { + "id": "api.templates.delinquency_45.button", + "translation": "Actualizar pago" + }, + { + "id": "api.templates.delinquency_30.title", + "translation": "Tu workspace será degradado pronto" + }, + { + "id": "api.templates.delinquency_30.subtitle2", + "translation": "si no se toma ninguna medida, tu workspace será degradado y los siguientes datos serán archivados:" + }, + { + "id": "api.templates.delinquency_30.subtitle1", + "translation": "Tienes tiempo para mantener activo tu {{.Plan}} Mattermost pero necesitarás resolver los problemas con tu método de pago." + }, + { + "id": "api.templates.delinquency_30.subject", + "translation": "Actúa para mantener las característica de tu {{.Plan}} Mattermost" + }, + { + "id": "api.templates.delinquency_30.limits_documentation", + "translation": "Ver toda la documentación de límites." + }, + { + "id": "api.templates.delinquency_30.button", + "translation": "Actualizar pago" + }, + { + "id": "api.templates.delinquency_30.bullet.plugins", + "translation": "Plugins e integraciones activas" + }, + { + "id": "api.templates.delinquency_30.bullet.message_history", + "translation": "Historial de mensajes" + }, + { + "id": "api.templates.delinquency_30.bullet.files", + "translation": "Archivos" + }, + { + "id": "api.templates.delinquency_30.bullet.cards", + "translation": "Tarjetas de tus Boards" + }, + { + "id": "api.templates.delinquency_14.title", + "translation": "Pago no recibido" + }, + { + "id": "api.templates.delinquency_14.subtitle2", + "translation": "Por favor contacta a tu institución financiera para resolver cualquier problema. Luego, actualiza los detalles de pago como sean necesarios." + }, + { + "id": "api.templates.delinquency_14.subtitle1", + "translation": "No pudimos realizar el cargo a la tarjeta de crédito que tenemos registrada. Por lo cual tu workspace está en riesgo de ser degradado a Cloud Starter." + }, + { + "id": "api.templates.delinquency_14.subject", + "translation": "El pago está vencido para tu {{.Plan}} Mattermost." + }, + { + "id": "api.templates.delinquency_14.button", + "translation": "Actualizar pago" + }, + { + "id": "api.team.invite_guests_to_channels.license.error", + "translation": "Tu licencia no admite cuentas de invitado" + }, + { + "id": "api.team.invite_guests_to_channels.disabled.error", + "translation": "Las cuentas de invitado están deshabilitadas" + }, + { + "id": "api.command_marketplace.unsupported.app_error", + "translation": "El comando marketplace no es soportado por tu dispositivo." + }, + { + "id": "api.command_marketplace.name", + "translation": "marketplace" + }, + { + "id": "api.command_marketplace.desc", + "translation": "Abre el Marketplace" + }, + { + "id": "api.command_help.success", + "translation": "Mattermost es una plataforma de código abierto para comunicación segura, colaboración, y orquestación de trabajo entre herramientas y equipos.\nMattermost contiene tres herramientas clave:\n\n**Channels** - Mantente conectado con tu equipo vía 1:1 y en por mensajes grupales.\n**[Playbooks](/playbooks)** - Construye y configura procesos repetibles para lograr resultados específicos y predecibles.\n**[Tableros](/boards)** - Administra proyectos y tareas en una estructura de tablero Kanban para ayudar a que tú equipo logre los hitos clave.\n\n[Ver documentación y guías]({{.HelpLink}})" + }, + { + "id": "app.cloud.trial_plan_bot_message", + "translation": "los miembros {{.UsersNum}} del espacio de trabajo {{.WorkspaceName}} han solicitado comenzar la prueba Enterprise para acceder a: " + }, + { + "id": "api.cloud.delinquency_email.missing_email_to_trigger", + "translation": "Campos faltantes requeridos para enviar el correo electrónico de morosidad." + }, + { + "id": "model.group.name.reserved_name.app_error", + "translation": "el nombre de grupo ya existe como un nombre reservado" + }, + { + "id": "model.config.is_valid.amazons3_timeout.app_error", + "translation": "Valor de timeout inválido {{.Value}}. Debe ser un número positivo." + }, + { + "id": "ent.saml.configure.certificate_parse_error.app_error", + "translation": "SAML no pudo cargar satisfactoriamente el Identity Provider Public Certificate, por favor contacta a tu administrador del sistema." + }, + { + "id": "app.user.get_badge_count.app_error", + "translation": "No pudimos obtener el recuento de insignias para el usuario." + }, + { + "id": "app.team.clear_cache.app_error", + "translation": "Error al limpiar el caché de los miembros del equipo" + }, + { + "id": "app.post_reminder_dm", + "translation": "Hola, este es un recordatorio acerca de este mensaje de @{{.Username}}: {{.SiteURL}}/{{.TeamName}}/pl/{{.PostId}}" + }, + { + "id": "app.post.get_top_dms_for_user_since.app_error", + "translation": "No es posible obtener los DMs top para el usuario." + }, + { + "id": "app.plugin.product_mode.app_error", + "translation": "El plugin {{.Name}} no se pudo activar en el modo producto." + }, + { + "id": "app.notify_admin.send_notification_post.app_error", + "translation": "No es posible enviar la publicación de notificación." + }, + { + "id": "app.notify_admin.save.app_error", + "translation": "No es posible guardar los datos de notificación." + }, + { + "id": "app.last_accessible_file.app_error", + "translation": "Error al obtener el último archivo accesible" + }, + { + "id": "app.job.error", + "translation": "Error durante la ejecución de un job." + }, + { + "id": "app.file.cloud.get.app_error", + "translation": "No se puede obtener el archivo porque superó los límites del plan en la nube." + }, + { + "id": "app.cloud.upgrade_plan_bot_message_single", + "translation": "el miembro {{.UsersNum}} del workspace {{.WorkspaceName}} ha solicitado una actualización del workspace para: " + }, + { + "id": "app.cloud.upgrade_plan_bot_message", + "translation": "los miembros {{.UsersNum}} del workspace {{.WorkspaceName}} han solicitado una actualización del workspace para: " + }, + { + "id": "app.cloud.trial_plan_bot_message_single", + "translation": "el miembro {{.UsersNum}} del workspace {{.WorkspaceName}} ha solicitado comenzar la prueba Enterprise para acceder a: " } ] From 312bf283a8c577189fb7f643af5e68977b561cab Mon Sep 17 00:00:00 2001 From: Shivashis Padhi Date: Thu, 20 Oct 2022 10:08:09 +0530 Subject: [PATCH 40/47] [MM-47538] Pass handler functions directly while creating API endpoints (#21441) * Pass handler functions directly without permission check middlewares * Create necessary handlers in api4/group_local.go * Refactor GetGroupsByTeam, GetGroupsByChannel to a common function for local and API --- api4/group.go | 241 +++++++++++++++++++++++++++++++------------- api4/group_local.go | 36 ++++++- api4/handlers.go | 38 +++---- api4/insights.go | 141 ++++++++++++++++++++++++-- 4 files changed, 351 insertions(+), 105 deletions(-) diff --git a/api4/group.go b/api4/group.go index f8b65aa5bb..abcde27459 100644 --- a/api4/group.go +++ b/api4/group.go @@ -18,82 +18,88 @@ import ( func (api *API) InitGroup() { // GET /api/v4/groups - api.BaseRoutes.Groups.Handle("", api.APISessionRequired(requireLicense(getGroups))).Methods("GET") + api.BaseRoutes.Groups.Handle("", api.APISessionRequired(getGroups)).Methods("GET") // POST /api/v4/groups - api.BaseRoutes.Groups.Handle("", api.APISessionRequired(requireLicense(createGroup))).Methods("POST") + api.BaseRoutes.Groups.Handle("", api.APISessionRequired(createGroup)).Methods("POST") // GET /api/v4/groups/:group_id api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}", - api.APISessionRequired(requireLicense(getGroup))).Methods("GET") + api.APISessionRequired(getGroup)).Methods("GET") // PUT /api/v4/groups/:group_id/patch api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/patch", - api.APISessionRequired(requireLicense(patchGroup))).Methods("PUT") + api.APISessionRequired(patchGroup)).Methods("PUT") // POST /api/v4/groups/:group_id/teams/:team_id/link // POST /api/v4/groups/:group_id/channels/:channel_id/link api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/{syncable_type:teams|channels}/{syncable_id:[A-Za-z0-9]+}/link", - api.APISessionRequired(requireLicense(linkGroupSyncable))).Methods("POST") + api.APISessionRequired(linkGroupSyncable)).Methods("POST") // DELETE /api/v4/groups/:group_id/teams/:team_id/link // DELETE /api/v4/groups/:group_id/channels/:channel_id/link api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/{syncable_type:teams|channels}/{syncable_id:[A-Za-z0-9]+}/link", - api.APISessionRequired(requireLicense(unlinkGroupSyncable))).Methods("DELETE") + api.APISessionRequired(unlinkGroupSyncable)).Methods("DELETE") // GET /api/v4/groups/:group_id/teams/:team_id // GET /api/v4/groups/:group_id/channels/:channel_id api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/{syncable_type:teams|channels}/{syncable_id:[A-Za-z0-9]+}", - api.APISessionRequired(requireLicense(getGroupSyncable))).Methods("GET") + api.APISessionRequired(getGroupSyncable)).Methods("GET") // GET /api/v4/groups/:group_id/teams // GET /api/v4/groups/:group_id/channels api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/{syncable_type:teams|channels}", - api.APISessionRequired(requireLicense(getGroupSyncables))).Methods("GET") + api.APISessionRequired(getGroupSyncables)).Methods("GET") // PUT /api/v4/groups/:group_id/teams/:team_id/patch // PUT /api/v4/groups/:group_id/channels/:channel_id/patch api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/{syncable_type:teams|channels}/{syncable_id:[A-Za-z0-9]+}/patch", - api.APISessionRequired(requireLicense(patchGroupSyncable))).Methods("PUT") + api.APISessionRequired(patchGroupSyncable)).Methods("PUT") // GET /api/v4/groups/:group_id/stats api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/stats", - api.APISessionRequired(requireLicense(getGroupStats))).Methods("GET") + api.APISessionRequired(getGroupStats)).Methods("GET") // GET /api/v4/groups/:group_id/members api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/members", - api.APISessionRequired(requireLicense(getGroupMembers))).Methods("GET") + api.APISessionRequired(getGroupMembers)).Methods("GET") // GET /api/v4/users/:user_id/groups api.BaseRoutes.Users.Handle("/{user_id:[A-Za-z0-9]+}/groups", - api.APISessionRequired(requireLicense(getGroupsByUserId))).Methods("GET") + api.APISessionRequired(getGroupsByUserId)).Methods("GET") // GET /api/v4/channels/:channel_id/groups api.BaseRoutes.Channels.Handle("/{channel_id:[A-Za-z0-9]+}/groups", - api.APISessionRequired(requireLicense(getGroupsByChannel))).Methods("GET") + api.APISessionRequired(getGroupsByChannel)).Methods("GET") // GET /api/v4/teams/:team_id/groups api.BaseRoutes.Teams.Handle("/{team_id:[A-Za-z0-9]+}/groups", - api.APISessionRequired(requireLicense(getGroupsByTeam))).Methods("GET") + api.APISessionRequired(getGroupsByTeam)).Methods("GET") // GET /api/v4/teams/:team_id/groups_by_channels api.BaseRoutes.Teams.Handle("/{team_id:[A-Za-z0-9]+}/groups_by_channels", - api.APISessionRequired(requireLicense(getGroupsAssociatedToChannelsByTeam))).Methods("GET") + api.APISessionRequired(getGroupsAssociatedToChannelsByTeam)).Methods("GET") // DELETE /api/v4/groups/:group_id api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}", - api.APISessionRequired(requireLicense(deleteGroup))).Methods("DELETE") + api.APISessionRequired(deleteGroup)).Methods("DELETE") // POST /api/v4/groups/:group_id/members api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/members", - api.APISessionRequired(requireLicense(addGroupMembers))).Methods("POST") + api.APISessionRequired(addGroupMembers)).Methods("POST") // DELETE /api/v4/groups/:group_id/members api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/members", - api.APISessionRequired(requireLicense(deleteGroupMembers))).Methods("DELETE") + api.APISessionRequired(deleteGroupMembers)).Methods("DELETE") } func getGroup(c *Context, w http.ResponseWriter, r *http.Request) { + permissionErr := requireLicense(c) + if permissionErr != nil { + c.Err = permissionErr + return + } + c.RequireGroupId() if c.Err != nil { return @@ -130,6 +136,11 @@ func getGroup(c *Context, w http.ResponseWriter, r *http.Request) { } func createGroup(c *Context, w http.ResponseWriter, r *http.Request) { + permissionErr := requireLicense(c) + if permissionErr != nil { + c.Err = permissionErr + return + } var group *model.GroupWithUserIds if err := json.NewDecoder(r.Body).Decode(&group); err != nil { c.SetInvalidParamWithErr("group", err) @@ -185,6 +196,11 @@ func createGroup(c *Context, w http.ResponseWriter, r *http.Request) { } func patchGroup(c *Context, w http.ResponseWriter, r *http.Request) { + permissionErr := requireLicense(c) + if permissionErr != nil { + c.Err = permissionErr + return + } c.RequireGroupId() if c.Err != nil { return @@ -277,6 +293,11 @@ func patchGroup(c *Context, w http.ResponseWriter, r *http.Request) { } func linkGroupSyncable(c *Context, w http.ResponseWriter, r *http.Request) { + permissionErr := requireLicense(c) + if permissionErr != nil { + c.Err = permissionErr + return + } c.RequireGroupId() if c.Err != nil { return @@ -368,6 +389,11 @@ func linkGroupSyncable(c *Context, w http.ResponseWriter, r *http.Request) { } func getGroupSyncable(c *Context, w http.ResponseWriter, r *http.Request) { + permissionErr := requireLicense(c) + if permissionErr != nil { + c.Err = permissionErr + return + } c.RequireGroupId() if c.Err != nil { return @@ -411,6 +437,11 @@ func getGroupSyncable(c *Context, w http.ResponseWriter, r *http.Request) { } func getGroupSyncables(c *Context, w http.ResponseWriter, r *http.Request) { + permissionErr := requireLicense(c) + if permissionErr != nil { + c.Err = permissionErr + return + } c.RequireGroupId() if c.Err != nil { return @@ -448,6 +479,11 @@ func getGroupSyncables(c *Context, w http.ResponseWriter, r *http.Request) { } func patchGroupSyncable(c *Context, w http.ResponseWriter, r *http.Request) { + permissionErr := requireLicense(c) + if permissionErr != nil { + c.Err = permissionErr + return + } c.RequireGroupId() if c.Err != nil { return @@ -529,6 +565,11 @@ func patchGroupSyncable(c *Context, w http.ResponseWriter, r *http.Request) { } func unlinkGroupSyncable(c *Context, w http.ResponseWriter, r *http.Request) { + permissionErr := requireLicense(c) + if permissionErr != nil { + c.Err = permissionErr + return + } c.RequireGroupId() if c.Err != nil { return @@ -606,6 +647,11 @@ func verifyLinkUnlinkPermission(c *Context, syncableType model.GroupSyncableType } func getGroupMembers(c *Context, w http.ResponseWriter, r *http.Request) { + permissionErr := requireLicense(c) + if permissionErr != nil { + c.Err = permissionErr + return + } c.RequireGroupId() if c.Err != nil { return @@ -651,6 +697,11 @@ func getGroupMembers(c *Context, w http.ResponseWriter, r *http.Request) { } func getGroupStats(c *Context, w http.ResponseWriter, r *http.Request) { + permissionErr := requireLicense(c) + if permissionErr != nil { + c.Err = permissionErr + return + } c.RequireGroupId() if c.Err != nil { return @@ -686,6 +737,11 @@ func getGroupStats(c *Context, w http.ResponseWriter, r *http.Request) { } func getGroupsByUserId(c *Context, w http.ResponseWriter, r *http.Request) { + permissionErr := requireLicense(c) + if permissionErr != nil { + c.Err = permissionErr + return + } c.RequireUserId() if c.Err != nil { return @@ -717,72 +773,46 @@ func getGroupsByUserId(c *Context, w http.ResponseWriter, r *http.Request) { } func getGroupsByChannel(c *Context, w http.ResponseWriter, r *http.Request) { + permissionErr := requireLicense(c) + if permissionErr != nil { + c.Err = permissionErr + return + } c.RequireChannelId() if c.Err != nil { return } - - if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.LDAPGroups { - c.Err = model.NewAppError("Api4.getGroupsByChannel", "api.ldap_groups.license_error", nil, "", http.StatusForbidden) - return - } - - channel, appErr := c.App.GetChannel(c.AppContext, c.Params.ChannelId) + b, appErr := getGroupsByChannelCommon(c, r) if appErr != nil { c.Err = appErr return } - - var permission *model.Permission - if channel.Type == model.ChannelTypePrivate { - permission = model.PermissionReadPrivateChannelGroups - } else { - permission = model.PermissionReadPublicChannelGroups - } - if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, permission) { - c.SetPermissionError(permission) - return - } - - opts := model.GroupSearchOpts{ - Q: c.Params.Q, - IncludeMemberCount: c.Params.IncludeMemberCount, - FilterAllowReference: c.Params.FilterAllowReference, - } - if c.Params.Paginate == nil || *c.Params.Paginate { - opts.PageOpts = &model.PageOpts{Page: c.Params.Page, PerPage: c.Params.PerPage} - } - - groups, totalCount, appErr := c.App.GetGroupsByChannel(c.Params.ChannelId, opts) - if appErr != nil { - c.Err = appErr - return - } - - b, err := json.Marshal(struct { - Groups []*model.GroupWithSchemeAdmin `json:"groups"` - Count int `json:"total_group_count"` - }{ - Groups: groups, - Count: totalCount, - }) - if err != nil { - c.Err = model.NewAppError("Api4.getGroupsByChannel", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - return - } - w.Write(b) } func getGroupsByTeam(c *Context, w http.ResponseWriter, r *http.Request) { + permissionErr := requireLicense(c) + if permissionErr != nil { + c.Err = permissionErr + return + } c.RequireTeamId() if c.Err != nil { return } - if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.LDAPGroups { - c.Err = model.NewAppError("Api4.getGroupsByTeam", "api.ldap_groups.license_error", nil, "", http.StatusForbidden) + + b, appError := getGroupsByTeamCommon(c, r) + if appError != nil { + c.Err = appError return } + w.Write(b) +} + +func getGroupsByTeamCommon(c *Context, r *http.Request) ([]byte, *model.AppError) { + if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.LDAPGroups { + return nil, model.NewAppError("Api4.getGroupsByTeam", "api.ldap_groups.license_error", nil, "", http.StatusForbidden) + } opts := model.GroupSearchOpts{ Q: c.Params.Q, @@ -795,8 +825,7 @@ func getGroupsByTeam(c *Context, w http.ResponseWriter, r *http.Request) { groups, totalCount, appErr := c.App.GetGroupsByTeam(c.Params.TeamId, opts) if appErr != nil { - c.Err = appErr - return + return nil, appErr } b, err := json.Marshal(struct { @@ -808,14 +837,64 @@ func getGroupsByTeam(c *Context, w http.ResponseWriter, r *http.Request) { }) if err != nil { - c.Err = model.NewAppError("Api4.getGroupsByTeam", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - return + return nil, model.NewAppError("Api4.getGroupsByTeam", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) } - w.Write(b) + return b, nil +} +func getGroupsByChannelCommon(c *Context, r *http.Request) ([]byte, *model.AppError) { + if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.LDAPGroups { + return nil, model.NewAppError("Api4.getGroupsByChannel", "api.ldap_groups.license_error", nil, "", http.StatusForbidden) + } + + channel, appErr := c.App.GetChannel(c.AppContext, c.Params.ChannelId) + if appErr != nil { + return nil, appErr + } + + var permission *model.Permission + if channel.Type == model.ChannelTypePrivate { + permission = model.PermissionReadPrivateChannelGroups + } else { + permission = model.PermissionReadPublicChannelGroups + } + if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, permission) { + return nil, c.App.MakePermissionError(c.AppContext.Session(), []*model.Permission{permission}) + } + + opts := model.GroupSearchOpts{ + Q: c.Params.Q, + IncludeMemberCount: c.Params.IncludeMemberCount, + FilterAllowReference: c.Params.FilterAllowReference, + } + if c.Params.Paginate == nil || *c.Params.Paginate { + opts.PageOpts = &model.PageOpts{Page: c.Params.Page, PerPage: c.Params.PerPage} + } + + groups, totalCount, appErr := c.App.GetGroupsByChannel(c.Params.ChannelId, opts) + if appErr != nil { + return nil, appErr + } + + b, err := json.Marshal(struct { + Groups []*model.GroupWithSchemeAdmin `json:"groups"` + Count int `json:"total_group_count"` + }{ + Groups: groups, + Count: totalCount, + }) + if err != nil { + return nil, model.NewAppError("Api4.getGroupsByChannel", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) + } + return b, nil } func getGroupsAssociatedToChannelsByTeam(c *Context, w http.ResponseWriter, r *http.Request) { + permissionErr := requireLicense(c) + if permissionErr != nil { + c.Err = permissionErr + return + } c.RequireTeamId() if c.Err != nil { return @@ -855,6 +934,11 @@ func getGroupsAssociatedToChannelsByTeam(c *Context, w http.ResponseWriter, r *h } func getGroups(c *Context, w http.ResponseWriter, r *http.Request) { + permissionErr := requireLicense(c) + if permissionErr != nil { + c.Err = permissionErr + return + } var teamID, channelID string source := c.Params.GroupSource @@ -961,6 +1045,11 @@ func getGroups(c *Context, w http.ResponseWriter, r *http.Request) { } func deleteGroup(c *Context, w http.ResponseWriter, r *http.Request) { + permissionErr := requireLicense(c) + if permissionErr != nil { + c.Err = permissionErr + return + } c.RequireGroupId() if c.Err != nil { return @@ -1004,6 +1093,11 @@ func deleteGroup(c *Context, w http.ResponseWriter, r *http.Request) { } func addGroupMembers(c *Context, w http.ResponseWriter, r *http.Request) { + permissionErr := requireLicense(c) + if permissionErr != nil { + c.Err = permissionErr + return + } c.RequireGroupId() if c.Err != nil { return @@ -1058,6 +1152,11 @@ func addGroupMembers(c *Context, w http.ResponseWriter, r *http.Request) { } func deleteGroupMembers(c *Context, w http.ResponseWriter, r *http.Request) { + permissionErr := requireLicense(c) + if permissionErr != nil { + c.Err = permissionErr + return + } c.RequireGroupId() if c.Err != nil { return diff --git a/api4/group_local.go b/api4/group_local.go index 5ede4d7bf3..1964fdbed4 100644 --- a/api4/group_local.go +++ b/api4/group_local.go @@ -3,7 +3,39 @@ package api4 +import ( + "net/http" +) + func (api *API) InitGroupLocal() { - api.BaseRoutes.Channels.Handle("/{channel_id:[A-Za-z0-9]+}/groups", api.APILocal(getGroupsByChannel)).Methods("GET") - api.BaseRoutes.Teams.Handle("/{team_id:[A-Za-z0-9]+}/groups", api.APILocal(getGroupsByTeam)).Methods("GET") + api.BaseRoutes.Channels.Handle("/{channel_id:[A-Za-z0-9]+}/groups", api.APILocal(getGroupsByChannelLocal)).Methods("GET") + api.BaseRoutes.Teams.Handle("/{team_id:[A-Za-z0-9]+}/groups", api.APILocal(getGroupsByTeamLocal)).Methods("GET") +} + +func getGroupsByChannelLocal(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireChannelId() + if c.Err != nil { + return + } + b, appErr := getGroupsByChannelCommon(c, r) + if appErr != nil { + c.Err = appErr + return + } + + w.Write(b) +} + +func getGroupsByTeamLocal(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireTeamId() + if c.Err != nil { + return + } + b, appError := getGroupsByTeamCommon(c, r) + if appError != nil { + c.Err = appError + return + } + + w.Write(b) } diff --git a/api4/handlers.go b/api4/handlers.go index 9ec6be4aae..250dac32e7 100644 --- a/api4/handlers.go +++ b/api4/handlers.go @@ -200,33 +200,27 @@ func (api *API) APILocal(h handlerFunc) http.Handler { return handler } -func requireLicense(f handlerFunc) handlerFunc { - return func(c *Context, w http.ResponseWriter, r *http.Request) { - if c.App.Channels().License() == nil { - c.Err = model.NewAppError("", "api.license_error", nil, "", http.StatusNotImplemented) - return - } - f(c, w, r) +func requireLicense(c *Context) *model.AppError { + if c.App.Channels().License() == nil { + err := model.NewAppError("", "api.license_error", nil, "", http.StatusNotImplemented) + return err } + return nil } -func minimumProfessionalLicense(f handlerFunc) handlerFunc { - return func(c *Context, w http.ResponseWriter, r *http.Request) { - lic := c.App.Srv().License() - if lic == nil || (lic.SkuShortName != model.LicenseShortSkuProfessional && lic.SkuShortName != model.LicenseShortSkuEnterprise) { - c.Err = model.NewAppError("", model.NoTranslation, nil, "license is neither professional nor enterprise", http.StatusNotImplemented) - return - } - f(c, w, r) +func minimumProfessionalLicense(c *Context) *model.AppError { + lic := c.App.Srv().License() + if lic == nil || (lic.SkuShortName != model.LicenseShortSkuProfessional && lic.SkuShortName != model.LicenseShortSkuEnterprise) { + err := model.NewAppError("", model.NoTranslation, nil, "license is neither professional nor enterprise", http.StatusNotImplemented) + return err } + return nil } -func rejectGuests(f handlerFunc) handlerFunc { - return func(c *Context, w http.ResponseWriter, r *http.Request) { - if c.AppContext.Session().Props[model.SessionPropIsGuest] == "true" { - c.Err = model.NewAppError("", model.NoTranslation, nil, "insufficient permissions as a guest user", http.StatusNotImplemented) - return - } - f(c, w, r) +func rejectGuests(c *Context) *model.AppError { + if c.AppContext.Session().Props[model.SessionPropIsGuest] == "true" { + err := model.NewAppError("", model.NoTranslation, nil, "insufficient permissions as a guest user", http.StatusNotImplemented) + return err } + return nil } diff --git a/api4/insights.go b/api4/insights.go index 3c90294ec7..fce03cfa04 100644 --- a/api4/insights.go +++ b/api4/insights.go @@ -13,31 +13,44 @@ import ( func (api *API) InitInsights() { // Reactions - api.BaseRoutes.InsightsForTeam.Handle("/reactions", api.APISessionRequired(minimumProfessionalLicense(rejectGuests(getTopReactionsForTeamSince)))).Methods("GET") - api.BaseRoutes.InsightsForUser.Handle("/reactions", api.APISessionRequired(minimumProfessionalLicense(rejectGuests(getTopReactionsForUserSince)))).Methods("GET") + api.BaseRoutes.InsightsForTeam.Handle("/reactions", api.APISessionRequired(getTopReactionsForTeamSince)).Methods("GET") + api.BaseRoutes.InsightsForUser.Handle("/reactions", api.APISessionRequired(getTopReactionsForUserSince)).Methods("GET") // Channels - api.BaseRoutes.InsightsForTeam.Handle("/channels", api.APISessionRequired(minimumProfessionalLicense(rejectGuests(getTopChannelsForTeamSince)))).Methods("GET") - api.BaseRoutes.InsightsForUser.Handle("/channels", api.APISessionRequired(minimumProfessionalLicense(rejectGuests(getTopChannelsForUserSince)))).Methods("GET") + api.BaseRoutes.InsightsForTeam.Handle("/channels", api.APISessionRequired(getTopChannelsForTeamSince)).Methods("GET") + api.BaseRoutes.InsightsForUser.Handle("/channels", api.APISessionRequired(getTopChannelsForUserSince)).Methods("GET") // Threads - api.BaseRoutes.InsightsForTeam.Handle("/threads", api.APISessionRequired(minimumProfessionalLicense(rejectGuests(getTopThreadsForTeamSince)))).Methods("GET") - api.BaseRoutes.InsightsForUser.Handle("/threads", api.APISessionRequired(minimumProfessionalLicense(rejectGuests(getTopThreadsForUserSince)))).Methods("GET") + api.BaseRoutes.InsightsForTeam.Handle("/threads", api.APISessionRequired(getTopThreadsForTeamSince)).Methods("GET") + api.BaseRoutes.InsightsForUser.Handle("/threads", api.APISessionRequired(getTopThreadsForUserSince)).Methods("GET") // user DMs - api.BaseRoutes.InsightsForUser.Handle("/dms", api.APISessionRequired(minimumProfessionalLicense(rejectGuests(getTopDMsForUserSince)))).Methods("GET") + api.BaseRoutes.InsightsForUser.Handle("/dms", api.APISessionRequired(getTopDMsForUserSince)).Methods("GET") // Inactive channels - api.BaseRoutes.InsightsForTeam.Handle("/inactive_channels", api.APISessionRequired(minimumProfessionalLicense(rejectGuests(getTopInactiveChannelsForTeamSince)))).Methods("GET") - api.BaseRoutes.InsightsForUser.Handle("/inactive_channels", api.APISessionRequired(minimumProfessionalLicense(rejectGuests(getTopInactiveChannelsForUserSince)))).Methods("GET") + api.BaseRoutes.InsightsForTeam.Handle("/inactive_channels", api.APISessionRequired(getTopInactiveChannelsForTeamSince)).Methods("GET") + api.BaseRoutes.InsightsForUser.Handle("/inactive_channels", api.APISessionRequired(getTopInactiveChannelsForUserSince)).Methods("GET") // New teammembers - api.BaseRoutes.InsightsForTeam.Handle("/team_members", api.APISessionRequired(minimumProfessionalLicense(rejectGuests(getNewTeamMembersSince)))).Methods("GET") + api.BaseRoutes.InsightsForTeam.Handle("/team_members", api.APISessionRequired(getNewTeamMembersSince)).Methods("GET") } // Top Reactions func getTopReactionsForTeamSince(c *Context, w http.ResponseWriter, r *http.Request) { + + // license and guest user check + permissionErr := minimumProfessionalLicense(c) + if permissionErr != nil { + c.Err = permissionErr + return + } + permissionErr = rejectGuests(c) + if permissionErr != nil { + c.Err = permissionErr + return + } + c.RequireTeamId() if c.Err != nil { return @@ -82,6 +95,18 @@ func getTopReactionsForTeamSince(c *Context, w http.ResponseWriter, r *http.Requ } func getTopReactionsForUserSince(c *Context, w http.ResponseWriter, r *http.Request) { + // license and guest user check + permissionErr := minimumProfessionalLicense(c) + if permissionErr != nil { + c.Err = permissionErr + return + } + permissionErr = rejectGuests(c) + if permissionErr != nil { + c.Err = permissionErr + return + } + c.Params.TeamId = r.URL.Query().Get("team_id") // TeamId is an optional parameter @@ -133,6 +158,18 @@ func getTopReactionsForUserSince(c *Context, w http.ResponseWriter, r *http.Requ // Top Channels func getTopChannelsForTeamSince(c *Context, w http.ResponseWriter, r *http.Request) { + // license and guest user check + permissionErr := minimumProfessionalLicense(c) + if permissionErr != nil { + c.Err = permissionErr + return + } + permissionErr = rejectGuests(c) + if permissionErr != nil { + c.Err = permissionErr + return + } + c.RequireTeamId() if c.Err != nil { return @@ -184,6 +221,18 @@ func getTopChannelsForTeamSince(c *Context, w http.ResponseWriter, r *http.Reque } func getTopChannelsForUserSince(c *Context, w http.ResponseWriter, r *http.Request) { + // license and guest user check + permissionErr := minimumProfessionalLicense(c) + if permissionErr != nil { + c.Err = permissionErr + return + } + permissionErr = rejectGuests(c) + if permissionErr != nil { + c.Err = permissionErr + return + } + c.Params.TeamId = r.URL.Query().Get("team_id") // TeamId is an optional parameter @@ -241,6 +290,18 @@ func getTopChannelsForUserSince(c *Context, w http.ResponseWriter, r *http.Reque // Top Threads func getTopThreadsForTeamSince(c *Context, w http.ResponseWriter, r *http.Request) { + // license and guest user check + permissionErr := minimumProfessionalLicense(c) + if permissionErr != nil { + c.Err = permissionErr + return + } + permissionErr = rejectGuests(c) + if permissionErr != nil { + c.Err = permissionErr + return + } + c.RequireTeamId() if c.Err != nil { return @@ -286,6 +347,18 @@ func getTopThreadsForTeamSince(c *Context, w http.ResponseWriter, r *http.Reques } func getTopThreadsForUserSince(c *Context, w http.ResponseWriter, r *http.Request) { + // license and guest user check + permissionErr := minimumProfessionalLicense(c) + if permissionErr != nil { + c.Err = permissionErr + return + } + permissionErr = rejectGuests(c) + if permissionErr != nil { + c.Err = permissionErr + return + } + c.Params.TeamId = r.URL.Query().Get("team_id") // restrict users with no access to team @@ -336,6 +409,18 @@ func getTopThreadsForUserSince(c *Context, w http.ResponseWriter, r *http.Reques // Top DMs func getTopDMsForUserSince(c *Context, w http.ResponseWriter, r *http.Request) { + // license and guest user check + permissionErr := minimumProfessionalLicense(c) + if permissionErr != nil { + c.Err = permissionErr + return + } + permissionErr = rejectGuests(c) + if permissionErr != nil { + c.Err = permissionErr + return + } + user, err := c.App.GetUser(c.AppContext.Session().UserId) if err != nil { c.Err = err @@ -367,6 +452,18 @@ func getTopDMsForUserSince(c *Context, w http.ResponseWriter, r *http.Request) { // Top Channels func getTopInactiveChannelsForTeamSince(c *Context, w http.ResponseWriter, r *http.Request) { + // license and guest user check + permissionErr := minimumProfessionalLicense(c) + if permissionErr != nil { + c.Err = permissionErr + return + } + permissionErr = rejectGuests(c) + if permissionErr != nil { + c.Err = permissionErr + return + } + c.RequireTeamId() if c.Err != nil { return @@ -411,6 +508,18 @@ func getTopInactiveChannelsForTeamSince(c *Context, w http.ResponseWriter, r *ht // top inactive channels func getTopInactiveChannelsForUserSince(c *Context, w http.ResponseWriter, r *http.Request) { + // license and guest user check + permissionErr := minimumProfessionalLicense(c) + if permissionErr != nil { + c.Err = permissionErr + return + } + permissionErr = rejectGuests(c) + if permissionErr != nil { + c.Err = permissionErr + return + } + c.Params.TeamId = r.URL.Query().Get("team_id") // TeamId is an optional parameter @@ -479,6 +588,18 @@ func postCountByDurationViewModel(c *Context, topChannelList *model.TopChannelLi } func getNewTeamMembersSince(c *Context, w http.ResponseWriter, r *http.Request) { + // license and guest user check + permissionErr := minimumProfessionalLicense(c) + if permissionErr != nil { + c.Err = permissionErr + return + } + permissionErr = rejectGuests(c) + if permissionErr != nil { + c.Err = permissionErr + return + } + c.RequireTeamId() if c.Err != nil { return From 31e6cdd0e6d334e35ca155e12a2d985b49af5f31 Mon Sep 17 00:00:00 2001 From: Harshil Sharma <18575143+harshilsharma63@users.noreply.github.com> Date: Thu, 20 Oct 2022 11:05:22 +0530 Subject: [PATCH 41/47] Slack attachment in email notifications (#20946) * First POC * Added author data and markdown support * WIP * styled fields * WIP * WIP * IMplementation done * Updated template * fixed mjml compilation error * removed card.mjml * Added test * Updated a styling * Updated a styling * CI * Updated generated templates * Explicitelly specified font for message attachments * Lint fix * Removed leftover existances of slack attachment --- app/notification_email.go | 89 +++++++ app/notification_email_test.go | 97 +++++++ templates/invite_body.mjml | 8 +- templates/messages_notification.html | 244 +++++++++++++++++- templates/messages_notification.mjml | 10 +- templates/partials/card.mjml | 42 --- templates/partials/message_attachment.css | 146 +++++++++++ templates/partials/message_attachment.html | 95 +++++++ .../partials/message_attachment_styles.mjml | 5 + templates/partials/message_avatar_col.mjml | 3 + templates/partials/message_button.mjml | 5 + templates/partials/sender_info_col.mjml | 30 +++ 12 files changed, 729 insertions(+), 45 deletions(-) delete mode 100644 templates/partials/card.mjml create mode 100644 templates/partials/message_attachment.css create mode 100644 templates/partials/message_attachment.html create mode 100644 templates/partials/message_attachment_styles.mjml create mode 100644 templates/partials/message_avatar_col.mjml create mode 100644 templates/partials/message_button.mjml create mode 100644 templates/partials/sender_info_col.mjml diff --git a/app/notification_email.go b/app/notification_email.go index 914b729a01..965dd9a292 100644 --- a/app/notification_email.go +++ b/app/notification_email.go @@ -201,6 +201,18 @@ func truncateUserNames(name string, i int) string { return name } +type FieldRow struct { + Cells []*model.SlackAttachmentField +} + +type EmailMessageAttachment struct { + model.SlackAttachment + + Pretext template.HTML + Text template.HTML + FieldRows []FieldRow +} + type postData struct { SenderName string ChannelName string @@ -211,6 +223,7 @@ type postData struct { Time string ShowChannelIcon bool OtherChannelMembersCount int + MessageAttachments []*EmailMessageAttachment } /** @@ -245,6 +258,7 @@ func (a *App) getNotificationEmailBody(c request.CTX, recipient *model.User, pos } pData.Message = template.HTML(normalizedPostMessage) pData.Time = translateFunc("app.notification.body.dm.time", messageTime) + pData.MessageAttachments = a.processMessageAttachments(post) } data := a.Srv().EmailService.NewEmailTemplateData(recipient.Locale) @@ -306,6 +320,81 @@ func (a *App) getNotificationEmailBody(c request.CTX, recipient *model.User, pos return a.Srv().TemplatesContainer().RenderToString("messages_notification", data) } +func (a *App) processMessageAttachments(post *model.Post) []*EmailMessageAttachment { + emailMessageAttachments := []*EmailMessageAttachment{} + + for _, messageAttachment := range post.Attachments() { + emailMessageAttachment := &EmailMessageAttachment{ + SlackAttachment: *messageAttachment, + Pretext: a.prepareTextForEmail(messageAttachment.Pretext), + Text: a.prepareTextForEmail(messageAttachment.Text), + } + + stripedTitle, err := utils.StripMarkdown(emailMessageAttachment.Title) + if err != nil { + mlog.Warn("Failed parse to markdown from messageatatchment title", mlog.String("post_id", post.Id), mlog.Err(err)) + stripedTitle = "" + } + + emailMessageAttachment.Title = stripedTitle + + shortFieldRow := FieldRow{} + + for i := range messageAttachment.Fields { + // Create a new instance to avoid altering the original pointer reference + // We update field value to parse markdown. + // If we do that on the original pointer, the rendered text in mattermost + // becomes invalid as its no longer a markdown string, but rather an HTML string. + field := &model.SlackAttachmentField{ + Title: messageAttachment.Fields[i].Title, + Value: messageAttachment.Fields[i].Value, + Short: messageAttachment.Fields[i].Short, + } + + if stringValue, ok := field.Value.(string); ok { + field.Value = a.prepareTextForEmail(stringValue) + } + + if !field.Short { + if len(shortFieldRow.Cells) > 0 { + emailMessageAttachment.FieldRows = append(emailMessageAttachment.FieldRows, shortFieldRow) + shortFieldRow = FieldRow{} + } + + emailMessageAttachment.FieldRows = append(emailMessageAttachment.FieldRows, FieldRow{[]*model.SlackAttachmentField{field}}) + } else { + shortFieldRow.Cells = append(shortFieldRow.Cells, field) + + if len(shortFieldRow.Cells) == 2 { + emailMessageAttachment.FieldRows = append(emailMessageAttachment.FieldRows, shortFieldRow) + shortFieldRow = FieldRow{} + } + } + } + + // collect any leftover short fields + if len(shortFieldRow.Cells) > 0 { + emailMessageAttachment.FieldRows = append(emailMessageAttachment.FieldRows, shortFieldRow) + shortFieldRow = FieldRow{} + } + + emailMessageAttachments = append(emailMessageAttachments, emailMessageAttachment) + } + + return emailMessageAttachments +} + +func (a *App) prepareTextForEmail(text string) template.HTML { + escapedText := html.EscapeString(text) + markdownText, err := utils.MarkdownToHTML(escapedText) + if err != nil { + mlog.Warn("Encountered error while converting markdown to HTML", mlog.Err(err)) + return template.HTML(text) + } + + return template.HTML(markdownText) +} + type formattedPostTime struct { Time time.Time Year string diff --git a/app/notification_email_test.go b/app/notification_email_test.go index 8b48b50035..4d0f77f433 100644 --- a/app/notification_email_test.go +++ b/app/notification_email_test.go @@ -339,6 +339,103 @@ func TestGetNotificationEmailBodyFullNotificationLocaleTime24Hour(t *testing.T) require.Contains(t, body, "14:30", fmt.Sprintf("Expected email text '14:30'. Got %s", body)) } +func TestGetNotificationEmailBodyFullNotificationWithSlackAttachments(t *testing.T) { + th := SetupWithStoreMock(t) + defer th.TearDown() + + recipient := &model.User{} + post := &model.Post{ + Message: "This is the message", + } + + messageAttachments := []*model.SlackAttachment{ + { + Color: "#FF0000", + Pretext: "message attachment 1 pretext", + AuthorName: "author name", + AuthorLink: "https://example.com/slack_attachment_1/author_link", + AuthorIcon: "https://example.com/slack_attachment_1/author_icon", + Title: "message attachment 1 title", + TitleLink: "https://example.com/slack_attachment_1/title_link", + Text: "message attachment 1 text", + ImageURL: "https://example.com/slack_attachment_1/image", + ThumbURL: "https://example.com/slack_attachment_1/thumb", + Fields: []*model.SlackAttachmentField{ + { + Short: true, + Title: "message attachment 1 field 1 title", + Value: "message attachment 1 field 1 value", + }, + { + Short: false, + Title: "message attachment 1 field 2 title", + Value: "message attachment 1 field 2 value", + }, + { + Short: true, + Title: "message attachment 1 field 3 title", + Value: "message attachment 1 field 3 value", + }, + { + Short: true, + Title: "message attachment 1 field 4 title", + Value: "message attachment 1 field 4 value", + }, + }, + }, + { + Color: "#FF0000", + Pretext: "message attachment 2 pretext", + AuthorName: "author name 2", + Text: "message attachment 2 text", + }, + } + + model.ParseSlackAttachment(post, messageAttachments) + + channel := &model.Channel{ + DisplayName: "ChannelName", + Type: model.ChannelTypeOpen, + } + + channelName := "ChannelName" + senderName := "sender" + teamName := "testteam" + teamURL := "http://localhost:8065/testteam" + emailNotificationContentsType := model.EmailNotificationContentsFull + translateFunc := i18n.GetUserTranslations("en") + + storeMock := th.App.Srv().Store().(*mocks.Store) + teamStoreMock := mocks.TeamStore{} + teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil) + storeMock.On("Team").Return(&teamStoreMock) + + body, err := th.App.getNotificationEmailBody(th.Context, recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc, "user-avatar.png") + require.NoError(t, err) + require.Contains(t, body, "#FF0000") + require.Contains(t, body, "message attachment 1 pretext") + require.Contains(t, body, "author name") + require.Contains(t, body, "https://example.com/slack_attachment_1/author_link") + require.Contains(t, body, "https://example.com/slack_attachment_1/author_icon") + require.Contains(t, body, "message attachment 1 title") + require.Contains(t, body, "https://example.com/slack_attachment_1/title_link") + require.Contains(t, body, "message attachment 1 text") + require.Contains(t, body, "https://example.com/slack_attachment_1/image") + require.Contains(t, body, "https://example.com/slack_attachment_1/thumb") + require.Contains(t, body, "message attachment 1 field 1 title") + require.Contains(t, body, "message attachment 1 field 1 value") + require.Contains(t, body, "message attachment 1 field 2 title") + require.Contains(t, body, "message attachment 1 field 2 value") + require.Contains(t, body, "message attachment 1 field 3 title") + require.Contains(t, body, "message attachment 1 field 3 value") + require.Contains(t, body, "message attachment 1 field 4 title") + require.Contains(t, body, "message attachment 1 field 4 value") + require.Contains(t, body, "https://example.com/slack_attachment_1/thumb") + require.Contains(t, body, "message attachment 2 pretext") + require.Contains(t, body, "author name 2") + require.Contains(t, body, "message attachment 2 text") +} + // from here func TestGetNotificationEmailBodyGenericNotificationPublicChannel(t *testing.T) { th := SetupWithStoreMock(t) diff --git a/templates/invite_body.mjml b/templates/invite_body.mjml index 869a359b2c..d16c20af68 100644 --- a/templates/invite_body.mjml +++ b/templates/invite_body.mjml @@ -8,7 +8,13 @@ {{if .Props.Message}} {{range .Props.Posts}}
- + + + + + + +
{{end}}
{{else}} diff --git a/templates/messages_notification.html b/templates/messages_notification.html index 9c40227203..7b47478cfa 100644 --- a/templates/messages_notification.html +++ b/templates/messages_notification.html @@ -356,7 +356,160 @@ max-width: 100% !important; } } + + .messageAttachments * { + font-family: Open Sans, sans-serif !important; + } + + .messageAttachmentContent { + padding: 0px; + border: 1px solid rgba(63, 67, 80, 0.16); + margin-bottom: 20px; + border-radius: 0 4px 4px 0; + } + + .messageAttachmentContent>table, + .attachment__body { + font-family: Open Sans, sans-serif; + text-align: left; + font-size: 14px; + line-height: 20px; + color: #3F4350; + } + + .messageAttachmentContent .attachment__author-icon { + width: 14px; + height: 14px; + margin-right: 5px; + border-radius: 50px; + vertical-align: middle; + } + + .attachment__author-name { + opacity: 0.6; + } + + .messageAttachmentContent p { + margin: 0; + } + + .attachment__title { + padding: 0; + margin: 5px 0; + font-size: 14px; + font-weight: 600; + line-height: 18px; + } + + .attachment__image { + max-height: 300px; + border: 1px solid transparent; + margin-bottom: 1em; + } + + .attachment__thumb-image { + max-width: 100%; + max-height: 75px; + } + + .attachment__thumb-container { + max-width: 80px; + width: max-content; + } + + .attachment__wrapper { + width: 100%; + display: flex; + flex-direction: row; + gap: 12px; + } + + .attachment__body { + flex: 1; + } + + .messageAttachmentContent>table.attachment__footer-container { + color: #a3a3a3; + font-size: 12px; + } + + .attachment__footer-icon { + width: 16px; + height: 16px; + } + + .attachment__footer-container { + color: #a3a3a3; + font-size: 12px; + } + + .messageAttachment_title { + padding-top: 1em; + font-weight: 600; + margin-bottom: 4px; + } + + .pretext h1 { + font-size: 28px; + line-height: 32px; + } + + .pretext h2 { + font-size: 25px; + line-height: 30px; + } + + .pretext h3 { + font-size: 22px; + line-height: 25px; + } + + .pretext h4 { + font-size: 19px; + line-height: 24px; + } + + .pretext h5 { + font-size: 15px; + line-height: 20px; + } + + .pretext h6 { + font-size: 1em; + line-height: 1.4em; + } + + .pretext>* { + margin-bottom: 16px; + } + + .messageAttachments { + margin-top: 22px; + } + + .messageAttachments h1, + h2, + h3, + h4, + h5, + h6 { + font-weight: 500; + } + + code { + padding: 2px 4px; + font-size: 90%; + background-color: rgba(63, 67, 80, 0.1); + border-radius: 4px; + } + + .messageAttachments a { + background-color: unset !important; + border: none !important; + padding: unset !important; + } + @@ -507,7 +660,96 @@ - + + {{if .MessageAttachments}} +
+ {{range .MessageAttachments}} +
+
+ {{.Pretext}} +
+
+ + + {{if or .AuthorIcon .AuthorName}} + + + + {{end}} + {{if .Title}} + + + + {{end}} + +
+ {{if .AuthorLink}}{{end}} + {{if .AuthorIcon}}attachment author icon{{end}} + {{if .AuthorName}}{{.AuthorName}}{{end}} + {{if .AuthorLink}}{{end}} +
+

+ {{if .TitleLink}}{{end}} + {{.Title}} + {{if .Title}}{{end}} +

+
+
+
+ + + + + + {{if .ImageURL}} + + + + {{end}} + +
+
{{.Text}}
+
+ +
+ + {{range .FieldRows}} + + {{ $length := len .Cells }} + {{range .Cells}} + + {{end}} + + {{end}} +
+
{{.Title}}
+
{{.Value}}
+
+
+ {{if .ThumbURL}} +
+ +
+ {{end}} +
+ {{if .Footer}} + + + + + + + + {{end}} +
+
+ {{end}} +
+ {{end}} +
diff --git a/templates/messages_notification.mjml b/templates/messages_notification.mjml index ad840ff420..a2b53b6b16 100644 --- a/templates/messages_notification.mjml +++ b/templates/messages_notification.mjml @@ -1,13 +1,21 @@ + {{range .Props.Posts}}
- + + + + + + + +
{{end}}
diff --git a/templates/partials/card.mjml b/templates/partials/card.mjml deleted file mode 100644 index f1c835e04c..0000000000 --- a/templates/partials/card.mjml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - -
- - - - - {{.Message}} - - - - {{if .MessageURL}} - {{$.Props.MessageButton}} - {{end}} - - - diff --git a/templates/partials/message_attachment.css b/templates/partials/message_attachment.css new file mode 100644 index 0000000000..ee65c7f8a3 --- /dev/null +++ b/templates/partials/message_attachment.css @@ -0,0 +1,146 @@ +.messageAttachments * { + font-family: Open Sans, sans-serif !important; +} + +.messageAttachmentContent { + padding: 0px; + border: 1px solid rgba(63, 67, 80, 0.16); + margin-bottom: 20px; + border-radius: 0 4px 4px 0; +} + +.messageAttachmentContent > table, .attachment__body { + font-family: Open Sans, sans-serif; + text-align: left; + font-size: 14px; + line-height: 20px; + color: #3F4350; +} + +.messageAttachmentContent .attachment__author-icon { + width: 14px; + height: 14px; + margin-right: 5px; + border-radius: 50px; + vertical-align: middle; +} + +.attachment__author-name { + opacity: 0.6; +} + +.messageAttachmentContent p { + margin: 0; +} + +.attachment__title { + padding: 0; + margin: 5px 0; + font-size: 14px; + font-weight: 600; + line-height: 18px; +} + +.attachment__image { + max-height: 300px; + border: 1px solid transparent; + margin-bottom: 1em; +} + + +.attachment__thumb-image { + max-width: 100%; + max-height: 75px; +} + +.attachment__thumb-container { + max-width: 80px; + width: max-content; +} + +.attachment__wrapper { + width: 100%; + display: flex; + flex-direction: row; + gap: 12px; +} + +.attachment__body { + flex: 1; +} + +.messageAttachmentContent > table.attachment__footer-container { + color: #a3a3a3; + font-size: 12px; +} + +.attachment__footer-icon { + width: 16px; + height: 16px; +} + +.attachment__footer-container { + color: #a3a3a3; + font-size: 12px; +} + +.messageAttachment_title { + padding-top: 1em; + font-weight: 600; + margin-bottom: 4px; +} + +.pretext h1 { + font-size: 28px; + line-height: 32px; +} + +.pretext h2 { + font-size: 25px; + line-height: 30px; +} + +.pretext h3 { + font-size: 22px; + line-height: 25px; +} + +.pretext h4 { + font-size: 19px; + line-height: 24px; +} + +.pretext h5 { + font-size: 15px; + line-height: 20px; +} + +.pretext h6 { + font-size: 1em; + line-height: 1.4em; +} + +.pretext > * { + margin-bottom: 16px; +} + +.messageAttachments { + margin-top: 22px; +} + +.messageAttachments h1, h2, h3, h4, h5, h6 { + font-weight: 500; +} + +code { + padding: 2px 4px; + font-size: 90%; + background-color: rgba(63, 67, 80, 0.1); + border-radius: 4px; +} + +.messageAttachments a { + background-color: unset !important; + border: none !important; + padding: unset !important; +} diff --git a/templates/partials/message_attachment.html b/templates/partials/message_attachment.html new file mode 100644 index 0000000000..68dd5fe21b --- /dev/null +++ b/templates/partials/message_attachment.html @@ -0,0 +1,95 @@ +{{if .MessageAttachments}} +
+ {{range .MessageAttachments}} +
+
+ {{.Pretext}} +
+
+
-
-
{{.SenderName}}
- {{if .Time}} -
{{.Time}}
- {{end}} - {{if .ChannelName}} -
- {{if .ShowChannelIcon}} - - {{end}} -
- {{if .OtherChannelMembersCount}} - {{.OtherChannelMembersCount}} - {{end}} - {{.ChannelName}} -
-
- {{end}} -
-
+ + {{if or .AuthorIcon .AuthorName}} + + + + {{end}} + {{if .Title}} + + + + {{end}} + +
+ {{if .AuthorLink}}{{end}} + {{if .AuthorIcon}}attachment author icon{{end}} + {{if .AuthorName}}{{.AuthorName}}{{end}} + {{if .AuthorLink}}{{end}} +
+

+ {{if .TitleLink}}{{end}} + {{.Title}} + {{if .Title}}{{end}} +

+
+ +
+
+ + + + + + {{if .ImageURL}} + + + + {{end}} +
+
{{.Text}}
+
+ +
+ + + {{range .FieldRows}} + + {{ $length := len .Cells }} + {{range .Cells}} + + {{end}} + + {{end}} +
+
{{.Title}}
+
{{.Value}}
+
+
+ + {{if .ThumbURL}} +
+ +
+ {{end}} +
+ + {{if .Footer}} + + + + + + + + {{end}} + +
+ + {{end}} + +{{end}} diff --git a/templates/partials/message_attachment_styles.mjml b/templates/partials/message_attachment_styles.mjml new file mode 100644 index 0000000000..0e0de4dd3a --- /dev/null +++ b/templates/partials/message_attachment_styles.mjml @@ -0,0 +1,5 @@ + + + + + diff --git a/templates/partials/message_avatar_col.mjml b/templates/partials/message_avatar_col.mjml new file mode 100644 index 0000000000..dc4686896d --- /dev/null +++ b/templates/partials/message_avatar_col.mjml @@ -0,0 +1,3 @@ + + + diff --git a/templates/partials/message_button.mjml b/templates/partials/message_button.mjml new file mode 100644 index 0000000000..997edac1bc --- /dev/null +++ b/templates/partials/message_button.mjml @@ -0,0 +1,5 @@ + + {{if .MessageURL}} + {{$.Props.MessageButton}} + {{end}} + diff --git a/templates/partials/sender_info_col.mjml b/templates/partials/sender_info_col.mjml new file mode 100644 index 0000000000..69b37d1853 --- /dev/null +++ b/templates/partials/sender_info_col.mjml @@ -0,0 +1,30 @@ + + + + +
+
{{.SenderName}}
+ {{if .Time}} +
{{.Time}}
+ {{end}} + {{if .ChannelName}} +
+ {{if .ShowChannelIcon}} + + {{end}} +
+ {{if .OtherChannelMembersCount}} + {{.OtherChannelMembersCount}} + {{end}} + {{.ChannelName}} +
+
+ {{end}} +
+ + +
+ + {{.Message}} + +
From c39c05e93ccd9397b72fd07186233ef8b13fedbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20V=C3=A9lez=20Vidal?= Date: Thu, 20 Oct 2022 16:01:15 +0200 Subject: [PATCH 42/47] Replace 500 errors with more meaningful error codes (#21439) * Replace 500 errors with more meaningful error codes * replace forbidden with badrequest for cloud customer fetch, rename parameter name --- api4/cloud.go | 16 ++++++------ api4/cloud_test.go | 61 ++++++++++++++++++++++++++++++++++++++++++++++ model/client4.go | 4 +-- 3 files changed, 71 insertions(+), 10 deletions(-) diff --git a/api4/cloud.go b/api4/cloud.go index e86a9b0aca..a88ac919b3 100644 --- a/api4/cloud.go +++ b/api4/cloud.go @@ -158,14 +158,14 @@ func requestCloudTrial(c *Context, w http.ResponseWriter, r *http.Request) { // check if the email needs to be set bodyBytes, err := io.ReadAll(r.Body) if err != nil { - c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err) + c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.app_error", nil, "", http.StatusBadRequest).Wrap(err) return } // this value will not be empty when both emails (user admin and CWS customer) are not business email and - // we need to request a new email from the user via the request business email modal + // a new business email was provided via the request business email modal var startTrialRequest *model.StartCloudTrialRequest if err = json.Unmarshal(bodyBytes, &startTrialRequest); err != nil { - c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err) + c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.app_error", nil, "", http.StatusBadRequest).Wrap(err) return } @@ -199,20 +199,20 @@ func validateBusinessEmail(c *Context, w http.ResponseWriter, r *http.Request) { user, appErr := c.App.GetUser(c.AppContext.Session().UserId) if appErr != nil { - c.Err = model.NewAppError("Api4.validateBusinessEmail", "api.cloud.request_error", nil, "", http.StatusInternalServerError).Wrap(appErr) + c.Err = model.NewAppError("Api4.validateBusinessEmail", "api.cloud.request_error", nil, "", http.StatusForbidden).Wrap(appErr) return } bodyBytes, err := io.ReadAll(r.Body) if err != nil { - c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err) + c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.app_error", nil, "", http.StatusBadRequest).Wrap(err) return } var emailToValidate *model.ValidateBusinessEmailRequest err = json.Unmarshal(bodyBytes, &emailToValidate) if err != nil { - c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err) + c.Err = model.NewAppError("Api4.requestCloudTrial", "api.cloud.app_error", nil, "", http.StatusBadRequest).Wrap(err) return } @@ -244,14 +244,14 @@ func validateWorkspaceBusinessEmail(c *Context, w http.ResponseWriter, r *http.R user, userErr := c.App.GetUser(c.AppContext.Session().UserId) if userErr != nil { - c.Err = model.NewAppError("Api4.validateWorkspaceBusinessEmail", "api.cloud.request_error", nil, userErr.Error(), http.StatusInternalServerError) + c.Err = userErr return } // get the cloud customer email to validate if is a valid business email cloudCustomer, err := c.App.Cloud().GetCloudCustomer(user.Id) if err != nil { - c.Err = model.NewAppError("Api4.validateWorkspaceBusinessEmail", "api.cloud.request_error", nil, err.Error(), http.StatusInternalServerError) + c.Err = model.NewAppError("Api4.validateWorkspaceBusinessEmail", "api.cloud.request_error", nil, err.Error(), http.StatusBadRequest) return } emailErr := c.App.Cloud().ValidateBusinessEmail(user.Id, cloudCustomer.Email) diff --git a/api4/cloud_test.go b/api4/cloud_test.go index 999f1077e4..2092342c89 100644 --- a/api4/cloud_test.go +++ b/api4/cloud_test.go @@ -296,6 +296,20 @@ func Test_requestTrial(t *testing.T) { require.Equal(t, subscriptionChanged, subscription) require.Equal(t, http.StatusOK, r.StatusCode, "Status OK") }) + + t.Run("Empty body returns bad request", func(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + th.Client.Login(th.BasicUser.Email, th.BasicUser.Password) + + th.App.Srv().SetLicense(model.NewTestLicense("cloud")) + + r, err := th.SystemAdminClient.DoAPIPutBytes("/cloud/request-trial", nil) + require.Error(t, err) + closeBody(r) + require.Equal(t, http.StatusBadRequest, r.StatusCode, "Status Bad Request") + }) } func Test_validateBusinessEmail(t *testing.T) { @@ -373,6 +387,20 @@ func Test_validateBusinessEmail(t *testing.T) { require.NoError(t, err) require.Equal(t, http.StatusOK, res.StatusCode, "200") }) + + t.Run("Empty body returns bad request", func(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + th.Client.Login(th.BasicUser.Email, th.BasicUser.Password) + + th.App.Srv().SetLicense(model.NewTestLicense("cloud")) + + r, err := th.SystemAdminClient.DoAPIPostBytes("/cloud/validate-business-email", nil) + require.Error(t, err) + closeBody(r) + require.Equal(t, http.StatusBadRequest, r.StatusCode, "Status Bad Request") + }) } func Test_validateWorkspaceBusinessEmail(t *testing.T) { @@ -442,6 +470,39 @@ func Test_validateWorkspaceBusinessEmail(t *testing.T) { _, err := th.SystemAdminClient.ValidateWorkspaceBusinessEmail() require.NoError(t, err) }) + + t.Run("Error while grabbing the cloud customer returns bad request", func(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + th.Client.Login(th.BasicUser.Email, th.BasicUser.Password) + + th.App.Srv().SetLicense(model.NewTestLicense("cloud")) + + cloud := mocks.CloudInterface{} + + cloudCustomerInfo := model.CloudCustomerInfo{ + Email: "badrequest@gmail.com", + } + + // return an error while getting the cloud customer so we validate the forbidden error return + cloud.Mock.On("GetCloudCustomer", th.SystemAdminUser.Id).Return(nil, errors.New("error while gettings the cloud customer")) + + // required cloud mocks so the request doesn't fail + cloud.Mock.On("ValidateBusinessEmail", th.SystemAdminUser.Id, cloudCustomerInfo.Email).Return(errors.New("invalid email")) + cloud.Mock.On("ValidateBusinessEmail", th.SystemAdminUser.Id, th.SystemAdminUser.Email).Return(nil) + + cloudImpl := th.App.Srv().Cloud + defer func() { + th.App.Srv().Cloud = cloudImpl + }() + th.App.Srv().Cloud = &cloud + + r, err := th.SystemAdminClient.DoAPIPostBytes("/cloud/validate-workspace-business-email", nil) + require.Error(t, err) + closeBody(r) + require.Equal(t, http.StatusBadRequest, r.StatusCode, "Status Bad Request") + }) } func TestGetCloudProducts(t *testing.T) { diff --git a/model/client4.go b/model/client4.go index cfe0f9244e..b569a8fa86 100644 --- a/model/client4.go +++ b/model/client4.go @@ -8004,8 +8004,8 @@ func (c *Client4) ConfirmCustomerPayment(confirmRequest *ConfirmPaymentMethodReq return BuildResponse(r), nil } -func (c *Client4) RequestCloudTrial(email *StartCloudTrialRequest) (*Subscription, *Response, error) { - payload, err := json.Marshal(email) +func (c *Client4) RequestCloudTrial(cloudTrialRequest *StartCloudTrialRequest) (*Subscription, *Response, error) { + payload, err := json.Marshal(cloudTrialRequest) if err != nil { return nil, nil, NewAppError("RequestCloudTrial", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) } From 093c657e56902dc066700898a251964bd16e5ddc Mon Sep 17 00:00:00 2001 From: cyrilzhang-mm <112951043+cyrilzhang-mm@users.noreply.github.com> Date: Thu, 20 Oct 2022 14:37:24 -0400 Subject: [PATCH 43/47] [MM-45052] Add error checking in insights API (#21048) --- api4/insights.go | 60 +++++++++++++++++++++++++++++++++++-------- api4/insights_test.go | 24 +++++++++++++++++ app/channel_test.go | 8 +++--- app/reaction_test.go | 4 +-- i18n/en.json | 4 +++ model/insights.go | 18 +++++++++++++ 6 files changed, 102 insertions(+), 16 deletions(-) diff --git a/api4/insights.go b/api4/insights.go index fce03cfa04..9d74a57c2f 100644 --- a/api4/insights.go +++ b/api4/insights.go @@ -73,7 +73,11 @@ func getTopReactionsForTeamSince(c *Context, w http.ResponseWriter, r *http.Requ return } - startTime := model.StartOfDayForTimeRange(c.Params.TimeRange, user.GetTimezoneLocation()) + startTime, appErr := model.GetStartOfDayForTimeRange(c.Params.TimeRange, user.GetTimezoneLocation()) + if appErr != nil { + c.Err = appErr + return + } topReactionList, appErr := c.App.GetTopReactionsForTeamSince(c.Params.TeamId, c.AppContext.Session().UserId, &model.InsightsOpts{ StartUnixMilli: startTime.UnixMilli(), @@ -134,7 +138,11 @@ func getTopReactionsForUserSince(c *Context, w http.ResponseWriter, r *http.Requ return } - startTime := model.StartOfDayForTimeRange(c.Params.TimeRange, user.GetTimezoneLocation()) + startTime, appErr := model.GetStartOfDayForTimeRange(c.Params.TimeRange, user.GetTimezoneLocation()) + if appErr != nil { + c.Err = appErr + return + } topReactionList, appErr := c.App.GetTopReactionsForUserSince(c.AppContext.Session().UserId, c.Params.TeamId, &model.InsightsOpts{ StartUnixMilli: startTime.UnixMilli(), @@ -193,7 +201,11 @@ func getTopChannelsForTeamSince(c *Context, w http.ResponseWriter, r *http.Reque } loc := user.GetTimezoneLocation() - startTime := model.StartOfDayForTimeRange(c.Params.TimeRange, loc) + startTime, appErr := model.GetStartOfDayForTimeRange(c.Params.TimeRange, loc) + if appErr != nil { + c.Err = appErr + return + } topChannels, appErr := c.App.GetTopChannelsForTeamSince(c.AppContext, c.Params.TeamId, c.AppContext.Session().UserId, &model.InsightsOpts{ StartUnixMilli: startTime.UnixMilli(), @@ -261,7 +273,11 @@ func getTopChannelsForUserSince(c *Context, w http.ResponseWriter, r *http.Reque } loc := user.GetTimezoneLocation() - startTime := model.StartOfDayForTimeRange(c.Params.TimeRange, loc) + startTime, appErr := model.GetStartOfDayForTimeRange(c.Params.TimeRange, loc) + if appErr != nil { + c.Err = appErr + return + } topChannels, appErr := c.App.GetTopChannelsForUserSince(c.AppContext, c.AppContext.Session().UserId, c.Params.TeamId, &model.InsightsOpts{ StartUnixMilli: startTime.UnixMilli(), @@ -325,7 +341,11 @@ func getTopThreadsForTeamSince(c *Context, w http.ResponseWriter, r *http.Reques return } - startTime := model.StartOfDayForTimeRange(c.Params.TimeRange, user.GetTimezoneLocation()) + startTime, appErr := model.GetStartOfDayForTimeRange(c.Params.TimeRange, user.GetTimezoneLocation()) + if appErr != nil { + c.Err = appErr + return + } topThreads, appErr := c.App.GetTopThreadsForTeamSince(c.AppContext, c.Params.TeamId, c.AppContext.Session().UserId, &model.InsightsOpts{ StartUnixMilli: startTime.UnixMilli(), @@ -386,7 +406,11 @@ func getTopThreadsForUserSince(c *Context, w http.ResponseWriter, r *http.Reques } } - startTime := model.StartOfDayForTimeRange(c.Params.TimeRange, user.GetTimezoneLocation()) + startTime, appErr := model.GetStartOfDayForTimeRange(c.Params.TimeRange, user.GetTimezoneLocation()) + if appErr != nil { + c.Err = appErr + return + } topThreads, appErr := c.App.GetTopThreadsForUserSince(c.AppContext, c.Params.TeamId, c.AppContext.Session().UserId, &model.InsightsOpts{ StartUnixMilli: startTime.UnixMilli(), @@ -427,7 +451,11 @@ func getTopDMsForUserSince(c *Context, w http.ResponseWriter, r *http.Request) { return } - startTime := model.StartOfDayForTimeRange(c.Params.TimeRange, user.GetTimezoneLocation()) + startTime, appErr := model.GetStartOfDayForTimeRange(c.Params.TimeRange, user.GetTimezoneLocation()) + if appErr != nil { + c.Err = appErr + return + } topDMs, err := c.App.GetTopDMsForUserSince(user.Id, &model.InsightsOpts{ StartUnixMilli: startTime.UnixMilli(), @@ -487,7 +515,11 @@ func getTopInactiveChannelsForTeamSince(c *Context, w http.ResponseWriter, r *ht } loc := user.GetTimezoneLocation() - startTime := model.StartOfDayForTimeRange(c.Params.TimeRange, loc) + startTime, appErr := model.GetStartOfDayForTimeRange(c.Params.TimeRange, loc) + if appErr != nil { + c.Err = appErr + return + } topChannels, err := c.App.GetTopInactiveChannelsForTeamSince(c.AppContext, c.Params.TeamId, c.AppContext.Session().UserId, &model.InsightsOpts{ StartUnixMilli: startTime.UnixMilli(), @@ -548,7 +580,11 @@ func getTopInactiveChannelsForUserSince(c *Context, w http.ResponseWriter, r *ht } loc := user.GetTimezoneLocation() - startTime := model.StartOfDayForTimeRange(c.Params.TimeRange, loc) + startTime, appErr := model.GetStartOfDayForTimeRange(c.Params.TimeRange, loc) + if appErr != nil { + c.Err = appErr + return + } topChannels, err := c.App.GetTopInactiveChannelsForUserSince(c.AppContext, c.Params.TeamId, c.AppContext.Session().UserId, &model.InsightsOpts{ StartUnixMilli: startTime.UnixMilli(), @@ -622,7 +658,11 @@ func getNewTeamMembersSince(c *Context, w http.ResponseWriter, r *http.Request) return } loc := user.GetTimezoneLocation() - startTime := model.StartOfDayForTimeRange(c.Params.TimeRange, loc) + startTime, appErr := model.GetStartOfDayForTimeRange(c.Params.TimeRange, loc) + if appErr != nil { + c.Err = appErr + return + } ntms, count, err := c.App.GetNewTeamMembersSince(c.AppContext, c.Params.TeamId, &model.InsightsOpts{ StartUnixMilli: startTime.UnixMilli(), diff --git a/api4/insights_test.go b/api4/insights_test.go index 089bd26842..2443c1ea26 100644 --- a/api4/insights_test.go +++ b/api4/insights_test.go @@ -230,6 +230,12 @@ func TestGetTopReactionsForTeamSince(t *testing.T) { CheckNotFoundStatus(t, resp) }) + t.Run("get-top-reactions-for-team-since invalid time range", func(t *testing.T) { + _, resp, err := client.GetTopReactionsForTeamSince(teamId, "7_days", 0, 5) + require.Error(t, err) + CheckBadRequestStatus(t, resp) + }) + t.Run("get-top-reactions-for-team-since not a member of team", func(t *testing.T) { th.UnlinkUserFromTeam(th.BasicUser, th.BasicTeam) _, resp, err := client.GetTopReactionsForTeamSince(teamId, model.TimeRangeToday, 0, 5) @@ -417,6 +423,12 @@ func TestGetTopReactionsForUserSince(t *testing.T) { CheckNotFoundStatus(t, resp) }) + t.Run("get-top-reactions-for-user-since invalid time range", func(t *testing.T) { + _, resp, err := client.GetTopReactionsForUserSince(teamId, "7_days", 0, 5) + require.Error(t, err) + CheckBadRequestStatus(t, resp) + }) + t.Run("get-top-reactions-for-user-since not a member of team", func(t *testing.T) { th.UnlinkUserFromTeam(th.BasicUser, th.BasicTeam) _, resp, err := client.GetTopReactionsForUserSince(teamId, model.TimeRangeToday, 0, 5) @@ -515,6 +527,12 @@ func TestGetTopChannelsForTeamSince(t *testing.T) { CheckNotFoundStatus(t, resp) }) + t.Run("get-top-channels-for-team-since invalid time range", func(t *testing.T) { + _, resp, err := client.GetTopChannelsForTeamSince(teamId, "7_days", 0, 5) + assert.Error(t, err) + CheckBadRequestStatus(t, resp) + }) + t.Run("get-top-channels-for-team-since not a member of team", func(t *testing.T) { th.UnlinkUserFromTeam(th.BasicUser, th.BasicTeam) _, resp, err := client.GetTopChannelsForTeamSince(teamId, model.TimeRangeToday, 0, 5) @@ -592,6 +610,12 @@ func TestGetTopChannelsForUserSince(t *testing.T) { CheckNotFoundStatus(t, resp) }) + t.Run("get-top-channels-for-user-since invalid time range", func(t *testing.T) { + _, resp, err := client.GetTopChannelsForUserSince(teamId, "7_days", 0, 5) + assert.Error(t, err) + CheckBadRequestStatus(t, resp) + }) + t.Run("get-top-channels-for-user-since not a member of team", func(t *testing.T) { th.UnlinkUserFromTeam(th.BasicUser, th.BasicTeam) _, resp, err := client.GetTopChannelsForUserSince(teamId, model.TimeRangeToday, 0, 5) diff --git a/app/channel_test.go b/app/channel_test.go index 260b46d641..0361122027 100644 --- a/app/channel_test.go +++ b/app/channel_test.go @@ -2498,7 +2498,7 @@ func TestGetTopChannelsForTeamSince(t *testing.T) { {ID: channel5.Id, MessageCount: 2}, } - timeRange := model.StartOfDayForTimeRange(model.TimeRangeToday, time.Now().Location()) + timeRange, _ := model.GetStartOfDayForTimeRange(model.TimeRangeToday, time.Now().Location()) t.Run("get-top-channels-for-team-since", func(t *testing.T) { topChannels, err := th.App.GetTopChannelsForTeamSince(th.Context, th.BasicChannel.TeamId, th.BasicUser.Id, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 0, PerPage: 5}) @@ -2576,7 +2576,7 @@ func TestGetTopChannelsForUserSince(t *testing.T) { {ID: channel5.Id, MessageCount: 2}, } - timeRange := model.StartOfDayForTimeRange(model.TimeRangeToday, time.Now().Location()) + timeRange, _ := model.GetStartOfDayForTimeRange(model.TimeRangeToday, time.Now().Location()) t.Run("get-top-channels-for-user-since", func(t *testing.T) { topChannels, err := th.App.GetTopChannelsForUserSince(th.Context, th.BasicUser.Id, "", &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 0, PerPage: 5}) @@ -2787,7 +2787,7 @@ func TestGetTopInactiveChannelsForTeamSince(t *testing.T) { {ID: channel2.Id, MessageCount: 6}, } - timeRange := model.StartOfDayForTimeRange(model.TimeRangeToday, time.Now().Location()) + timeRange, _ := model.GetStartOfDayForTimeRange(model.TimeRangeToday, time.Now().Location()) t.Run("get-top-channels-for-team-since", func(t *testing.T) { topChannels, err := th.App.GetTopInactiveChannelsForTeamSince(th.Context, th.BasicChannel.TeamId, th.BasicUser.Id, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 0, PerPage: 5}) @@ -2885,7 +2885,7 @@ func TestGetTopInactiveChannelsForUserSince(t *testing.T) { {ID: channel2.Id, MessageCount: 6}, } - timeRange := model.StartOfDayForTimeRange(model.TimeRangeToday, time.Now().Location()) + timeRange, _ := model.GetStartOfDayForTimeRange(model.TimeRangeToday, time.Now().Location()) t.Run("get-top-channels-for-user-since", func(t *testing.T) { topChannels, err := th.App.GetTopInactiveChannelsForUserSince(th.Context, th.BasicChannel.TeamId, th.BasicUser.Id, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 0, PerPage: 4}) diff --git a/app/reaction_test.go b/app/reaction_test.go index d9670e4eeb..33d747136b 100644 --- a/app/reaction_test.go +++ b/app/reaction_test.go @@ -230,7 +230,7 @@ func TestGetTopReactionsForTeamSince(t *testing.T) { expectedTopReactions[3] = &model.TopReaction{EmojiName: "sad", Count: int64(3)} expectedTopReactions[4] = &model.TopReaction{EmojiName: "happy", Count: int64(2)} - timeRange := model.StartOfDayForTimeRange(model.TimeRangeToday, time.Now().Location()) + timeRange, _ := model.GetStartOfDayForTimeRange(model.TimeRangeToday, time.Now().Location()) t.Run("get-top-reactions-for-team-since", func(t *testing.T) { topReactions, err := th.App.GetTopReactionsForTeamSince(teamId, userId, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 0, PerPage: 5}) @@ -401,7 +401,7 @@ func TestGetTopReactionsForUserSince(t *testing.T) { expectedTopReactions[3] = &model.TopReaction{EmojiName: "heart", Count: int64(3)} expectedTopReactions[4] = &model.TopReaction{EmojiName: "blush", Count: int64(2)} - timeRange := model.StartOfDayForTimeRange(model.TimeRangeToday, time.Now().Location()) + timeRange, _ := model.GetStartOfDayForTimeRange(model.TimeRangeToday, time.Now().Location()) t.Run("get-top-reactions-for-user-since", func(t *testing.T) { topReactions, err := th.App.GetTopReactionsForUserSince(userId, teamId, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 0, PerPage: 5}) diff --git a/i18n/en.json b/i18n/en.json index f5db7894ed..710054bf1a 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -8739,6 +8739,10 @@ "id": "model.incoming_hook.username.app_error", "translation": "Invalid username." }, + { + "id": "model.insights.get_start_of_day_for_time_range.time_range.app_error", + "translation": "Invalid time range." + }, { "id": "model.job.is_valid.create_at.app_error", "translation": "Create at must be a valid time." diff --git a/model/insights.go b/model/insights.go index 438ca9402c..d66d48c283 100644 --- a/model/insights.go +++ b/model/insights.go @@ -4,6 +4,7 @@ package model import ( + "net/http" "time" ) @@ -260,6 +261,23 @@ func StartOfDayForTimeRange(timeRange string, location *time.Location) *time.Tim return &resultTime } +// GetStartOfDayForTimeRange gets the unix start time in milliseconds from the given time range. +// Time range can be one of: "today", "7_day", or "28_day". +func GetStartOfDayForTimeRange(timeRange string, location *time.Location) (*time.Time, *AppError) { + now := time.Now().In(location) + resultTime := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, location) + switch timeRange { + case TimeRangeToday: + case TimeRange7Day: + resultTime = resultTime.Add(time.Hour * time.Duration(-144)) + case TimeRange28Day: + resultTime = resultTime.Add(time.Hour * time.Duration(-648)) + default: + return nil, NewAppError("GetStartOfDayForTimeRange", "model.insights.get_start_of_day_for_time_range.time_range.app_error", nil, "", http.StatusBadRequest) + } + return &resultTime, nil +} + // GetTopReactionListWithPagination adds a rank to each item in the given list of TopReaction and checks if there is // another page that can be fetched based on the given limit and offset. The given list of TopReaction is assumed to be // sorted by Count. Returns a TopReactionList. From b30e511ffe84c559215950f8af4493ea8c449e3f Mon Sep 17 00:00:00 2001 From: Julien Tant <785518+JulienTant@users.noreply.github.com> Date: Thu, 20 Oct 2022 12:06:34 -0700 Subject: [PATCH 44/47] Deprecate ioutil (#21457) --- app/platform/helper_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/platform/helper_test.go b/app/platform/helper_test.go index b8d2da4594..9402f8def3 100644 --- a/app/platform/helper_test.go +++ b/app/platform/helper_test.go @@ -4,7 +4,7 @@ package platform import ( - "io/ioutil" + "os" "path/filepath" "sync" "testing" @@ -127,7 +127,7 @@ func SetupWithCluster(tb testing.TB, cluster einterfaces.ClusterInterface) *Test } func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer bool, tb testing.TB, options ...Option) *TestHelper { - tempWorkspace, err := ioutil.TempDir("", "apptest") + tempWorkspace, err := os.MkdirTemp("", "apptest") if err != nil { panic(err) } From 2ce3e81490251c71a0e7adccb5ec5cfd3e5d2472 Mon Sep 17 00:00:00 2001 From: Ibrahim Serdar Acikgoz Date: Fri, 21 Oct 2022 12:24:36 +0300 Subject: [PATCH 45/47] move file backend to the platform service (#21365) --- app/notification_push_test.go | 9 ++++---- app/options.go | 2 +- app/platform/options.go | 8 +++++++ app/platform/service.go | 19 ++++++++++++++++ app/server.go | 15 +++--------- app/server_test.go | 43 +++++++++++++++++++---------------- 6 files changed, 58 insertions(+), 38 deletions(-) diff --git a/app/notification_push_test.go b/app/notification_push_test.go index 6dd1b5a2ff..73bf38e72b 100644 --- a/app/notification_push_test.go +++ b/app/notification_push_test.go @@ -1440,20 +1440,19 @@ func TestPushNotificationRace(t *testing.T) { Return(&model.Preference{Value: "test"}, nil) mockStore.On("Preference").Return(&mockPreferenceStore) s := &Server{ - products: make(map[string]Product), - Router: mux.NewRouter(), - filestore: &fmocks.FileBackend{}, + products: make(map[string]Product), + Router: mux.NewRouter(), } var err error s.platform, err = platform.New(platform.ServiceConfig{ ConfigStore: memoryStore, - }) + }, platform.SetFileStore(&fmocks.FileBackend{})) s.SetStore(mockStore) require.NoError(t, err) serviceMap := map[ServiceKey]any{ ConfigKey: s.platform, LicenseKey: &licenseWrapper{s}, - FilestoreKey: s.filestore, + FilestoreKey: s.FileBackend(), } ch, err := NewChannels(s, serviceMap) require.NoError(t, err) diff --git a/app/options.go b/app/options.go index 15725bf4c3..19133c42fc 100644 --- a/app/options.go +++ b/app/options.go @@ -54,7 +54,7 @@ func ConfigStore(configStore *config.Store) Option { func SetFileStore(filestore filestore.FileBackend) Option { return func(s *Server) error { - s.filestore = filestore + s.platformOptions = append(s.platformOptions, platform.SetFileStore(filestore)) return nil } } diff --git a/app/platform/options.go b/app/platform/options.go index 072914e104..65471b63f6 100644 --- a/app/platform/options.go +++ b/app/platform/options.go @@ -11,6 +11,7 @@ import ( "github.com/mattermost/mattermost-server/v6/config" "github.com/mattermost/mattermost-server/v6/einterfaces" "github.com/mattermost/mattermost-server/v6/model" + "github.com/mattermost/mattermost-server/v6/shared/filestore" "github.com/mattermost/mattermost-server/v6/shared/mlog" "github.com/mattermost/mattermost-server/v6/store" "github.com/mattermost/mattermost-server/v6/store/localcachelayer" @@ -74,6 +75,13 @@ func Config(dsn string, readOnly bool, configDefaults *model.Config) Option { } } +func SetFileStore(filestore filestore.FileBackend) Option { + return func(ps *PlatformService) error { + ps.filestore = filestore + return nil + } +} + // ConfigStore applies the given config store, typically to replace the traditional sources with a memory store for testing. func ConfigStore(configStore *config.Store) Option { return func(ps *PlatformService) error { diff --git a/app/platform/service.go b/app/platform/service.go index d2104fb2bb..6620b7f487 100644 --- a/app/platform/service.go +++ b/app/platform/service.go @@ -20,6 +20,7 @@ import ( "github.com/mattermost/mattermost-server/v6/services/cache" "github.com/mattermost/mattermost-server/v6/services/searchengine" "github.com/mattermost/mattermost-server/v6/services/searchengine/bleveengine" + "github.com/mattermost/mattermost-server/v6/shared/filestore" "github.com/mattermost/mattermost-server/v6/shared/mlog" "github.com/mattermost/mattermost-server/v6/store" "github.com/mattermost/mattermost-server/v6/store/localcachelayer" @@ -41,6 +42,8 @@ type PlatformService struct { configStore *config.Store + filestore filestore.FileBackend + cacheProvider cache.Provider statusCache cache.Cache sessionCache cache.Cache @@ -213,6 +216,18 @@ func New(sc ServiceConfig, options ...Option) (*PlatformService, error) { } } + license := ps.License() + // Step 3: Initialize filestore + if ps.filestore == nil { + insecure := ps.Config().ServiceSettings.EnableInsecureOutgoingConnections + backend, err2 := filestore.NewFileBackend(ps.Config().FileSettings.ToFileBackendSettings(license != nil && *license.Features.Compliance, insecure != nil && *insecure)) + if err2 != nil { + return nil, fmt.Errorf("failed to initialize filebackend: %w", err2) + } + + ps.filestore = backend + } + var err error ps.Store, err = ps.newStore() if err != nil { @@ -431,3 +446,7 @@ func (ps *PlatformService) GetPluginStatuses() (model.PluginStatuses, *model.App return pluginStatuses, nil } + +func (ps *PlatformService) FileBackend() filestore.FileBackend { + return ps.filestore +} diff --git a/app/server.go b/app/server.go index a4688c8b43..0216fa240f 100644 --- a/app/server.go +++ b/app/server.go @@ -137,7 +137,6 @@ type Server struct { openGraphDataCache cache.Cache clusterLeaderListenerId string loggerLicenseListenerId string - filestore filestore.FileBackend platform *platform.PlatformService platformOptions []platform.Option @@ -239,15 +238,6 @@ func NewServer(options ...Option) (*Server, error) { s.LoadLicense() } - license := s.License() - insecure := s.platform.Config().ServiceSettings.EnableInsecureOutgoingConnections - // Step 3: Initialize filestore - backend, err := filestore.NewFileBackend(s.platform.Config().FileSettings.ToFileBackendSettings(license != nil && *license.Features.Compliance, insecure != nil && *insecure)) - if err != nil { - return nil, errors.Wrap(err, "failed to initialize filebackend") - } - s.filestore = backend - s.licenseWrapper = &licenseWrapper{ srv: s, } @@ -272,7 +262,7 @@ func NewServer(options ...Option) (*Server, error) { ChannelKey: &channelsWrapper{srv: s}, ConfigKey: s.platform, LicenseKey: s.licenseWrapper, - FilestoreKey: s.filestore, + FilestoreKey: s.platform.FileBackend(), FileInfoStoreKey: &fileInfoWrapper{srv: s}, ClusterKey: s.platform, UserKey: New(ServerConnector(s.Channels())), @@ -439,6 +429,7 @@ func NewServer(options ...Option) (*Server, error) { mlog.Info("Printing current working", mlog.String("directory", pwd)) mlog.Info("Loaded config", mlog.String("source", s.platform.DescribeConfig())) + license := s.License() allowAdvancedLogging := license != nil && *license.Features.AdvancedLogging if s.Audit == nil { @@ -1425,7 +1416,7 @@ func (s *Server) SendRemoveExpiredLicenseEmail(email string, renewalLink, locale } func (s *Server) FileBackend() filestore.FileBackend { - return s.filestore + return s.platform.FileBackend() } func (s *Server) TotalWebsocketConnections() int { diff --git a/app/server_test.go b/app/server_test.go index f8135d0461..64b85ce938 100644 --- a/app/server_test.go +++ b/app/server_test.go @@ -90,28 +90,29 @@ func TestStartServerNoS3Bucket(t *testing.T) { } s3Endpoint := fmt.Sprintf("%s:%s", s3Host, s3Port) + configStore, _ := config.NewFileStore("config.json", true) + store, _ := config.NewStoreFromBacking(configStore, nil, false) + + cfg := store.Get() + cfg.FileSettings = model.FileSettings{ + DriverName: model.NewString(model.ImageDriverS3), + AmazonS3AccessKeyId: model.NewString(model.MinioAccessKey), + AmazonS3SecretAccessKey: model.NewString(model.MinioSecretKey), + AmazonS3Bucket: model.NewString("nosuchbucket"), + AmazonS3Endpoint: model.NewString(s3Endpoint), + AmazonS3Region: model.NewString(""), + AmazonS3PathPrefix: model.NewString(""), + AmazonS3SSL: model.NewBool(false), + } + *cfg.ServiceSettings.ListenAddress = ":0" + _, _, err := store.Set(cfg) + require.NoError(t, err) s, err := NewServer(func(server *Server) error { - configStore, _ := config.NewFileStore("config.json", true) - store, _ := config.NewStoreFromBacking(configStore, nil, false) - var err error - server.platform, err = platform.New(platform.ServiceConfig{ - ConfigStore: store, - }) - require.NoError(t, err) - server.platform.UpdateConfig(func(cfg *model.Config) { - cfg.FileSettings = model.FileSettings{ - DriverName: model.NewString(model.ImageDriverS3), - AmazonS3AccessKeyId: model.NewString(model.MinioAccessKey), - AmazonS3SecretAccessKey: model.NewString(model.MinioSecretKey), - AmazonS3Bucket: model.NewString("nosuchbucket"), - AmazonS3Endpoint: model.NewString(s3Endpoint), - AmazonS3Region: model.NewString(""), - AmazonS3PathPrefix: model.NewString(""), - AmazonS3SSL: model.NewBool(false), - } - *cfg.ServiceSettings.ListenAddress = ":0" - }) + var err2 error + server.platform, err2 = platform.New(platform.ServiceConfig{}, platform.ConfigStore(store)) + require.NoError(t, err2) + return nil }) require.NoError(t, err) @@ -120,6 +121,8 @@ func TestStartServerNoS3Bucket(t *testing.T) { defer s.Shutdown() // ensure that a new bucket was created + require.IsType(t, &filestore.S3FileBackend{}, s.FileBackend()) + err = s.FileBackend().(*filestore.S3FileBackend).TestConnection() require.NoError(t, err) } From 0dc8b7b9dd531a0e06a665d4264cbb5beaaa8c5c Mon Sep 17 00:00:00 2001 From: Scott Bishel Date: Fri, 21 Oct 2022 09:54:17 -0600 Subject: [PATCH 46/47] update prepackage boards to v7.4.3 (#21481) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 43ccf306a5..85b7c222ef 100644 --- a/Makefile +++ b/Makefile @@ -160,7 +160,7 @@ PLUGIN_PACKAGES += mattermost-plugin-jira-v2.4.0 PLUGIN_PACKAGES += mattermost-plugin-nps-v1.2.0 PLUGIN_PACKAGES += mattermost-plugin-welcomebot-v1.2.0 PLUGIN_PACKAGES += mattermost-plugin-zoom-v1.6.0 -PLUGIN_PACKAGES += focalboard-v7.4.2 +PLUGIN_PACKAGES += focalboard-v7.4.3 PLUGIN_PACKAGES += mattermost-plugin-apps-v1.1.0 # Prepares the enterprise build if exists. The IGNORE stuff is a hack to get the Makefile to execute the commands outside a target From 6ac0faf99ea6de16f597fc4cf1a1b341826d5784 Mon Sep 17 00:00:00 2001 From: Scott Bishel Date: Fri, 21 Oct 2022 10:10:45 -0600 Subject: [PATCH 47/47] Display board statistics (#21433) * prepackage boards v7.4.2 * allow plugin to call HasPermissionTo Co-authored-by: Mattermod --- app/permissions.go | 4 ++++ product/api.go | 1 + 2 files changed, 5 insertions(+) diff --git a/app/permissions.go b/app/permissions.go index a992d0a952..2b34b85bb3 100644 --- a/app/permissions.go +++ b/app/permissions.go @@ -29,6 +29,10 @@ type permissionsServiceWrapper struct { app AppIface } +func (s *permissionsServiceWrapper) HasPermissionTo(userID string, permission *model.Permission) bool { + return s.app.HasPermissionTo(userID, permission) +} + func (s *permissionsServiceWrapper) HasPermissionToTeam(userID string, teamID string, permission *model.Permission) bool { return s.app.HasPermissionToTeam(userID, teamID, permission) } diff --git a/product/api.go b/product/api.go index 4b4ec8a93c..88eb9744e4 100644 --- a/product/api.go +++ b/product/api.go @@ -39,6 +39,7 @@ type PostService interface { // // The service shall be registered via app.PermissionKey service key. type PermissionService interface { + HasPermissionTo(userID string, permission *model.Permission) bool HasPermissionToTeam(userID, teamID string, permission *model.Permission) bool HasPermissionToChannel(askingUserID string, channelID string, permission *model.Permission) bool }