From 05657a3979d89c2031262bd420da6466511e0476 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Wed, 6 Oct 2021 06:38:47 -0300 Subject: [PATCH 001/137] [FIX] LDAP not stoping after wrong password (#23382) --- server/lib/ldap/Manager.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/server/lib/ldap/Manager.ts b/server/lib/ldap/Manager.ts index 184d01bb9f519..7702d1261ba2c 100644 --- a/server/lib/ldap/Manager.ts +++ b/server/lib/ldap/Manager.ts @@ -169,6 +169,7 @@ export class LDAPManager { const [ldapUser] = users; if (!await ldap.authenticate(ldapUser.dn, password)) { logger.debug(`Wrong password for ${ escapedUsername }`); + throw new Error('Invalid user or wrong password'); } if (settings.get('LDAP_Find_User_After_Login')) { From 9e3ba7cc9eb4ebc10e2503021127134fa6c086c7 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 6 Oct 2021 06:37:50 -0300 Subject: [PATCH 002/137] [FIX] MongoDB deprecation link (#23381) --- server/startup/serverRunning.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/startup/serverRunning.js b/server/startup/serverRunning.js index 85024736ab519..63d250bf747f3 100644 --- a/server/startup/serverRunning.js +++ b/server/startup/serverRunning.js @@ -78,7 +78,7 @@ Meteor.startup(function() { const id = `mongodbDeprecation_${ mongoVersion.replace(/[^0-9]/g, '_') }`; const title = 'MongoDB_Deprecated'; const text = 'MongoDB_version_s_is_deprecated_please_upgrade_your_installation'; - const link = 'https://rocket.chat/docs/installation'; + const link = 'https://go.rocket.chat/i/mongodb-deprecated'; if (!Users.bannerExistsById(id)) { sendMessagesToAdmins({ From 132814ac8f697f2c19f7cc4166a2647c145ed827 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 5 Oct 2021 18:57:03 -0300 Subject: [PATCH 003/137] [FIX] resumeToken not working (#23379) --- client/startup/loginViaQuery.ts | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/client/startup/loginViaQuery.ts b/client/startup/loginViaQuery.ts index 485dda8fb2a8a..5c7fcbf8e7d5a 100644 --- a/client/startup/loginViaQuery.ts +++ b/client/startup/loginViaQuery.ts @@ -1,17 +1,20 @@ import { FlowRouter } from 'meteor/kadira:flow-router'; import { Meteor } from 'meteor/meteor'; +import { Tracker } from 'meteor/tracker'; Meteor.startup(() => { - const resumeToken = FlowRouter.getQueryParam('resumeToken'); - if (!resumeToken) { - return; - } - - Meteor.loginWithToken(resumeToken, () => { - if (FlowRouter.getRouteName()) { - FlowRouter.setQueryParams({ resumeToken: null, userId: null }); + Tracker.afterFlush(() => { + const resumeToken = FlowRouter.getQueryParam('resumeToken'); + if (!resumeToken) { return; } - FlowRouter.go('/home'); + + Meteor.loginWithToken(resumeToken, () => { + if (FlowRouter.getRouteName()) { + FlowRouter.setQueryParams({ resumeToken: null, userId: null }); + return; + } + FlowRouter.go('/home'); + }); }); }); From 24ab7bbbe4f9bfc602c94927a98d842e32fc74be Mon Sep 17 00:00:00 2001 From: Leonardo Ostjen Couto Date: Tue, 5 Oct 2021 18:18:00 -0300 Subject: [PATCH 004/137] [FIX] Unwanted toastr error message when deleting user (#23372) * check if user exists when accessing canViewAllInfo * Explicitly return null Co-authored-by: Tasso Evangelista --- app/lib/server/functions/getFullUserData.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/lib/server/functions/getFullUserData.js b/app/lib/server/functions/getFullUserData.js index 63c17bb7d77d0..1a33ac4743160 100644 --- a/app/lib/server/functions/getFullUserData.js +++ b/app/lib/server/functions/getFullUserData.js @@ -89,6 +89,10 @@ export function getFullUserDataByIdOrUsername({ userId, filterId, filterUsername fields, }; const user = Users.findOneByIdOrUsername(filterId || filterUsername, options); + if (!user) { + return null; + } + user.canViewAllInfo = canViewAllInfo; return myself ? user : removePasswordInfo(user); From 9664fde11b8549352a72fcba2396ab00de0709e1 Mon Sep 17 00:00:00 2001 From: wolbernd Date: Tue, 5 Oct 2021 22:41:22 +0200 Subject: [PATCH 005/137] [FIX] BigBlueButton integration error due to missing file import (#23366) --- app/bigbluebutton/server/bigbluebutton-api.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/bigbluebutton/server/bigbluebutton-api.js b/app/bigbluebutton/server/bigbluebutton-api.js index f1c8c471027f1..8cb3f4d447c46 100644 --- a/app/bigbluebutton/server/bigbluebutton-api.js +++ b/app/bigbluebutton/server/bigbluebutton-api.js @@ -1,5 +1,6 @@ /* eslint-disable */ import crypto from 'crypto'; +import { SystemLogger } from '../../../server/lib/logger/system'; var BigBlueButtonApi, filterCustomParameters, include, noChecksumMethods, __indexOf = [].indexOf || function (item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; From 092a6e7a0cf4cc0ea8ef82a4699cf8da4dcac87c Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Tue, 5 Oct 2021 16:56:18 -0300 Subject: [PATCH 006/137] Chore: Update Apps-Engine version (#23375) --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index b603b8be6b3b3..ade23032cf4f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5282,9 +5282,9 @@ } }, "@rocket.chat/apps-engine": { - "version": "1.28.0-alpha.5428", - "resolved": "https://registry.npmjs.org/@rocket.chat/apps-engine/-/apps-engine-1.28.0-alpha.5428.tgz", - "integrity": "sha512-M2i74yj3fvOw60FssXrF+dSQ5F9U/LOa865UhJH8ijcY1lTbqku1v7gXEi4GaTZ9HSS7bV47YECCj5qQ1ENgcw==", + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@rocket.chat/apps-engine/-/apps-engine-1.28.0.tgz", + "integrity": "sha512-ALrlGHVO1IhgAQ/rdZVS9nuPHwSE2VtPy3e2xbLzhMloZSpcG/ht9PPrDqIX6ZHHwiDxLbBd2/yT8vt86lXZeg==", "requires": { "adm-zip": "^0.4.9", "cryptiles": "^4.1.3", diff --git a/package.json b/package.json index 4bfbef86972e5..69405a6e4eb56 100644 --- a/package.json +++ b/package.json @@ -160,7 +160,7 @@ "@nivo/heatmap": "0.73.0", "@nivo/line": "0.62.0", "@nivo/pie": "0.73.0", - "@rocket.chat/apps-engine": "^1.28.0-alpha.5428", + "@rocket.chat/apps-engine": "1.28.0", "@rocket.chat/css-in-js": "^0.6.3-dev.322", "@rocket.chat/emitter": "^0.6.3-dev.322", "@rocket.chat/fuselage": "^0.6.3-dev.326", From e76cbb68a24bd2748a9e6db0112c83d3631263e3 Mon Sep 17 00:00:00 2001 From: Leonardo Ostjen Couto Date: Tue, 5 Oct 2021 16:12:03 -0300 Subject: [PATCH 007/137] imported migration v240 (#23374) --- server/startup/migrations/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/server/startup/migrations/index.ts b/server/startup/migrations/index.ts index d16b2f2724626..d86bab89330cd 100644 --- a/server/startup/migrations/index.ts +++ b/server/startup/migrations/index.ts @@ -63,4 +63,5 @@ import './v236'; import './v237'; import './v238'; import './v239'; +import './v240'; import './xrun'; From f79a2a33d0d03dd97a7a0aeea69a27e3126fc1b0 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Wed, 6 Oct 2021 13:57:37 -0300 Subject: [PATCH 008/137] [FIX] Users' `roles` and `type` being reset to default on LDAP DataSync (#23378) Co-authored-by: Diego Sampaio --- server/lib/ldap/Manager.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/server/lib/ldap/Manager.ts b/server/lib/ldap/Manager.ts index 7702d1261ba2c..da0531ea50545 100644 --- a/server/lib/ldap/Manager.ts +++ b/server/lib/ldap/Manager.ts @@ -250,6 +250,18 @@ export class LDAPManager { logger.debug({ msg: 'Syncing user data', ldapUser: _.omit(ldapUser, '_raw'), user: { ...existingUser && { email: existingUser.emails, _id: existingUser._id } } }); const userData = this.mapUserData(ldapUser, usedUsername); + + // make sure to persist existing user data when passing to sync/convert + // TODO this is only needed because ImporterDataConverter assigns a default role and type if nothing is set. we might need to figure out a better way and stop doing that there + if (existingUser) { + if (!userData.roles && existingUser.roles) { + userData.roles = existingUser.roles; + } + if (!userData.type && existingUser.type) { + userData.type = existingUser.type as IImportUser['type']; + } + } + const options = this.getConverterOptions(); LDAPDataConverter.convertSingleUser(userData, options); From 2716d4a003ad6069209a0439342cebc891ad8d3f Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 6 Oct 2021 13:59:02 -0300 Subject: [PATCH 009/137] Bump version to 4.0.1 --- .docker/Dockerfile.rhel | 2 +- .github/history.json | 93 +- .snapcraft/resources/prepareRocketChat | 2 +- .snapcraft/snap/snapcraft.yaml | 2 +- HISTORY.md | 2079 +++++++++++++----------- app/utils/rocketchat.info | 2 +- package-lock.json | 2 +- package.json | 2 +- 8 files changed, 1231 insertions(+), 953 deletions(-) diff --git a/.docker/Dockerfile.rhel b/.docker/Dockerfile.rhel index b7189d5d47f2a..4ced28448f5ee 100644 --- a/.docker/Dockerfile.rhel +++ b/.docker/Dockerfile.rhel @@ -1,6 +1,6 @@ FROM registry.access.redhat.com/ubi8/nodejs-12 -ENV RC_VERSION 4.0.0 +ENV RC_VERSION 4.0.1 MAINTAINER buildmaster@rocket.chat diff --git a/.github/history.json b/.github/history.json index 92513a4960e69..47437eee92fda 100644 --- a/.github/history.json +++ b/.github/history.json @@ -66042,6 +66042,97 @@ "5.0" ], "pull_requests": [] + }, + "4.0.1": { + "node_version": "12.22.1", + "npm_version": "6.14.1", + "apps_engine_version": "1.28.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "23378", + "title": "[FIX] Users' `roles` and `type` being reset to default on LDAP DataSync", + "userLogin": "matheusbsilva137", + "milestone": "4.0.1", + "contributors": [ + "matheusbsilva137", + "sampaiodiego" + ] + }, + { + "pr": "23374", + "title": "[FIX] imported migration v240", + "userLogin": "ostjen", + "milestone": "4.0.1", + "contributors": [ + "ostjen" + ] + }, + { + "pr": "23375", + "title": "Chore: Update Apps-Engine version", + "userLogin": "d-gubert", + "milestone": "4.0.1", + "contributors": [ + "d-gubert" + ] + }, + { + "pr": "23366", + "title": "[FIX] BigBlueButton integration error due to missing file import", + "userLogin": "wolbernd", + "description": "Fixes BigBlueButton integration", + "milestone": "4.0.1", + "contributors": [ + "wolbernd", + "web-flow" + ] + }, + { + "pr": "23372", + "title": "[FIX] unwanted toastr error message when deleting user", + "userLogin": "ostjen", + "milestone": "4.0.1", + "contributors": [ + "ostjen", + "tassoevan", + "web-flow" + ] + }, + { + "pr": "23379", + "title": "[FIX] resumeToken not working", + "userLogin": "sampaiodiego", + "milestone": "4.0.1", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "23381", + "title": "[FIX] MongoDB deprecation link", + "userLogin": "sampaiodiego", + "milestone": "4.0.1", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "23382", + "title": "[FIX] LDAP not stoping after wrong password", + "userLogin": "rodrigok", + "milestone": "4.0.1", + "contributors": [ + "rodrigok" + ] + } + ] } } -} \ No newline at end of file +} diff --git a/.snapcraft/resources/prepareRocketChat b/.snapcraft/resources/prepareRocketChat index 1a2b8edf3b51d..18f24421facab 100755 --- a/.snapcraft/resources/prepareRocketChat +++ b/.snapcraft/resources/prepareRocketChat @@ -1,6 +1,6 @@ #!/bin/bash -curl -SLf "https://releases.rocket.chat/4.0.0/download/" -o rocket.chat.tgz +curl -SLf "https://releases.rocket.chat/4.0.1/download/" -o rocket.chat.tgz tar xf rocket.chat.tgz --strip 1 diff --git a/.snapcraft/snap/snapcraft.yaml b/.snapcraft/snap/snapcraft.yaml index 38ea7c2dddd52..f7948b8e2cfa5 100644 --- a/.snapcraft/snap/snapcraft.yaml +++ b/.snapcraft/snap/snapcraft.yaml @@ -7,7 +7,7 @@ # 5. `snapcraft snap` name: rocketchat-server -version: 4.0.0 +version: 4.0.1 summary: Rocket.Chat server description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ confinement: strict diff --git a/HISTORY.md b/HISTORY.md index 383262cae967b..8327c691dd69f 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,4 +1,53 @@ +# 4.0.1 +`2021-10-06 · 7 🐛 · 1 🔍 · 7 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.22.1` +- NPM: `6.14.1` +- MongoDB: `3.6, 4.0, 4.2, 4.4, 5.0` +- Apps-Engine: `1.28.0` + +### 🐛 Bug fixes + + +- BigBlueButton integration error due to missing file import ([#23366](https://github.com/RocketChat/Rocket.Chat/pull/23366) by [@wolbernd](https://github.com/wolbernd)) + + Fixes BigBlueButton integration + +- imported migration v240 ([#23374](https://github.com/RocketChat/Rocket.Chat/pull/23374)) + +- LDAP not stoping after wrong password ([#23382](https://github.com/RocketChat/Rocket.Chat/pull/23382)) + +- MongoDB deprecation link ([#23381](https://github.com/RocketChat/Rocket.Chat/pull/23381)) + +- resumeToken not working ([#23379](https://github.com/RocketChat/Rocket.Chat/pull/23379)) + +- unwanted toastr error message when deleting user ([#23372](https://github.com/RocketChat/Rocket.Chat/pull/23372)) + +- Users' `roles` and `type` being reset to default on LDAP DataSync ([#23378](https://github.com/RocketChat/Rocket.Chat/pull/23378)) + +
+🔍 Minor changes + + +- Chore: Update Apps-Engine version ([#23375](https://github.com/RocketChat/Rocket.Chat/pull/23375)) + +
+ +### 👩‍💻👨‍💻 Contributors 😍 + +- [@wolbernd](https://github.com/wolbernd) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@d-gubert](https://github.com/d-gubert) +- [@matheusbsilva137](https://github.com/matheusbsilva137) +- [@ostjen](https://github.com/ostjen) +- [@rodrigok](https://github.com/rodrigok) +- [@sampaiodiego](https://github.com/sampaiodiego) +- [@tassoevan](https://github.com/tassoevan) + # 4.0.0 `2021-10-01 · 15 ️️️⚠️ · 4 🎉 · 11 🚀 · 24 🐛 · 67 🔍 · 26 👩‍💻👨‍💻` @@ -13,18 +62,20 @@ - **ENTERPRISE:** "Download CSV" button doesn't work in the Engagement Dashboard's Active Users section ([#23013](https://github.com/RocketChat/Rocket.Chat/pull/23013)) - - Fix "Download CSV" button in the Engagement Dashboard's Active Users section; - - Add column headers to the CSV file downloaded from the Engagement Dashboard's Active Users section; + - Fix "Download CSV" button in the Engagement Dashboard's Active Users section; + + - Add column headers to the CSV file downloaded from the Engagement Dashboard's Active Users section; + - Split the data in multiple CSV files. - **ENTERPRISE:** CSV file downloaded in the Engagement Dashboard's New Users section contains undefined data ([#23014](https://github.com/RocketChat/Rocket.Chat/pull/23014)) - - Fix CSV file downloaded in the Engagement Dashboard's New Users section; + - Fix CSV file downloaded in the Engagement Dashboard's New Users section; - Add column headers to the CSV file downloaded from the Engagement Dashboard's New Users section. - **ENTERPRISE:** Missing headers in CSV files downloaded from the Engagement Dashboard ([#23223](https://github.com/RocketChat/Rocket.Chat/pull/23223)) - - Add headers to all CSV files downloaded from the "Messages" and "Channels" tabs from the Engagement Dashboard; + - Add headers to all CSV files downloaded from the "Messages" and "Channels" tabs from the Engagement Dashboard; - Add headers to the CSV file downloaded from the "Users by time of day" section (in the "Users" tab). - LDAP Refactoring ([#23171](https://github.com/RocketChat/Rocket.Chat/pull/23171)) @@ -39,17 +90,24 @@ - Remove deprecated endpoints ([#23162](https://github.com/RocketChat/Rocket.Chat/pull/23162)) - The following REST endpoints were removed: - - - `/api/v1/emoji-custom` - - `/api/v1/info` - - `/api/v1/permissions` - - `/api/v1/permissions.list` - - The following Real time API Methods were removed: - - - `getFullUserData` - - `getServerInfo` + The following REST endpoints were removed: + + + - `/api/v1/emoji-custom` + + - `/api/v1/info` + + - `/api/v1/permissions` + + - `/api/v1/permissions.list` + + The following Real time API Methods were removed: + + + - `getFullUserData` + + - `getServerInfo` + - `livechat:saveOfficeHours` - Remove Google Vision features ([#23160](https://github.com/RocketChat/Rocket.Chat/pull/23160)) @@ -58,8 +116,8 @@ - Remove old migrations up to version 2.4.14 ([#23277](https://github.com/RocketChat/Rocket.Chat/pull/23277)) - To update to version 4.0.0 you'll need to be running at least version 3.0.0, otherwise you might loose some database migrations which might have unexpected effects. - + To update to version 4.0.0 you'll need to be running at least version 3.0.0, otherwise you might loose some database migrations which might have unexpected effects. + This aims to clean up the code, since upgrades jumping 2 major versions are too risky and hard to maintain, we'll keep only migration from that last major (in this case 3.x). - Remove patch info from endpoint /api/info for non-logged in users ([#16050](https://github.com/RocketChat/Rocket.Chat/pull/16050) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) @@ -68,18 +126,18 @@ - Stop sending audio notifications via stream ([#23108](https://github.com/RocketChat/Rocket.Chat/pull/23108)) - Remove audio preferences and make them tied to desktop notification preferences. - + Remove audio preferences and make them tied to desktop notification preferences. + TL;DR: new message sounds will play only if you receive a desktop notification. you'll still be able to chose to not play any sound though - Webhook will fail if user is not part of the channel ([#23310](https://github.com/RocketChat/Rocket.Chat/pull/23310)) - Remove deprecated behavior added by https://github.com/RocketChat/Rocket.Chat/pull/18024 that accepts webhook integrations sending messages even if the user is not part of the channel. - - Starting from 4.0.0 the webhook request will fail with `error-not-allowed` error: - - ``` - {"success":false,"error":"error-not-allowed"} + Remove deprecated behavior added by https://github.com/RocketChat/Rocket.Chat/pull/18024 that accepts webhook integrations sending messages even if the user is not part of the channel. + + Starting from 4.0.0 the webhook request will fail with `error-not-allowed` error: + + ``` + {"success":false,"error":"error-not-allowed"} ``` ### 🎉 New features @@ -97,23 +155,26 @@ - Seats Cap ([#23017](https://github.com/RocketChat/Rocket.Chat/pull/23017) by [@g-thome](https://github.com/g-thome)) - - Adding New Members - - Awareness of seats usage while adding new members - - Seats Cap about to be reached - - Seats Cap reached - - Request more seats - - Warning Admins - - System telling admins max seats are about to exceed - - System telling admins max seats were exceed - - Metric on Info Page - - Request more seats - - Warning Members - - Invite link - - Block creating new invite links - - Block existing invite links (feedback on register process) - - Register to Workspaces - - Emails - - System telling admins max seats are about to exceed + - Adding New Members + - Awareness of seats usage while adding new members + - Seats Cap about to be reached + - Seats Cap reached + - Request more seats + + - Warning Admins + - System telling admins max seats are about to exceed + - System telling admins max seats were exceed + - Metric on Info Page + - Request more seats + + - Warning Members + - Invite link + - Block creating new invite links + - Block existing invite links (feedback on register process) + - Register to Workspaces + + - Emails + - System telling admins max seats are about to exceed - System telling admins max seats were exceed ### 🚀 Improvements @@ -121,10 +182,10 @@ - **APPS:** New storage strategy for Apps-Engine file packages ([#22657](https://github.com/RocketChat/Rocket.Chat/pull/22657)) - This is an enabler for our initiative to support NPM packages in the Apps-Engine. - - Currently, the packages (zip files) for Rocket.Chat Apps are stored as a base64 encoded string in a document in the database, which constrains us due to the size limit of a document in MongoDB (16Mb). - + This is an enabler for our initiative to support NPM packages in the Apps-Engine. + + Currently, the packages (zip files) for Rocket.Chat Apps are stored as a base64 encoded string in a document in the database, which constrains us due to the size limit of a document in MongoDB (16Mb). + When we allow apps to include NPM packages, the size of the App package itself will be potentially _very large_ (I'm looking at you `node_modules`). Thus we'll be changing the strategy to store apps either with GridFS or the host's File System itself. - **APPS:** Return task ids when using the scheduler api ([#23023](https://github.com/RocketChat/Rocket.Chat/pull/23023)) @@ -164,9 +225,9 @@ - "Read Only" and "Allow Reacting" system messages are missing in rooms ([#23037](https://github.com/RocketChat/Rocket.Chat/pull/23037)) - - Add system message to notify changes on the **"Read Only"** setting; - - Add system message to notify changes on the **"Allow Reacting"** setting; - - Fix "Allow Reacting" setting's description (updated from "Only authorized users can write new messages" to "Only authorized users can react to messages"). + - Add system message to notify changes on the **"Read Only"** setting; + - Add system message to notify changes on the **"Allow Reacting"** setting; + - Fix "Allow Reacting" setting's description (updated from "Only authorized users can write new messages" to "Only authorized users can react to messages"). ![system-messages](https://user-images.githubusercontent.com/36537004/130883527-9eb47fcd-c8e5-41fb-af34-5d99bd0a6780.PNG) - Add check before placing chat on-hold to confirm that contact sent last message ([#23053](https://github.com/RocketChat/Rocket.Chat/pull/23053)) @@ -181,9 +242,9 @@ - Inaccurate use of 'Mobile notifications' instead of 'Push notifications' in i18n strings ([#22978](https://github.com/RocketChat/Rocket.Chat/pull/22978)) - - Fix inaccurate use of 'Mobile notifications' (which is misleading in German) by 'Push notifications'; - - Update `'Notification_Mobile_Default_For'` key to `'Notification_Push_Default_For'` (and text to 'Send Push Notifications For' for English Language); - - Update `'Accounts_Default_User_Preferences_mobileNotifications'` key to `'Accounts_Default_User_Preferences_pushNotifications'`; + - Fix inaccurate use of 'Mobile notifications' (which is misleading in German) by 'Push notifications'; + - Update `'Notification_Mobile_Default_For'` key to `'Notification_Push_Default_For'` (and text to 'Send Push Notifications For' for English Language); + - Update `'Accounts_Default_User_Preferences_mobileNotifications'` key to `'Accounts_Default_User_Preferences_pushNotifications'`; - Update `'Mobile_Notifications_Default_Alert'` key to `'Mobile_Push_Notifications_Default_Alert'`; - Logging out from other clients ([#23276](https://github.com/RocketChat/Rocket.Chat/pull/23276)) @@ -192,7 +253,7 @@ - Modals is cutting pixels of the content ([#23243](https://github.com/RocketChat/Rocket.Chat/pull/23243)) - Fuselage Dependency: [543](https://github.com/RocketChat/Rocket.Chat.Fuselage/pull/543) + Fuselage Dependency: [543](https://github.com/RocketChat/Rocket.Chat.Fuselage/pull/543) ![image](https://user-images.githubusercontent.com/27704687/134049227-3cd1deed-34ba-454f-a95e-e99b79a7a7b9.png) - Omnichannel On hold chats being forwarded to offline agents ([#23185](https://github.com/RocketChat/Rocket.Chat/pull/23185)) @@ -201,15 +262,15 @@ - Prevent users to edit an existing role when adding a new one with the same name used before. ([#22407](https://github.com/RocketChat/Rocket.Chat/pull/22407) by [@lucassartor](https://github.com/lucassartor)) - ### before - ![Peek 2021-07-13 16-31](https://user-images.githubusercontent.com/27704687/125513721-953d84f4-1c95-45ca-80e1-b00992b874f6.gif) - - ### after + ### before + ![Peek 2021-07-13 16-31](https://user-images.githubusercontent.com/27704687/125513721-953d84f4-1c95-45ca-80e1-b00992b874f6.gif) + + ### after ![Peek 2021-07-13 16-34](https://user-images.githubusercontent.com/27704687/125514098-91ee8014-51e5-4c62-9027-5538acf57d08.gif) - Remove doubled "Canned Responses" strings ([#23056](https://github.com/RocketChat/Rocket.Chat/pull/23056)) - - Remove doubled canned response setting introduced in #22703 (by setting id change); + - Remove doubled canned response setting introduced in #22703 (by setting id change); - Update "Canned Responses" keys to "Canned_Responses". - Remove margin from quote inside quote ([#21779](https://github.com/RocketChat/Rocket.Chat/pull/21779)) @@ -220,16 +281,21 @@ - Sidebar not closing when clicking in Home or Directory on mobile view ([#23218](https://github.com/RocketChat/Rocket.Chat/pull/23218)) - ### Additional fixed - - Merge Burger menu components into a single component - - Show a badge with no-read messages in the Burger Button: - ![image](https://user-images.githubusercontent.com/27704687/133679378-20fea2c0-4ac1-4b4e-886e-45154cc6afea.png) + ### Additional fixed + + - Merge Burger menu components into a single component + + - Show a badge with no-read messages in the Burger Button: + ![image](https://user-images.githubusercontent.com/27704687/133679378-20fea2c0-4ac1-4b4e-886e-45154cc6afea.png) + - remove useSidebarClose hook - Stop queue when Omnichannel is disabled or the routing method does not support it ([#23261](https://github.com/RocketChat/Rocket.Chat/pull/23261)) - - Add missing key logs - - Stop queue (and logs) when livechat is disabled or when routing method does not support queue + - Add missing key logs + + - Stop queue (and logs) when livechat is disabled or when routing method does not support queue + - Stop ignoring offline bot agents from delegation (previously, if a bot was offline, even with "Assign new conversations to bot agent" enabled, bot will be ignored and chat will be left in limbo (since bot was assigned, but offline). - Toolbox click not working on Safari(iOS) ([#23244](https://github.com/RocketChat/Rocket.Chat/pull/23244)) @@ -336,17 +402,17 @@ - Regression: Blank screen in Jitsi video calls ([#23322](https://github.com/RocketChat/Rocket.Chat/pull/23322)) - - Fix Jitsi calls being disposed even when "Open in new window" setting is disabled; + - Fix Jitsi calls being disposed even when "Open in new window" setting is disabled; - Fix misspelling on `CallJitsWithData.js` file name. - Regression: Create new loggers based on server log level ([#23297](https://github.com/RocketChat/Rocket.Chat/pull/23297)) - Regression: Fix app storage migration ([#23286](https://github.com/RocketChat/Rocket.Chat/pull/23286)) - The previous version of this migration didn't take into consideration apps that were installed prior to [Rocket.Chat@3.8.0](https://github.com/RocketChat/Rocket.Chat/releases/tag/3.8.0), which [removed the typescript compiler from the server](https://github.com/RocketChat/Rocket.Chat/pull/18687) and into the CLI. As a result, the zip files inside each installed app's document in the database had typescript files in them instead of the now required javascript files. - - As the new strategy of source code storage for apps changes the way the app is loaded, those zip files containing the source code are read everytime the app is started (or [in this particular case, updated](https://github.com/RocketChat/Rocket.Chat/pull/23286/files#diff-caf9f7a22478639e58d6514be039140a42ce1ab2d999c3efe5678c38ee36d0ccR43)), and as the zips' contents were wrong, the operation was failing. - + The previous version of this migration didn't take into consideration apps that were installed prior to [Rocket.Chat@3.8.0](https://github.com/RocketChat/Rocket.Chat/releases/tag/3.8.0), which [removed the typescript compiler from the server](https://github.com/RocketChat/Rocket.Chat/pull/18687) and into the CLI. As a result, the zip files inside each installed app's document in the database had typescript files in them instead of the now required javascript files. + + As the new strategy of source code storage for apps changes the way the app is loaded, those zip files containing the source code are read everytime the app is started (or [in this particular case, updated](https://github.com/RocketChat/Rocket.Chat/pull/23286/files#diff-caf9f7a22478639e58d6514be039140a42ce1ab2d999c3efe5678c38ee36d0ccR43)), and as the zips' contents were wrong, the operation was failing. + The fix extract the data from old apps and creates new zip files with the compiled `js` already present. - Regression: Fix Bugsnag not started error ([#23308](https://github.com/RocketChat/Rocket.Chat/pull/23308)) @@ -523,8 +589,10 @@ - **ENTERPRISE:** Maximum waiting time for chats in Omnichannel queue ([#22955](https://github.com/RocketChat/Rocket.Chat/pull/22955)) - - Add new settings to support closing chats that have been too long on waiting queue - - Moved old settings to new "Queue Management" section + - Add new settings to support closing chats that have been too long on waiting queue + + - Moved old settings to new "Queue Management" section + - Fix issue when closing a livechat room that caused client to not to know if room was open or not - Banner for the updates regarding authentication services ([#23055](https://github.com/RocketChat/Rocket.Chat/pull/23055) by [@g-thome](https://github.com/g-thome)) @@ -539,10 +607,10 @@ - Separate RegEx Settings for Channels and Usernames validation ([#21937](https://github.com/RocketChat/Rocket.Chat/pull/21937) by [@aditya-mitra](https://github.com/aditya-mitra)) - Now, there are 2 separate settings for validating names - One for **channels** and another for **usernames**. - - This change also removes the old `UTF8_Names_Validation` setting and adds 2 new settings `UTF8_User_Names_Validation` and `UTF8_Channel_Names_Validation`. - + Now, there are 2 separate settings for validating names - One for **channels** and another for **usernames**. + + This change also removes the old `UTF8_Names_Validation` setting and adds 2 new settings `UTF8_User_Names_Validation` and `UTF8_Channel_Names_Validation`. + https://user-images.githubusercontent.com/55396651/116969904-af5bb800-acd4-11eb-9fc4-dacac60cb08f.mp4 ### 🚀 Improvements @@ -558,13 +626,13 @@ - Rewrite File Upload Modal ([#22750](https://github.com/RocketChat/Rocket.Chat/pull/22750)) - Image preview: - ![image](https://user-images.githubusercontent.com/40830821/127223432-dccd2182-aec0-430f-8d70-03ac88aec791.png) - - Video preview: - ![image](https://user-images.githubusercontent.com/40830821/127225982-f8b21840-0d9c-4aff-a354-16188c7ed66e.png) - - Files larger than 10mb: + Image preview: + ![image](https://user-images.githubusercontent.com/40830821/127223432-dccd2182-aec0-430f-8d70-03ac88aec791.png) + + Video preview: + ![image](https://user-images.githubusercontent.com/40830821/127225982-f8b21840-0d9c-4aff-a354-16188c7ed66e.png) + + Files larger than 10mb: ![image](https://user-images.githubusercontent.com/40830821/127222611-5265040f-a06b-4ec5-b528-89b40e6a9072.png) - Types from currentChatsPage.tsx ([#22967](https://github.com/RocketChat/Rocket.Chat/pull/22967)) @@ -580,14 +648,14 @@ - "Users By Time of the Day" chart displays incorrect data for Local Timezone ([#22836](https://github.com/RocketChat/Rocket.Chat/pull/22836)) - - Add local timezone conversion to the "Users By Time of the Day" chart in the Engagement Dashboard; + - Add local timezone conversion to the "Users By Time of the Day" chart in the Engagement Dashboard; - Simplify date creations by using `endOf` and `startOf` methods. - Atlassian Crowd connection not working ([#22996](https://github.com/RocketChat/Rocket.Chat/pull/22996) by [@piotrkochan](https://github.com/piotrkochan)) - Audio recording doesn't stop in direct messages on channel switch ([#22880](https://github.com/RocketChat/Rocket.Chat/pull/22880)) - - Cancel audio recordings on message bar destroy event. + - Cancel audio recordings on message bar destroy event. ![test-22372](https://user-images.githubusercontent.com/36537004/128569780-d83747b0-fb9c-4dc6-9bc5-7ae573e720c8.gif) - Bad words falling if message is empty ([#22930](https://github.com/RocketChat/Rocket.Chat/pull/22930)) @@ -612,21 +680,23 @@ - Return transcript/dashboards based on timezone settings ([#22850](https://github.com/RocketChat/Rocket.Chat/pull/22850)) - - Added new setting to manage timezones - - Applied new setting to omnichannel dashboards (realtime, analytics) [NOTE: Other dashboards aren't using this setting actually) + - Added new setting to manage timezones + + - Applied new setting to omnichannel dashboards (realtime, analytics) [NOTE: Other dashboards aren't using this setting actually) + - Change getAnalyticsBetweenDate query to filter out system messages instead of substracting them - Tab margin style ([#22851](https://github.com/RocketChat/Rocket.Chat/pull/22851)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/128103848-2a25ba7e-0e59-4502-9bcd-2569cad9379a.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/128103848-2a25ba7e-0e59-4502-9bcd-2569cad9379a.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/128103633-ec7b93fc-4667-4dc9-bad3-bfffaff3974e.png) - Threads and discussions searches don't display proper results ([#22914](https://github.com/RocketChat/Rocket.Chat/pull/22914)) - - _Fix_ issue in discussions search (which wasn't working after a search with no results was made); + - _Fix_ issue in discussions search (which wasn't working after a search with no results was made); - _Improve_ discussions and threads searches: both searches (`chat.getDiscussions` and `chat.getThreadsList`) are now case insensitive (do NOT differ capital from lower letters) and match incomplete words or terms. - Threads List being requested more than expected ([#22879](https://github.com/RocketChat/Rocket.Chat/pull/22879)) @@ -661,8 +731,8 @@ - Chore: Script to start Rocket.Chat in HA mode during development ([#22398](https://github.com/RocketChat/Rocket.Chat/pull/22398)) - Sometimes we need to start Rocket.Chat in High-Availability mode (cluster) during development to test how a feature behaves or hunt down a bug. Currently, this involves a lot of commands with details that might be lost if you haven't done it in a while. - + Sometimes we need to start Rocket.Chat in High-Availability mode (cluster) during development to test how a feature behaves or hunt down a bug. Currently, this involves a lot of commands with details that might be lost if you haven't done it in a while. + This PR intends to provide a really simple way for us to start many instances of Rocket.Chat connected in a cluster. - Chore: Update Livechat widget to 1.9.4 ([#22990](https://github.com/RocketChat/Rocket.Chat/pull/22990)) @@ -679,13 +749,13 @@ - Regression: File upload name suggestion ([#22953](https://github.com/RocketChat/Rocket.Chat/pull/22953)) - Before: - ![image](https://user-images.githubusercontent.com/40830821/129774936-ecdbe9a1-5e3f-4a0a-ad1e-6f13eb15c60b.png) - ![image](https://user-images.githubusercontent.com/40830821/129775011-fb0df01d-74e4-41ae-bb47-dcf4cc17735e.png) - - - After: - ![image](https://user-images.githubusercontent.com/40830821/129774877-928a8aa0-c003-4e57-8b33-ea6accc32774.png) + Before: + ![image](https://user-images.githubusercontent.com/40830821/129774936-ecdbe9a1-5e3f-4a0a-ad1e-6f13eb15c60b.png) + ![image](https://user-images.githubusercontent.com/40830821/129775011-fb0df01d-74e4-41ae-bb47-dcf4cc17735e.png) + + + After: + ![image](https://user-images.githubusercontent.com/40830821/129774877-928a8aa0-c003-4e57-8b33-ea6accc32774.png) ![image](https://user-images.githubusercontent.com/40830821/129774972-d67debaf-0ce9-44fb-93cb-d7612dd18edf.png) - Regression: Fix creation of self-DMs ([#23015](https://github.com/RocketChat/Rocket.Chat/pull/23015)) @@ -753,7 +823,8 @@ - Fix Auto Selection algorithm on community edition ([#22991](https://github.com/RocketChat/Rocket.Chat/pull/22991)) - - When using the autoselection algo on community editions, all agents were marked as unavailable due to an unapplied filter + - When using the autoselection algo on community editions, all agents were marked as unavailable due to an unapplied filter + - Fixed an issue when both user & system setting to manange EE max number of chats allowed were set to 0
@@ -793,7 +864,7 @@ - Apps-Engine's scheduler failing to update run tasks ([#22882](https://github.com/RocketChat/Rocket.Chat/pull/22882)) - [Agenda](https://github.com/agenda/agenda), the library that manages scheduling, depended on setting a job property named `nextRunAt` as `undefined` to signal whether it should be run on schedule or not. [Rocket.Chat's current Mongo driver](https://github.com/RocketChat/Rocket.Chat/pull/22399) ignores `undefined` values when updating documents and this was causing jobs to never stop running as Agenda couldn't clear that property (set them as `undefined`). + [Agenda](https://github.com/agenda/agenda), the library that manages scheduling, depended on setting a job property named `nextRunAt` as `undefined` to signal whether it should be run on schedule or not. [Rocket.Chat's current Mongo driver](https://github.com/RocketChat/Rocket.Chat/pull/22399) ignores `undefined` values when updating documents and this was causing jobs to never stop running as Agenda couldn't clear that property (set them as `undefined`). This updates Rocket.Chat's dependency on Agenda.js to point to [a fork that fixes the problem](https://github.com/RocketChat/agenda/releases/tag/3.1.2). - Close omnichannel conversations when agent is deactivated ([#22917](https://github.com/RocketChat/Rocket.Chat/pull/22917)) @@ -847,7 +918,7 @@ - Monitoring Track messages' round trip time ([#22676](https://github.com/RocketChat/Rocket.Chat/pull/22676)) - Track messages' roundtrip time from backend saves time to the time when received back from the oplog allowing track of oplog slowness. + Track messages' roundtrip time from backend saves time to the time when received back from the oplog allowing track of oplog slowness. Prometheus metric: `rocketchat_messages_roundtrip_time` - REST endpoint to remove User from Role ([#20485](https://github.com/RocketChat/Rocket.Chat/pull/20485) by [@Cosnavel](https://github.com/Cosnavel) & [@lucassartor](https://github.com/lucassartor)) @@ -859,19 +930,22 @@ - Change message deletion confirmation modal to toast ([#22544](https://github.com/RocketChat/Rocket.Chat/pull/22544)) - Changed a timed modal for a toast message + Changed a timed modal for a toast message ![image](https://user-images.githubusercontent.com/40830821/124192670-0646f900-da9c-11eb-941c-9ae35421f6ef.png) - Configuration for indices in Apps-Engine models ([#22705](https://github.com/RocketChat/Rocket.Chat/pull/22705)) - * Add `appId` field to the data saved by the Scheduler - * Add `appId` index to `rocketchat_apps_persistence` model - * Skip "trash collection" when deleting records from `rocketchat_apps_persistence` - * Add a new setting to control for how long we should keep logs from the apps - - ![image](https://user-images.githubusercontent.com/1810309/126246666-907f9d98-1d84-4dfe-a80a-7dd874d36fa8.png) - - + * Add `appId` field to the data saved by the Scheduler + + * Add `appId` index to `rocketchat_apps_persistence` model + + * Skip "trash collection" when deleting records from `rocketchat_apps_persistence` + + * Add a new setting to control for how long we should keep logs from the apps + + ![image](https://user-images.githubusercontent.com/1810309/126246666-907f9d98-1d84-4dfe-a80a-7dd874d36fa8.png) + + ![image](https://user-images.githubusercontent.com/1810309/126246655-2ce3cb5f-b2f5-456e-a9c4-beccd9b3ef41.png) - Make `shortcut` field of canned responses unique ([#22700](https://github.com/RocketChat/Rocket.Chat/pull/22700)) @@ -894,37 +968,38 @@ - Replace remaing discussion creation modals with React modal. ([#22448](https://github.com/RocketChat/Rocket.Chat/pull/22448)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/123840524-cbe72b80-d8e4-11eb-9ddb-23a9f9d90aac.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/123840524-cbe72b80-d8e4-11eb-9ddb-23a9f9d90aac.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/123840219-74e15680-d8e4-11eb-95aa-00a990ffe0e7.png) - Return open room if available for visitors ([#22742](https://github.com/RocketChat/Rocket.Chat/pull/22742)) - Rewrite Enter Encryption Password Modal ([#22456](https://github.com/RocketChat/Rocket.Chat/pull/22456)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/123182889-bbf3c580-d466-11eb-8d4d-9cfc3d224e33.png) - - ### after - ![image](https://user-images.githubusercontent.com/27704687/123182916-cada7800-d466-11eb-96ee-850be190d419.png) - - ### Aditional Improves: + ### before + ![image](https://user-images.githubusercontent.com/27704687/123182889-bbf3c580-d466-11eb-8d4d-9cfc3d224e33.png) + + ### after + ![image](https://user-images.githubusercontent.com/27704687/123182916-cada7800-d466-11eb-96ee-850be190d419.png) + + ### Aditional Improves: + - Added a visual validation in the password field - Rewrite OTR modals ([#22583](https://github.com/RocketChat/Rocket.Chat/pull/22583)) - ![image](https://user-images.githubusercontent.com/40830821/124513267-cb510800-ddb0-11eb-8165-f103029c348f.png) - ![image](https://user-images.githubusercontent.com/40830821/124513354-04897800-ddb1-11eb-96f4-41fe906ca0d7.png) + ![image](https://user-images.githubusercontent.com/40830821/124513267-cb510800-ddb0-11eb-8165-f103029c348f.png) + ![image](https://user-images.githubusercontent.com/40830821/124513354-04897800-ddb1-11eb-96f4-41fe906ca0d7.png) ![image](https://user-images.githubusercontent.com/40830821/124513395-1b2fcf00-ddb1-11eb-83e4-3f8f9b4676ba.png) - Rewrite Save Encryption Password Modal ([#22447](https://github.com/RocketChat/Rocket.Chat/pull/22447)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/122980201-c337a800-d36e-11eb-8e2b-68534cea8e1e.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/122980201-c337a800-d36e-11eb-8e2b-68534cea8e1e.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/122980409-f8dc9100-d36e-11eb-9c15-aff779c84a91.png) - Rewrite sidebar footer as React Component ([#22687](https://github.com/RocketChat/Rocket.Chat/pull/22687)) @@ -939,12 +1014,12 @@ - Wrong error message when trying to create a blocked username ([#22452](https://github.com/RocketChat/Rocket.Chat/pull/22452) by [@lucassartor](https://github.com/lucassartor)) - When trying to create a user with a blocked username, the UI was showing generic error message that it wasn't very detailed. - - Old error message: - ![image](https://user-images.githubusercontent.com/49413772/123120080-6d203e80-d41a-11eb-8c87-64e34334c856.png) - - New error message: + When trying to create a user with a blocked username, the UI was showing generic error message that it wasn't very detailed. + + Old error message: + ![image](https://user-images.githubusercontent.com/49413772/123120080-6d203e80-d41a-11eb-8c87-64e34334c856.png) + + New error message: ![aaa](https://user-images.githubusercontent.com/49413772/123120251-8c1ed080-d41a-11eb-8dc2-d7484923d851.PNG) ### 🐛 Bug fixes @@ -952,19 +1027,19 @@ - **ENTERPRISE:** Engagement Dashboard displaying incorrect data about active users ([#22381](https://github.com/RocketChat/Rocket.Chat/pull/22381)) - - Fix sessions' and users' grouping in the Engagement Dashboard API endpoints; - - Fix the data displayed in the charts from the "Active users", "Users by time of day" and "When is the chat busier?" sections of the Engagement Dashboard; + - Fix sessions' and users' grouping in the Engagement Dashboard API endpoints; + - Fix the data displayed in the charts from the "Active users", "Users by time of day" and "When is the chat busier?" sections of the Engagement Dashboard; - Replace label used to describe the amount of Active Users in the License section of the Info page. - **ENTERPRISE:** Make AutoSelect algo take current agent load in consideration ([#22611](https://github.com/RocketChat/Rocket.Chat/pull/22611)) - **ENTERPRISE:** Race condition on Omnichannel visitor abandoned callback ([#22413](https://github.com/RocketChat/Rocket.Chat/pull/22413)) - As you can see [here](https://github.com/RocketChat/Rocket.Chat/blob/857791c39c97b51b5b6fd3718e0c816959a81c3b/ee/app/livechat-enterprise/server/lib/Helper.js#L127) the `predictedVisitorAbandonment` flag is not set if the room object doesn't have `v.lastMessageTs` property. So we need to always make sure the `v.lastMessageTs` is set before this method is called. - - Currently the `v.lastMessageTs` is being set in [this](https://github.com/RocketChat/Rocket.Chat/blob/857791c39c97b51b5b6fd3718e0c816959a81c3b/app/livechat/server/hooks/saveLastVisitorMessageTs.js#L4) (lets call this **hook-1**) hook which has `HIGH` priority - and the `predictedVisitorAbandonment` check is inturn performed in [this](https://github.com/RocketChat/Rocket.Chat/blob/857791c39c97b51b5b6fd3718e0c816959a81c3b/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.js#L5) (let call this **hook-2**) hook which is also `HIGH` priority. - + As you can see [here](https://github.com/RocketChat/Rocket.Chat/blob/857791c39c97b51b5b6fd3718e0c816959a81c3b/ee/app/livechat-enterprise/server/lib/Helper.js#L127) the `predictedVisitorAbandonment` flag is not set if the room object doesn't have `v.lastMessageTs` property. So we need to always make sure the `v.lastMessageTs` is set before this method is called. + + Currently the `v.lastMessageTs` is being set in [this](https://github.com/RocketChat/Rocket.Chat/blob/857791c39c97b51b5b6fd3718e0c816959a81c3b/app/livechat/server/hooks/saveLastVisitorMessageTs.js#L4) (lets call this **hook-1**) hook which has `HIGH` priority + and the `predictedVisitorAbandonment` check is inturn performed in [this](https://github.com/RocketChat/Rocket.Chat/blob/857791c39c97b51b5b6fd3718e0c816959a81c3b/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.js#L5) (let call this **hook-2**) hook which is also `HIGH` priority. + So ideally we'd except the **hook-1** to be called b4 **hook-2**, however currently since both of them are at same priority, there is no way to control which one is executed first. Hence in this PR, I'm making the priority of **hook-2** as `MEDIUM` to keeping the priority of **hook-1** the same as b4, i.e. `HIGH`. This should make sure that the **hook-1** is always executed b4 **hook-2** - Admin page crashing when commit hash is null ([#22057](https://github.com/RocketChat/Rocket.Chat/pull/22057) by [@cprice-kgi](https://github.com/cprice-kgi)) @@ -973,39 +1048,41 @@ - Blank screen in message auditing DM tab ([#22763](https://github.com/RocketChat/Rocket.Chat/pull/22763)) - The DM tab in message auditing was displaying a blank screen, instead of the actual tab. - + The DM tab in message auditing was displaying a blank screen, instead of the actual tab. + ![image](https://user-images.githubusercontent.com/28611993/127041404-dfca7f6a-2b8b-4c15-9cbd-c6238fac0063.png) - Bugs in AutoCompleteDepartment ([#22414](https://github.com/RocketChat/Rocket.Chat/pull/22414)) - Call button is still displayed when the user doesn't have permission to use it ([#22170](https://github.com/RocketChat/Rocket.Chat/pull/22170)) - - Hide 'Call' buttons from the tab bar for muted users; + - Hide 'Call' buttons from the tab bar for muted users; + - Display an error when a muted user attempts to enter a call using the 'Click to Join!' button. - Can't see full user profile on team's room ([#22355](https://github.com/RocketChat/Rocket.Chat/pull/22355)) - ### before - ![before](https://user-images.githubusercontent.com/27704687/121966860-bbac4980-cd45-11eb-8d48-2b0457110fc7.gif) - - ### after - ![after](https://user-images.githubusercontent.com/27704687/121966870-bea73a00-cd45-11eb-9c89-ec52ac17e20f.gif) - - ### aditional fix :rocket: + ### before + ![before](https://user-images.githubusercontent.com/27704687/121966860-bbac4980-cd45-11eb-8d48-2b0457110fc7.gif) + + ### after + ![after](https://user-images.githubusercontent.com/27704687/121966870-bea73a00-cd45-11eb-9c89-ec52ac17e20f.gif) + + ### aditional fix :rocket: + - unnecessary `TeamsMembers` component removed - Cannot create a discussion from top left sidebar as a user ([#22618](https://github.com/RocketChat/Rocket.Chat/pull/22618) by [@lucassartor](https://github.com/lucassartor)) - When trying to create a discussion using the top left sidebar modal with an role that don't have the `view-other-user-channels ` permission, an empty list would be shown, which is a wrong behavior. - Also, when being able to use this modal, discussions were listed as options, which is also a wrong behavior as there can't be nested discussions. - - This PR looks to fix both these issues. - - **Old behavior:** - ![old](https://user-images.githubusercontent.com/49413772/124960017-3c333280-dff2-11eb-86cd-b2638311517e.png) - - **New behavior:** + When trying to create a discussion using the top left sidebar modal with an role that don't have the `view-other-user-channels ` permission, an empty list would be shown, which is a wrong behavior. + Also, when being able to use this modal, discussions were listed as options, which is also a wrong behavior as there can't be nested discussions. + + This PR looks to fix both these issues. + + **Old behavior:** + ![old](https://user-images.githubusercontent.com/49413772/124960017-3c333280-dff2-11eb-86cd-b2638311517e.png) + + **New behavior:** ![image](https://user-images.githubusercontent.com/49413772/124958882-05a8e800-dff1-11eb-8203-b34a4f1c98a0.png) - Channel is automatically getting added to the first option in move to team feature ([#22670](https://github.com/RocketChat/Rocket.Chat/pull/22670)) @@ -1020,12 +1097,12 @@ - Create discussion modal - cancel button and invite users alignment ([#22718](https://github.com/RocketChat/Rocket.Chat/pull/22718)) - Changes in "open discussion" modal - - > Added cancel button - > Fixed alignment in invite user - - + Changes in "open discussion" modal + + > Added cancel button + > Fixed alignment in invite user + + ![image](https://user-images.githubusercontent.com/28611993/126388304-6ac76574-6924-426e-843d-afd53dc1c874.png) - crush in the getChannelHistory method ([#22667](https://github.com/RocketChat/Rocket.Chat/pull/22667) by [@MaestroArt](https://github.com/MaestroArt)) @@ -1060,27 +1137,29 @@ - Quote message not working for Livechat visitors ([#22586](https://github.com/RocketChat/Rocket.Chat/pull/22586)) - ### Before: - ![image](https://user-images.githubusercontent.com/34130764/124583613-de2b1180-de70-11eb-82aa-18564b317626.png) - ### After: + ### Before: + ![image](https://user-images.githubusercontent.com/34130764/124583613-de2b1180-de70-11eb-82aa-18564b317626.png) + ### After: ![image](https://user-images.githubusercontent.com/34130764/124583775-12063700-de71-11eb-8ab5-b0169fac2d40.png) - Redirect to login after delete own account ([#22499](https://github.com/RocketChat/Rocket.Chat/pull/22499)) - Redirect the user to login after delete own account - - ### Aditional fixes: - - Visual issue in password input on Delete Own Account Modal - - ### before - ![image](https://user-images.githubusercontent.com/27704687/123711503-f5ea1080-d846-11eb-96aa-8ed638ca665c.png) - - ### after + Redirect the user to login after delete own account + + ### Aditional fixes: + + - Visual issue in password input on Delete Own Account Modal + + ### before + ![image](https://user-images.githubusercontent.com/27704687/123711503-f5ea1080-d846-11eb-96aa-8ed638ca665c.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/123711336-b3c0cf00-d846-11eb-9408-a686d8668ba5.png) - Remove stack traces from Meteor errors when debug setting is disabled ([#22699](https://github.com/RocketChat/Rocket.Chat/pull/22699)) - - Fix 'not iterable' errors in the `normalizeMessage` function; + - Fix 'not iterable' errors in the `normalizeMessage` function; + - Remove stack traces from errors thrown by the `jitsi:updateTimeout` (and other `Meteor.Error`s) method. - Rewrite CurrentChats to TS ([#22424](https://github.com/RocketChat/Rocket.Chat/pull/22424)) @@ -1169,15 +1248,16 @@ - Regression: Data in the "Active Users" section is delayed in 1 day ([#22794](https://github.com/RocketChat/Rocket.Chat/pull/22794)) - - Fix 1 day delay in the Engagement Dashboard's "Active Users" section; - - Downgrade `@nivo/line` version. - **Expected behavior:** + - Fix 1 day delay in the Engagement Dashboard's "Active Users" section; + + - Downgrade `@nivo/line` version. + **Expected behavior:** ![active-users-engagement-dashboard](https://user-images.githubusercontent.com/36537004/127372185-390dc42f-bc90-4841-a22b-731f0aafcafe.PNG) - Regression: Data in the "New Users" section is delayed in 1 day ([#22751](https://github.com/RocketChat/Rocket.Chat/pull/22751)) - - Update nivo version (which was causing errors in the bar chart); - - Fix 1 day delay in '7 days' and '30 days' periods; + - Update nivo version (which was causing errors in the bar chart); + - Fix 1 day delay in '7 days' and '30 days' periods; - Update tooltip theme. - Regression: Federation warnings on ci ([#22765](https://github.com/RocketChat/Rocket.Chat/pull/22765) by [@g-thome](https://github.com/g-thome)) @@ -1202,9 +1282,9 @@ - Regression: Fix tooltip style in the "Busiest Chat Times" chart ([#22813](https://github.com/RocketChat/Rocket.Chat/pull/22813)) - - Fix tooltip in the Engagement Dashboard's "Busiest Chat Times" chart (Hours). - - **Expected behavior:** + - Fix tooltip in the Engagement Dashboard's "Busiest Chat Times" chart (Hours). + + **Expected behavior:** ![busiest-times-ed](https://user-images.githubusercontent.com/36537004/127527827-465397ed-f089-4fb7-9ab2-6fa8cea6abdf.PNG) - Regression: Fix users not being able to see the scope of the canned m… ([#22760](https://github.com/RocketChat/Rocket.Chat/pull/22760)) @@ -1221,10 +1301,10 @@ - Regression: Prevent custom status from being visible in sequential messages ([#22733](https://github.com/RocketChat/Rocket.Chat/pull/22733)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/126641946-866dae96-1983-43a5-b689-b24670473ad0.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/126641946-866dae96-1983-43a5-b689-b24670473ad0.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/126641752-3163eb95-1cd4-4d99-a61a-4d06d9e7e13e.png) - Regression: Properly force newline in attachment fields ([#22727](https://github.com/RocketChat/Rocket.Chat/pull/22727)) @@ -1405,30 +1485,32 @@ - Add `teams.convertToChannel` endpoint ([#22188](https://github.com/RocketChat/Rocket.Chat/pull/22188)) - - Add new `teams.converToChannel` endpoint; - - Update `ConvertToTeam` modal text (since this action can now be reversed); + - Add new `teams.converToChannel` endpoint; + + - Update `ConvertToTeam` modal text (since this action can now be reversed); + - Remove corresponding team memberships when a team is deleted or converted to a channel; - Add setting to configure default role for user on manual registration ([#20650](https://github.com/RocketChat/Rocket.Chat/pull/20650) by [@lucassartor](https://github.com/lucassartor)) - Add an `admin` setting to determine the initial `role` for new users who registered manually (through the register form and via API, not using an authentication service), normally all new users are assigned to the `user` role. - - The setting can be found in `Admin`->`Accounts`->`Registration`. - - ![image](https://user-images.githubusercontent.com/49413772/107252603-47b70900-6a14-11eb-9cc6-df76720b7365.png) - The setting initial value is false, so the default behaviour stays the same while creating a new server or upgrading one. - - https://user-images.githubusercontent.com/49413772/107253220-ddeb2f00-6a14-11eb-85b4-f770dbbe4970.mp4 - + Add an `admin` setting to determine the initial `role` for new users who registered manually (through the register form and via API, not using an authentication service), normally all new users are assigned to the `user` role. + + The setting can be found in `Admin`->`Accounts`->`Registration`. + + ![image](https://user-images.githubusercontent.com/49413772/107252603-47b70900-6a14-11eb-9cc6-df76720b7365.png) + The setting initial value is false, so the default behaviour stays the same while creating a new server or upgrading one. + + https://user-images.githubusercontent.com/49413772/107253220-ddeb2f00-6a14-11eb-85b4-f770dbbe4970.mp4 + Video showing an example of the setting being used and creating an new user with the default roles via API. - Content-Security-Policy for inline scripts ([#20724](https://github.com/RocketChat/Rocket.Chat/pull/20724)) - Security policies were applied for inline scripts cases. Due to the libraries and components we use it is not possible to disable inline styles and images as they would break Oembeds and other libraries. - - - basically the inline scripts were moved to a js file - + Security policies were applied for inline scripts cases. Due to the libraries and components we use it is not possible to disable inline styles and images as they would break Oembeds and other libraries. + + + basically the inline scripts were moved to a js file + and besides that some suggars syntax like `addScript` and `addStyle` were added, this way the application already takes care of inserting the elements and providing the content automatically. - Open modals in side effects outside React ([#22247](https://github.com/RocketChat/Rocket.Chat/pull/22247)) @@ -1444,15 +1526,17 @@ - Add BBB and Jitsi to Team ([#22312](https://github.com/RocketChat/Rocket.Chat/pull/22312)) - Added 2 new settings: - - `Admin > Video Conference > Big Blue Button > Enable for teams` + Added 2 new settings: + + - `Admin > Video Conference > Big Blue Button > Enable for teams` + - `Admin > Video Conference > Jitsi > Enable in teams` - Add debouncing to units selects filters ([#22097](https://github.com/RocketChat/Rocket.Chat/pull/22097)) - Add modal to close chats when tags/comments are not required ([#22245](https://github.com/RocketChat/Rocket.Chat/pull/22245) by [@rafaelblink](https://github.com/rafaelblink)) - When neither tags or comments are required to close a livechat, show this modal instead: + When neither tags or comments are required to close a livechat, show this modal instead: ![Screen Shot 2021-05-20 at 7 33 19 PM](https://user-images.githubusercontent.com/20868078/119057741-6af23c80-b9a3-11eb-902f-f8a7458ad11c.png) - Fallback messages on contextual bar ([#22376](https://github.com/RocketChat/Rocket.Chat/pull/22376)) @@ -1475,10 +1559,10 @@ - Remove differentiation between public x private channels in sidebar ([#22160](https://github.com/RocketChat/Rocket.Chat/pull/22160)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/119752184-e7d55880-be72-11eb-9167-be2f305ddb3f.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/119752184-e7d55880-be72-11eb-9167-be2f305ddb3f.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/119752125-c8d6c680-be72-11eb-8444-2e0c7cb1c600.png) - Rewrite create direct modal ([#22209](https://github.com/RocketChat/Rocket.Chat/pull/22209)) @@ -1487,8 +1571,8 @@ - Rewrite Create Discussion Modal (only through sidebar) ([#22224](https://github.com/RocketChat/Rocket.Chat/pull/22224)) - This is only available by creating a new discussion when clicking on the sidebar button. Other places will be implemented afterwards. - + This is only available by creating a new discussion when clicking on the sidebar button. Other places will be implemented afterwards. + ![image](https://user-images.githubusercontent.com/40830821/120556093-6af63180-c3d2-11eb-97ea-63c5423049dc.png) - Send only relevant data via WebSocket ([#22258](https://github.com/RocketChat/Rocket.Chat/pull/22258)) @@ -1502,12 +1586,12 @@ - **EE:** Canned responses can't be deleted ([#22095](https://github.com/RocketChat/Rocket.Chat/pull/22095) by [@rafaelblink](https://github.com/rafaelblink)) - Deletion button has been removed from the edition option. - - ## Before - ![image](https://user-images.githubusercontent.com/2493803/119059416-9f1b2c80-b9a6-11eb-933a-4efa1ac0552a.png) - - ### After + Deletion button has been removed from the edition option. + + ## Before + ![image](https://user-images.githubusercontent.com/2493803/119059416-9f1b2c80-b9a6-11eb-933a-4efa1ac0552a.png) + + ### After ![Rocket Chat (2)](https://user-images.githubusercontent.com/2493803/119172517-72b1ef80-ba3c-11eb-9178-04a12176f312.gif) - **ENTERPRISE:** Omnichannel enterprise permissions being added back to its default roles ([#22322](https://github.com/RocketChat/Rocket.Chat/pull/22322)) @@ -1516,19 +1600,19 @@ - **ENTERPRISE:** Prevent Visitor Abandonment after forwarding chat ([#22243](https://github.com/RocketChat/Rocket.Chat/pull/22243)) - Currently the Visitor Abandonment timer isn't affected when the chat is forwarded. However this is affecting the UX in certain situations like eg: A bot forwarding a chat to an human agent - ![image](https://user-images.githubusercontent.com/34130764/120896383-e4925780-c63e-11eb-937e-ffd7c4836159.png) - + Currently the Visitor Abandonment timer isn't affected when the chat is forwarded. However this is affecting the UX in certain situations like eg: A bot forwarding a chat to an human agent + ![image](https://user-images.githubusercontent.com/34130764/120896383-e4925780-c63e-11eb-937e-ffd7c4836159.png) + To solve this issue, we'll now be stoping the Visitor Abandonment timer once a chat is forwarded. - **IMPROVE:** Prevent creation of duplicated roles and new `roles.update` endpoint ([#22279](https://github.com/RocketChat/Rocket.Chat/pull/22279) by [@lucassartor](https://github.com/lucassartor)) - Currently, the action of updating a role is broken: because roles have their `_id` = `name`, when updating a role there's no way to validate if the user is trying to update or create a new role with a name that already exists - which causes wrong behaviors, such as roles with the same name and not being able to update them. - - To proper fix this, this PR looks to change the creation of roles. Now, roles have a unique `_id` value and there's a endpoint to update roles: `/api/v1/roles.update`. - - Doing so, it's possible to validate on both endpoints (`roles.create` and `roles.update`) to not allow roles with duplicated names. - + Currently, the action of updating a role is broken: because roles have their `_id` = `name`, when updating a role there's no way to validate if the user is trying to update or create a new role with a name that already exists - which causes wrong behaviors, such as roles with the same name and not being able to update them. + + To proper fix this, this PR looks to change the creation of roles. Now, roles have a unique `_id` value and there's a endpoint to update roles: `/api/v1/roles.update`. + + Doing so, it's possible to validate on both endpoints (`roles.create` and `roles.update`) to not allow roles with duplicated names. + **OBS:** The unique id changes only reflect new roles, the standard roles (such as admin and user) still have `_id` = `name`, but new roles now **can't** have the same name as them. - `channels.history`, `groups.history` and `im.history` REST endpoints not respecting hide system message config ([#22364](https://github.com/RocketChat/Rocket.Chat/pull/22364)) @@ -1545,10 +1629,10 @@ - Can't delete file from Room's file list ([#22191](https://github.com/RocketChat/Rocket.Chat/pull/22191)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/120215931-bb239700-c20c-11eb-9494-d4bc017df390.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/120215931-bb239700-c20c-11eb-9494-d4bc017df390.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/120216113-f8882480-c20c-11eb-9afb-b127e66a43da.png) - Cancel button and success toast at Leave Team modal ([#22373](https://github.com/RocketChat/Rocket.Chat/pull/22373)) @@ -1559,10 +1643,10 @@ - Convert and Move team permission ([#22350](https://github.com/RocketChat/Rocket.Chat/pull/22350)) - ### before - https://user-images.githubusercontent.com/45966964/114909360-5c04f100-9e1d-11eb-9363-f308e5d0be68.mp4 - - ### after + ### before + https://user-images.githubusercontent.com/45966964/114909360-5c04f100-9e1d-11eb-9363-f308e5d0be68.mp4 + + ### after https://user-images.githubusercontent.com/45966964/114909388-61fad200-9e1d-11eb-9bbe-114b55954a9f.mp4 - CORS error while interacting with any action button on Livechat ([#22150](https://github.com/RocketChat/Rocket.Chat/pull/22150)) @@ -1581,50 +1665,50 @@ - Members tab visual issues ([#22138](https://github.com/RocketChat/Rocket.Chat/pull/22138)) - ## Before - ![image](https://user-images.githubusercontent.com/27704687/119558283-95fbd800-bd77-11eb-91b4-91821f365bf3.png) - - ## After + ## Before + ![image](https://user-images.githubusercontent.com/27704687/119558283-95fbd800-bd77-11eb-91b4-91821f365bf3.png) + + ## After ![image](https://user-images.githubusercontent.com/27704687/119558120-6947c080-bd77-11eb-8ecb-7fedc07afa82.png) - Memory leak generated by Stream Cast usage ([#22329](https://github.com/RocketChat/Rocket.Chat/pull/22329)) - Stream Cast uses a different approach to broadcast data to the instances, it uses the DDP subscription method that requires a collection on the other side, if no collection exists with the given name `broadcast-stream` it caches in memory waiting for the collection to be set later. The cache is cleared only when a reconnection happens. - + Stream Cast uses a different approach to broadcast data to the instances, it uses the DDP subscription method that requires a collection on the other side, if no collection exists with the given name `broadcast-stream` it caches in memory waiting for the collection to be set later. The cache is cleared only when a reconnection happens. + This PR overrides the function that processes the data for that specific connection, preventing the cache and everything else to be processed since we already have our low-level listener to process the data. - Message box hiding on mobile view (Safari) ([#22212](https://github.com/RocketChat/Rocket.Chat/pull/22212)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/120404256-5b1c1600-c31c-11eb-96e9-860e4132db5f.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/120404256-5b1c1600-c31c-11eb-96e9-860e4132db5f.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/120404406-acc4a080-c31c-11eb-9efb-c2ad88664fda.png) - Missing burger menu on direct messages ([#22211](https://github.com/RocketChat/Rocket.Chat/pull/22211)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/120403671-09bf5700-c31b-11eb-92a1-a2f589bd85fc.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/120403671-09bf5700-c31b-11eb-92a1-a2f589bd85fc.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/120403693-1643af80-c31b-11eb-8027-dbdc4f560647.png) - Missing Throbber while thread list is loading ([#22316](https://github.com/RocketChat/Rocket.Chat/pull/22316)) - ### before - List was starting with no results even if there's results: - - ![image](https://user-images.githubusercontent.com/27704687/121606744-1e8ba100-ca25-11eb-9b31-706fb998d05f.png) - - ### after + ### before + List was starting with no results even if there's results: + + ![image](https://user-images.githubusercontent.com/27704687/121606744-1e8ba100-ca25-11eb-9b31-706fb998d05f.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/121606635-e97f4e80-ca24-11eb-81f7-af8b0cc41c89.png) - Not possible to edit some messages inside threads ([#22325](https://github.com/RocketChat/Rocket.Chat/pull/22325)) - ### Before - ![before](https://user-images.githubusercontent.com/27704687/121755733-4eeb4200-caee-11eb-9d77-1b498c38c478.gif) - - ### After + ### Before + ![before](https://user-images.githubusercontent.com/27704687/121755733-4eeb4200-caee-11eb-9d77-1b498c38c478.gif) + + ### After ![after](https://user-images.githubusercontent.com/27704687/121755736-514d9c00-caee-11eb-9897-78fcead172f2.gif) - Notifications not using user's name ([#22309](https://github.com/RocketChat/Rocket.Chat/pull/22309)) @@ -1651,10 +1735,10 @@ - Sidebar not closing when clicking on a channel ([#22271](https://github.com/RocketChat/Rocket.Chat/pull/22271)) - ### before - ![before](https://user-images.githubusercontent.com/27704687/121074843-c6e20100-c7aa-11eb-88db-76e39b57b064.gif) - - ### after + ### before + ![before](https://user-images.githubusercontent.com/27704687/121074843-c6e20100-c7aa-11eb-88db-76e39b57b064.gif) + + ### after ![after](https://user-images.githubusercontent.com/27704687/121074860-cb0e1e80-c7aa-11eb-9e96-06d75044b763.gif) - Sound notification is not emitted when the Omnichannel chat comes from another department ([#22291](https://github.com/RocketChat/Rocket.Chat/pull/22291)) @@ -1665,9 +1749,9 @@ - Undefined error when forwarding chats to offline department ([#22154](https://github.com/RocketChat/Rocket.Chat/pull/22154) by [@rafaelblink](https://github.com/rafaelblink)) - ![Screen Shot 2021-05-26 at 5 29 17 PM](https://user-images.githubusercontent.com/59577424/119727520-c495b380-be48-11eb-88a2-158017c7ad0a.png) - - Omnichannel agents are facing the error shown above when forwarding chats to offline departments. + ![Screen Shot 2021-05-26 at 5 29 17 PM](https://user-images.githubusercontent.com/59577424/119727520-c495b380-be48-11eb-88a2-158017c7ad0a.png) + + Omnichannel agents are facing the error shown above when forwarding chats to offline departments. The error usually takes place when the routing system algorithm is **Manual Selection**. - Unread bar in channel flash quickly and then disappear ([#22275](https://github.com/RocketChat/Rocket.Chat/pull/22275)) @@ -1698,15 +1782,15 @@ - Chore: Change modals for remove user from team && leave team ([#22141](https://github.com/RocketChat/Rocket.Chat/pull/22141)) - ![image](https://user-images.githubusercontent.com/40830821/119576154-93f14380-bd8e-11eb-8885-f889f2939bf4.png) + ![image](https://user-images.githubusercontent.com/40830821/119576154-93f14380-bd8e-11eb-8885-f889f2939bf4.png) ![image](https://user-images.githubusercontent.com/40830821/119576219-b5eac600-bd8e-11eb-832c-ea7a17a56bdd.png) - Chore: Check PR Title on every submission ([#22140](https://github.com/RocketChat/Rocket.Chat/pull/22140)) - Chore: Enable push gateway only if the server is registered ([#22346](https://github.com/RocketChat/Rocket.Chat/pull/22346) by [@lucassartor](https://github.com/lucassartor)) - Currently, when creating an unregistered server, the default value of the push gateway setting is set to true and is disabled (it can't be changed unless the server is registered). This is a wrong behavior as an unregistered server **can't** use the push gateway. - + Currently, when creating an unregistered server, the default value of the push gateway setting is set to true and is disabled (it can't be changed unless the server is registered). This is a wrong behavior as an unregistered server **can't** use the push gateway. + This PR creates a validation to check if the server is registered when enabling the push gateway. That way, even if the push gateway setting is turned on, but the server is unregistered, the push gateway **won't** work - it will behave like it is off. - Chore: Enforce TypeScript on Storybook ([#22317](https://github.com/RocketChat/Rocket.Chat/pull/22317)) @@ -1723,7 +1807,7 @@ - Chore: Update delete team modal to new design ([#22127](https://github.com/RocketChat/Rocket.Chat/pull/22127)) - Now the modal has only 2 steps (steps 1 and 2 were merged) + Now the modal has only 2 steps (steps 1 and 2 were merged) ![image](https://user-images.githubusercontent.com/40830821/119414580-2e398480-bcc6-11eb-9a47-515568257974.png) - Language update from LingoHub 🤖 on 2021-05-31Z ([#22196](https://github.com/RocketChat/Rocket.Chat/pull/22196)) @@ -1750,10 +1834,10 @@ - Regression: Missing flexDirection on select field ([#22300](https://github.com/RocketChat/Rocket.Chat/pull/22300)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/121425905-532a2a80-c949-11eb-885f-e8ddaf5c8d5c.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/121425905-532a2a80-c949-11eb-885f-e8ddaf5c8d5c.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/121425770-283fd680-c949-11eb-8d94-86886f174599.png) - Regression: RoomProvider using wrong types ([#22370](https://github.com/RocketChat/Rocket.Chat/pull/22370)) @@ -1894,50 +1978,50 @@ - **ENTERPRISE:** Introduce Load Rotation routing algorithm for Omnichannel ([#22090](https://github.com/RocketChat/Rocket.Chat/pull/22090) by [@rafaelblink](https://github.com/rafaelblink)) - This PR introduces a new Auto Chat Distribution (ACD) algorithm for Omnichannel: **Load Rotation**. - The algorithm distributes chats to agents one by one, which means that when a new chat arrives, the agent with the oldest routing assignment time will be selected to serve the chat, regardless of the number of chats in progress each agent has. - + This PR introduces a new Auto Chat Distribution (ACD) algorithm for Omnichannel: **Load Rotation**. + The algorithm distributes chats to agents one by one, which means that when a new chat arrives, the agent with the oldest routing assignment time will be selected to serve the chat, regardless of the number of chats in progress each agent has. + ![Screen Shot 2021-05-20 at 5 17 40 PM](https://user-images.githubusercontent.com/59577424/119043752-c61a3400-b98f-11eb-8543-f3176879af1d.png) - Back button for Omnichannel ([#21647](https://github.com/RocketChat/Rocket.Chat/pull/21647) by [@rafaelblink](https://github.com/rafaelblink)) - New Message Parser ([#21962](https://github.com/RocketChat/Rocket.Chat/pull/21962)) - The objective is to put an end to the confusion that we face having multiple parsers, and the problems that this brings, it is still experimental then users need to choose to use it. - - The benefits are multiple. no more unexpected cases or grammatical collisions (in addition to more flexible nested cases like bold within link labels). - Besides, we no longer render raw html, instead we use components, so the xss attacks are over (the easy ones at least). Without further discoveries and at the fronted, we only reder what is delivered thus improving our performance. + The objective is to put an end to the confusion that we face having multiple parsers, and the problems that this brings, it is still experimental then users need to choose to use it. + + The benefits are multiple. no more unexpected cases or grammatical collisions (in addition to more flexible nested cases like bold within link labels). + Besides, we no longer render raw html, instead we use components, so the xss attacks are over (the easy ones at least). Without further discoveries and at the fronted, we only reder what is delivered thus improving our performance. This can be used in multiple places, (message, alert, sidenav and in the entire mobile application.) - Option to notify failed login attempts to a channel ([#21968](https://github.com/RocketChat/Rocket.Chat/pull/21968)) - Option to prevent users from using Invisible status ([#20084](https://github.com/RocketChat/Rocket.Chat/pull/20084) by [@lucassartor](https://github.com/lucassartor)) - Add an `admin` option to allow/disallow the `Invisible` status option from all users. This option is available in the `Accounts` section. - - ![2021-01-06-11-55-22](https://user-images.githubusercontent.com/49413772/103782988-ebc52300-5016-11eb-8a29-dd540c21e11c.gif) - - If the option is turned off, the `users.setStatus` endpoint is also restricted from users trying to change their status to `Invisible`, throwing the following error: - ```json - { - "success": false, - "error": "Invisible status is disabled [error-not-allowed]", - "stack": "Error: Invisible status is disabled [error-not-allowed]\n at DDPCommon.MethodInvocation. (app/api/server/v1/users.js:425:13)\n at packages/dispatch_run-as-user.js:211:14\n at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1234:12)\n at Object.Meteor.runAsUser (packages/dispatch_run-as-user.js:210:33)\n at Object.post (app/api/server/v1/users.js:415:10)\n at app/api/server/api.js:394:82\n at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1234:12)\n at Object._internalRouteActionHandler [as action] (app/api/server/api.js:394:39)\n at Route.share.Route.Route._callEndpoint (packages/nimble_restivus/lib/route.coffee:150:32)\n at packages/nimble_restivus/lib/route.coffee:59:33\n at packages/simple_json-routes.js:98:9", - "errorType": "error-not-allowed", - "details": { - "method": "users.setStatus" - } - } + Add an `admin` option to allow/disallow the `Invisible` status option from all users. This option is available in the `Accounts` section. + + ![2021-01-06-11-55-22](https://user-images.githubusercontent.com/49413772/103782988-ebc52300-5016-11eb-8a29-dd540c21e11c.gif) + + If the option is turned off, the `users.setStatus` endpoint is also restricted from users trying to change their status to `Invisible`, throwing the following error: + ```json + { + "success": false, + "error": "Invisible status is disabled [error-not-allowed]", + "stack": "Error: Invisible status is disabled [error-not-allowed]\n at DDPCommon.MethodInvocation. (app/api/server/v1/users.js:425:13)\n at packages/dispatch_run-as-user.js:211:14\n at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1234:12)\n at Object.Meteor.runAsUser (packages/dispatch_run-as-user.js:210:33)\n at Object.post (app/api/server/v1/users.js:415:10)\n at app/api/server/api.js:394:82\n at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1234:12)\n at Object._internalRouteActionHandler [as action] (app/api/server/api.js:394:39)\n at Route.share.Route.Route._callEndpoint (packages/nimble_restivus/lib/route.coffee:150:32)\n at packages/nimble_restivus/lib/route.coffee:59:33\n at packages/simple_json-routes.js:98:9", + "errorType": "error-not-allowed", + "details": { + "method": "users.setStatus" + } + } ``` - Paginated and Filtered selects on new/edit unit ([#22052](https://github.com/RocketChat/Rocket.Chat/pull/22052) by [@rafaelblink](https://github.com/rafaelblink)) - REQUIRES https://github.com/RocketChat/Rocket.Chat.Fuselage/pull/447 - - Adds infinite scrolling selects to the units edit/create with the ability to be filtered by text as well - - ![Screen Shot 2021-05-17 at 9 24 19 AM](https://user-images.githubusercontent.com/20868078/118487999-abc32a80-b6f1-11eb-8d58-d031111ea0fb.png) - + REQUIRES https://github.com/RocketChat/Rocket.Chat.Fuselage/pull/447 + + Adds infinite scrolling selects to the units edit/create with the ability to be filtered by text as well + + ![Screen Shot 2021-05-17 at 9 24 19 AM](https://user-images.githubusercontent.com/20868078/118487999-abc32a80-b6f1-11eb-8d58-d031111ea0fb.png) + This Affects the monitors and departments inputs - Remove exif metadata from uploaded files ([#22044](https://github.com/RocketChat/Rocket.Chat/pull/22044)) @@ -1965,13 +2049,17 @@ - Inconsistent and misleading 2FA settings ([#22042](https://github.com/RocketChat/Rocket.Chat/pull/22042) by [@lucassartor](https://github.com/lucassartor)) - Currently, there are some inconsistencies and incorrect behaviors on the 2FA settings, such as: - - - When disabling the TOTP 2FA, all 2FA are disabled; - - There are no option to disable only the TOTP 2FA; - - If 2FA are disabled, the other settings aren't blocked (the e-mail 2FA setting, for example); - - It lacks some labels to warn the user of some specific 2FA options. - + Currently, there are some inconsistencies and incorrect behaviors on the 2FA settings, such as: + + + - When disabling the TOTP 2FA, all 2FA are disabled; + + - There are no option to disable only the TOTP 2FA; + + - If 2FA are disabled, the other settings aren't blocked (the e-mail 2FA setting, for example); + + - It lacks some labels to warn the user of some specific 2FA options. + This PR looks to fix those issues. - LDAP port setting input type to allow only numbers ([#21912](https://github.com/RocketChat/Rocket.Chat/pull/21912) by [@Deepak-learner](https://github.com/Deepak-learner)) @@ -1993,16 +2081,16 @@ - **APPS:** Scheduler duplicating recurrent tasks after server restart ([#21866](https://github.com/RocketChat/Rocket.Chat/pull/21866)) - Reintroduces the old method for creating recurring tasks in the apps' scheduler bridge to ensure tasks won't be duplicated. - - By introducing the [`skipImmediate` property option](https://github.com/RocketChat/Rocket.Chat/pull/21353) at the [`scheduleRecurring`](https://github.com/RocketChat/Rocket.Chat/blob/f8171f464ed8a7487795651767695fb33a1c709e/app/apps/server/bridges/scheduler.js#L119) method, the `every` method from _agenda.js_, which ensured no duplicates were created, was removed in favor of a more manual procedure. The new procedure was not taking into account the management of duplicates and as a result multiple copies of the same task could be created and they would get executed at the same time. - + Reintroduces the old method for creating recurring tasks in the apps' scheduler bridge to ensure tasks won't be duplicated. + + By introducing the [`skipImmediate` property option](https://github.com/RocketChat/Rocket.Chat/pull/21353) at the [`scheduleRecurring`](https://github.com/RocketChat/Rocket.Chat/blob/f8171f464ed8a7487795651767695fb33a1c709e/app/apps/server/bridges/scheduler.js#L119) method, the `every` method from _agenda.js_, which ensured no duplicates were created, was removed in favor of a more manual procedure. The new procedure was not taking into account the management of duplicates and as a result multiple copies of the same task could be created and they would get executed at the same time. + In the case of server restarts, every time this event happened and the app had the `startupSetting` configured to use _recurring tasks_, they would get recreated the same number of times. In the case of a server that restarts frequently (_n_ times), there would be the same (_n_) number of tasks duplicated (and running) in the system. - **ENTERPRISE:** Omnichannel Monitors can't forward chats to departments that they are not supervising ([#22128](https://github.com/RocketChat/Rocket.Chat/pull/22128)) - Currently, Omnichannel Monitors just can't forward chats to a department that is part of a `Business Unit` they're not supervising. This issue is causing critical problems on customer operations since this behaviour is not by design. - The reason this issue is taking place is that, by design, Monitors just have access to departments related to the `Business Units` they're monitoring, but this restriction is designed only for Omnichannel management areas, which means in case the monitor is, also, an agent, they're supposed to be able to forward a chat to any available departments regardless the `Business Units` it's associated with. + Currently, Omnichannel Monitors just can't forward chats to a department that is part of a `Business Unit` they're not supervising. This issue is causing critical problems on customer operations since this behaviour is not by design. + The reason this issue is taking place is that, by design, Monitors just have access to departments related to the `Business Units` they're monitoring, but this restriction is designed only for Omnichannel management areas, which means in case the monitor is, also, an agent, they're supposed to be able to forward a chat to any available departments regardless the `Business Units` it's associated with. So, initially, the restriction was implemented on the `Department Model` and, now, we're implementing the logic properly and introducing a new parameter to department endpoints, so the client will define which type of departments it needs. - **ENTERPRISE:** Omnichannel Monitors can't forward chats to departments that they are not supervising ([#22142](https://github.com/RocketChat/Rocket.Chat/pull/22142)) @@ -2041,18 +2129,18 @@ - Correcting a the wrong Archived label in edit room ([#21717](https://github.com/RocketChat/Rocket.Chat/pull/21717) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - ![image](https://user-images.githubusercontent.com/45966964/116584997-3cd78a80-a918-11eb-81fa-8a7eb5318ae9.png) - + ![image](https://user-images.githubusercontent.com/45966964/116584997-3cd78a80-a918-11eb-81fa-8a7eb5318ae9.png) + A label exists for Archived, and it has not been used. So I replaced it with the existing one. the label 'Archived' does not exist. - Custom OAuth not being completely deleted ([#21637](https://github.com/RocketChat/Rocket.Chat/pull/21637) by [@siva2204](https://github.com/siva2204)) - Directory Table's Sort Function ([#21921](https://github.com/RocketChat/Rocket.Chat/pull/21921)) - ### TableRow Margin Issue: - ![image](https://user-images.githubusercontent.com/27704687/116907348-d6a07f80-ac17-11eb-9411-edfe0906bfe1.png) - - ### Table Sort Action Issue: + ### TableRow Margin Issue: + ![image](https://user-images.githubusercontent.com/27704687/116907348-d6a07f80-ac17-11eb-9411-edfe0906bfe1.png) + + ### Table Sort Action Issue: ![directory](https://user-images.githubusercontent.com/27704687/116907441-f20b8a80-ac17-11eb-8790-bfce19e89a67.gif) - Discussion names showing a random value ([#22172](https://github.com/RocketChat/Rocket.Chat/pull/22172)) @@ -2063,54 +2151,54 @@ - Emails being sent with HTML entities getting escaped multiple times ([#21994](https://github.com/RocketChat/Rocket.Chat/pull/21994) by [@bhavayAnand9](https://github.com/bhavayAnand9)) - fixes an issue where if password contains special HTML character like &, in the email it would end up something like `&amp;` - - - password was going through multiple escapeHTML function calls - `secure&123 => secure&123 => secure&amp;123 + fixes an issue where if password contains special HTML character like &, in the email it would end up something like `&amp;` + + + password was going through multiple escapeHTML function calls + `secure&123 => secure&123 => secure&amp;123 ` - Error when you look at the members list of a room in which you are not a member ([#21952](https://github.com/RocketChat/Rocket.Chat/pull/21952) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - Before, when you look at the members of a room in which you are not a member the app crashed, i corrected this problem. - Indeed, there was a check on each currentSubscription. to see if it was not undefined except on currentSubscription.blocker - + Before, when you look at the members of a room in which you are not a member the app crashed, i corrected this problem. + Indeed, there was a check on each currentSubscription. to see if it was not undefined except on currentSubscription.blocker + https://user-images.githubusercontent.com/45966964/117087470-d3101400-ad4f-11eb-8f44-0ebca830a4d8.mp4 - errors when viewing a room that you're not subscribed to ([#21984](https://github.com/RocketChat/Rocket.Chat/pull/21984)) - Files list will not show deleted files. ([#21732](https://github.com/RocketChat/Rocket.Chat/pull/21732) by [@Darshilp326](https://github.com/Darshilp326)) - When you delete files from the header option, deleted files will not be shown. - + When you delete files from the header option, deleted files will not be shown. + https://user-images.githubusercontent.com/55157259/115730786-38552400-a3a4-11eb-9684-7f510920db66.mp4 - Fixed the fact that when a team was deleted, not all channels were unlinked from the team ([#21942](https://github.com/RocketChat/Rocket.Chat/pull/21942) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - Fixed the fact that when a team was deleted, not all channels were unlinked from the team. Only the first room of the rooms list was unlinked. - - After the fix, there is nos more errors: - - + Fixed the fact that when a team was deleted, not all channels were unlinked from the team. Only the first room of the rooms list was unlinked. + + After the fix, there is nos more errors: + + https://user-images.githubusercontent.com/45966964/117055182-2a47c180-ad1b-11eb-806f-07fb3fa7ec12.mp4 - Fixing Jitsi call ended Issue. ([#21808](https://github.com/RocketChat/Rocket.Chat/pull/21808)) - The new rewrite in react of contextual call component broke the Jitsi "click to join" messages. The issue being after 10 seconds of initiating the call, the message "click to join" always returned "Call Ended" even if the call was still going on. - This was due to the fact that after closing the contextual bar, the react component gets unmounted and we are not able to keep track of ongoing call and increase jitsi room timeout. - - This PR solves this issue by using the setInterval methods on component will unmount. When the call component unmounts, we keep on checking the state of jitsi call and based on conditions increase the jitsi room timeout. After the call is ended all setInterval calls are closed. - + The new rewrite in react of contextual call component broke the Jitsi "click to join" messages. The issue being after 10 seconds of initiating the call, the message "click to join" always returned "Call Ended" even if the call was still going on. + This was due to the fact that after closing the contextual bar, the react component gets unmounted and we are not able to keep track of ongoing call and increase jitsi room timeout. + + This PR solves this issue by using the setInterval methods on component will unmount. When the call component unmounts, we keep on checking the state of jitsi call and based on conditions increase the jitsi room timeout. After the call is ended all setInterval calls are closed. + This PR also removes the implementation of HEARTBEAT events of JitsiBridge. This is because this is no longer needed and all logic is being taken care of by the unmount function. - Handle NPS errors instead of throwing them ([#21945](https://github.com/RocketChat/Rocket.Chat/pull/21945)) - Header Tag Visual Issues ([#21991](https://github.com/RocketChat/Rocket.Chat/pull/21991)) - ### Normal - ![image](https://user-images.githubusercontent.com/27704687/117504793-69635600-af59-11eb-8b79-9d8f631490ee.png) - - ### Hover + ### Normal + ![image](https://user-images.githubusercontent.com/27704687/117504793-69635600-af59-11eb-8b79-9d8f631490ee.png) + + ### Hover ![image](https://user-images.githubusercontent.com/27704687/117504934-97489a80-af59-11eb-87c3-0a62731e9ce3.png) - Horizontal scrollbar not showing on tables ([#21852](https://github.com/RocketChat/Rocket.Chat/pull/21852)) @@ -2119,17 +2207,17 @@ - iFrame size on embedded videos ([#21992](https://github.com/RocketChat/Rocket.Chat/pull/21992)) - ### Before - ![image](https://user-images.githubusercontent.com/27704687/117508802-8bf86d80-af5f-11eb-9eb8-29e55b73eac5.png) - - ### After + ### Before + ![image](https://user-images.githubusercontent.com/27704687/117508802-8bf86d80-af5f-11eb-9eb8-29e55b73eac5.png) + + ### After ![image](https://user-images.githubusercontent.com/27704687/117508870-a4688800-af5f-11eb-9176-7f24de5fc424.png) - Incorrect error message when opening channel in anonymous read ([#22066](https://github.com/RocketChat/Rocket.Chat/pull/22066) by [@lucassartor](https://github.com/lucassartor)) - Every time you open a public channel with threads in it when using anonymous read an `Incorrect User` error will be thrown. - This is an incorrect behaviour as everything that is public should be valid for an anonymous user. - + Every time you open a public channel with threads in it when using anonymous read an `Incorrect User` error will be thrown. + This is an incorrect behaviour as everything that is public should be valid for an anonymous user. + Some files are adapted to that and have already removed this kind of incorrect error, but there are some that need some fix, this PR aims to do that. - Incorrect Team's Info spacing ([#22021](https://github.com/RocketChat/Rocket.Chat/pull/22021)) @@ -2142,19 +2230,21 @@ - Make the FR translation consistent with the 'room' translation + typos ([#21913](https://github.com/RocketChat/Rocket.Chat/pull/21913) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - In the FR translation files, there were two terms that were used to refer to **'room'**: - - 'salon' (149 times used) - - ![image](https://user-images.githubusercontent.com/45966964/116829860-ac62a980-aba6-11eb-8212-e6f15ed0af82.png) - - - 'salle' (46 times used) - - ![image](https://user-images.githubusercontent.com/45966964/116829871-be444c80-aba6-11eb-9b42-e213fee6586a.png) - - The problem is that both were used in the same context and sometimes even in the same option list. - However, since 'salon' is a better translation and was also in the majority, I used the translation 'salon' wherever 'salle' was marked. - - For example: + In the FR translation files, there were two terms that were used to refer to **'room'**: + + - 'salon' (149 times used) + + ![image](https://user-images.githubusercontent.com/45966964/116829860-ac62a980-aba6-11eb-8212-e6f15ed0af82.png) + + + - 'salle' (46 times used) + + ![image](https://user-images.githubusercontent.com/45966964/116829871-be444c80-aba6-11eb-9b42-e213fee6586a.png) + + The problem is that both were used in the same context and sometimes even in the same option list. + However, since 'salon' is a better translation and was also in the majority, I used the translation 'salon' wherever 'salle' was marked. + + For example: ![image](https://user-images.githubusercontent.com/45966964/116830523-1da45b80-abab-11eb-81f8-5225d51cecc6.png) - Maximum 25 channels can be loaded in the teams' channels list ([#21708](https://github.com/RocketChat/Rocket.Chat/pull/21708) by [@Jeanstaquet](https://github.com/Jeanstaquet)) @@ -2169,8 +2259,8 @@ - No warning message is sent when user is removed from a team's main channel ([#21949](https://github.com/RocketChat/Rocket.Chat/pull/21949)) - - Send a warning message to a team's main channel when a user is removed from the team; - - Trigger events while removing a user from a team's main channel; + - Send a warning message to a team's main channel when a user is removed from the team; + - Trigger events while removing a user from a team's main channel; - Fix `usersCount` field in the team's main room when a user is removed from the team (`usersCount` is now decreased by 1). - Not possible accept video call if "Hide right sidebar with click" is enabled ([#22175](https://github.com/RocketChat/Rocket.Chat/pull/22175)) @@ -2191,14 +2281,14 @@ - Prevent the userInfo tab to return 'User not found' each time if a certain member of a DM group has been deleted ([#21970](https://github.com/RocketChat/Rocket.Chat/pull/21970) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - Prevent the userInfo tab to return 'User not found' if a member of a DM group has been deleted. - This happens if the user that has been deleted is the one originally displayed on the userInfo tab in a DM group with >2 users. - + Prevent the userInfo tab to return 'User not found' if a member of a DM group has been deleted. + This happens if the user that has been deleted is the one originally displayed on the userInfo tab in a DM group with >2 users. + https://user-images.githubusercontent.com/45966964/117221081-db785580-ae08-11eb-9b33-2314a99eb037.mp4 - Prune messages not cleaning up unread threads ([#21326](https://github.com/RocketChat/Rocket.Chat/pull/21326) by [@renancleyson-dev](https://github.com/renancleyson-dev)) - Fixes permanent unread messages when admin prune at least two different thread messages in the room that were unread by some user. + Fixes permanent unread messages when admin prune at least two different thread messages in the room that were unread by some user. ![screencapture-localhost-3000-channel-general-thread-2021-03-26-13_17_16](https://user-images.githubusercontent.com/43624243/112678973-62b9cd00-8e4a-11eb-9af9-56f17cc66baf.png) - Redirect on remove user from channel by user profile tab ([#21951](https://github.com/RocketChat/Rocket.Chat/pull/21951)) @@ -2209,8 +2299,8 @@ - Removed fields from User Info for which the user doesn't have permissions. ([#20923](https://github.com/RocketChat/Rocket.Chat/pull/20923) by [@Darshilp326](https://github.com/Darshilp326)) - Removed LastLogin, CreatedAt and Roles for users who don't have permission. - + Removed LastLogin, CreatedAt and Roles for users who don't have permission. + https://user-images.githubusercontent.com/55157259/109381351-f2c62e80-78ff-11eb-9289-e11072bf62f8.mp4 - Replace `query` param by `name`, `username` and `status` on the `teams.members` endpoint ([#21539](https://github.com/RocketChat/Rocket.Chat/pull/21539)) @@ -2223,39 +2313,39 @@ - Unable to edit a 'direct' room setting in the admin due to the room name ([#21636](https://github.com/RocketChat/Rocket.Chat/pull/21636) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - When you are in the admin and want to change a room 'd' setting, it doesn't work because it takes into account the name that is set automatically and therefore tries to save that name. Since the name is not valid and should not be registered, we cannot change the setting for the 'd' room. - I made sure that when you want to change a setting in a 'd' room, that you don't take the name into account - - - https://user-images.githubusercontent.com/45966964/115150919-cd85af00-a06a-11eb-9667-ef3dcfc5adb6.mp4 - - + When you are in the admin and want to change a room 'd' setting, it doesn't work because it takes into account the name that is set automatically and therefore tries to save that name. Since the name is not valid and should not be registered, we cannot change the setting for the 'd' room. + I made sure that when you want to change a setting in a 'd' room, that you don't take the name into account + + + https://user-images.githubusercontent.com/45966964/115150919-cd85af00-a06a-11eb-9667-ef3dcfc5adb6.mp4 + + Behind the scene, the name is not saved - Unable to edit a user who does not have an email via the admin or via the user's profile ([#21626](https://github.com/RocketChat/Rocket.Chat/pull/21626) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - If a user does not have an email address, they cannot change it via their profile or via the admin. I fixed this issue. I have created several profiles and there was one that didn't have an email, I don't know how I did it, I am working on it. I had not modified the db to delete his email, hence the fix - - in admin - - https://user-images.githubusercontent.com/45966964/115112617-9b9b1c80-9f86-11eb-8e3a-950c3c1a1746.mp4 - - - - in the user profile - + If a user does not have an email address, they cannot change it via their profile or via the admin. I fixed this issue. I have created several profiles and there was one that didn't have an email, I don't know how I did it, I am working on it. I had not modified the db to delete his email, hence the fix + + in admin + + https://user-images.githubusercontent.com/45966964/115112617-9b9b1c80-9f86-11eb-8e3a-950c3c1a1746.mp4 + + + + in the user profile + https://user-images.githubusercontent.com/45966964/115112620-a0f86700-9f86-11eb-97b1-56eaba42216b.mp4 - Unable to get channels, sort by most recent message ([#21701](https://github.com/RocketChat/Rocket.Chat/pull/21701) by [@sumukhah](https://github.com/sumukhah)) - Unable to update app manually ([#21215](https://github.com/RocketChat/Rocket.Chat/pull/21215)) - It allows for update of apps using a zip file. - - When installing apps using the zip file, either by url or the file form, if the app was already installed, an error would be thrown stating the condition and forbidding the installation. Now, when sending a zip file of an app that is already installed, the user is presented with the following modal: - - ![2021-04-30-113936_627x235_scrot](https://user-images.githubusercontent.com/733282/116711383-2cbbbb80-a9a9-11eb-8c77-22d6802cb9f5.png) - + It allows for update of apps using a zip file. + + When installing apps using the zip file, either by url or the file form, if the app was already installed, an error would be thrown stating the condition and forbidding the installation. Now, when sending a zip file of an app that is already installed, the user is presented with the following modal: + + ![2021-04-30-113936_627x235_scrot](https://user-images.githubusercontent.com/733282/116711383-2cbbbb80-a9a9-11eb-8c77-22d6802cb9f5.png) + If the app also requires permissions to be reviewed, the modal that handles permission reviews will be shown after this one is accepted. - Unpin message reactivity ([#22029](https://github.com/RocketChat/Rocket.Chat/pull/22029)) @@ -2266,20 +2356,20 @@ - User Impersonation through sendMessage API ([#20391](https://github.com/RocketChat/Rocket.Chat/pull/20391) by [@lucassartor](https://github.com/lucassartor)) - Create a new permission: `message-impersonate`. For new installs only bot role will have the permission and for updating installs the permission will also be given to user role, so it won't break running deployments. - - If a message is being sent with `avatar` or `alias` properties, it validates if the sender has the `message-impersonate` permission, if not, an error is throwed: - ```json - { - "success": false, - "error": "Not enough permission", - "stack": "Error: Not enough permission\n ..." - } + Create a new permission: `message-impersonate`. For new installs only bot role will have the permission and for updating installs the permission will also be given to user role, so it won't break running deployments. + + If a message is being sent with `avatar` or `alias` properties, it validates if the sender has the `message-impersonate` permission, if not, an error is throwed: + ```json + { + "success": false, + "error": "Not enough permission", + "stack": "Error: Not enough permission\n ..." + } ``` - Visibility of burger menu on certain width ([#20736](https://github.com/RocketChat/Rocket.Chat/pull/20736) by [@yash-rajpal](https://github.com/yash-rajpal)) - Burger was not visible on a certain width, specifically between 600 to 780. if width is more than 780px sidebar is shown, if less than 600 then burger icon was shown. But it wasn't shown between 600px to 780 px. + Burger was not visible on a certain width, specifically between 600 to 780. if width is more than 780px sidebar is shown, if less than 600 then burger icon was shown. But it wasn't shown between 600px to 780 px. It was because for showing burger icon we were only checking for `isMobile` which is lenght only less than 600. So i added one more check for condition if length is less than 780 px. - When closing chats a comment is always required ([#21947](https://github.com/RocketChat/Rocket.Chat/pull/21947)) @@ -2294,8 +2384,8 @@ - Wrong icon on "Move to team" option in the channel info actions ([#21944](https://github.com/RocketChat/Rocket.Chat/pull/21944)) - ![image](https://user-images.githubusercontent.com/40830821/117061659-d9bf6c80-acf8-11eb-8e29-be47e702dedd.png) - + ![image](https://user-images.githubusercontent.com/40830821/117061659-d9bf6c80-acf8-11eb-8e29-be47e702dedd.png) + Depends on https://github.com/RocketChat/Rocket.Chat.Fuselage/pull/444
@@ -2312,8 +2402,10 @@ - Add two more test cases to the slash-command test suite ([#21317](https://github.com/RocketChat/Rocket.Chat/pull/21317) by [@EduardoPicolo](https://github.com/EduardoPicolo)) - Added two more test cases to the slash-command test suite: - - 'should return an error when the command does not exist''; + Added two more test cases to the slash-command test suite: + + - 'should return an error when the command does not exist''; + - 'should return an error when no command is provided'; - Bump actions/stale from v3.0.8 to v3.0.18 ([#21877](https://github.com/RocketChat/Rocket.Chat/pull/21877) by [@dependabot[bot]](https://github.com/dependabot[bot])) @@ -2348,9 +2440,9 @@ - i18n: Add missing translation string in account preference ([#21448](https://github.com/RocketChat/Rocket.Chat/pull/21448) by [@sumukhah](https://github.com/sumukhah)) - "Test Desktop Notifications" was missing in translation, Added to the file. - Screenshot 2021-04-05 at 3 58 01 PM - + "Test Desktop Notifications" was missing in translation, Added to the file. + Screenshot 2021-04-05 at 3 58 01 PM + Screenshot 2021-04-05 at 3 58 32 PM - i18n: Correct a typo in German ([#21711](https://github.com/RocketChat/Rocket.Chat/pull/21711) by [@Jeanstaquet](https://github.com/Jeanstaquet)) @@ -2377,10 +2469,10 @@ - Regression: discussions display on sidebar ([#22157](https://github.com/RocketChat/Rocket.Chat/pull/22157)) - ### group by type active - ![image](https://user-images.githubusercontent.com/27704687/119741996-37a92500-be5d-11eb-8b36-4067a7a229f1.png) - - ### group by type inactive + ### group by type active + ![image](https://user-images.githubusercontent.com/27704687/119741996-37a92500-be5d-11eb-8b36-4067a7a229f1.png) + + ### group by type inactive ![image](https://user-images.githubusercontent.com/27704687/119742054-56a7b700-be5d-11eb-8810-e31d4216f573.png) - regression: fix departments with empty ancestors not being returned ([#22068](https://github.com/RocketChat/Rocket.Chat/pull/22068)) @@ -2391,8 +2483,8 @@ - regression: Fix Users list in the Administration ([#22034](https://github.com/RocketChat/Rocket.Chat/pull/22034) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - The app crashed if no custom fields for user profiles have been created by the admin. I fixed this issue. This bug was introduced by a recent commit. - + The app crashed if no custom fields for user profiles have been created by the admin. I fixed this issue. This bug was introduced by a recent commit. + https://user-images.githubusercontent.com/45966964/118210838-5b3a9b80-b46b-11eb-9fe5-5b813848190c.mp4 - Regression: Improve migration 225 ([#22099](https://github.com/RocketChat/Rocket.Chat/pull/22099)) @@ -2409,7 +2501,7 @@ - Regression: not allowed to edit roles due to a new verification ([#22159](https://github.com/RocketChat/Rocket.Chat/pull/22159)) - introduced by https://github.com/RocketChat/Rocket.Chat/pull/21905 + introduced by https://github.com/RocketChat/Rocket.Chat/pull/21905 ![Peek 2021-05-26 22-21](https://user-images.githubusercontent.com/27704687/119750970-b9567e00-be70-11eb-9d52-04c8595950df.gif) - regression: Select Team Modal margin ([#22030](https://github.com/RocketChat/Rocket.Chat/pull/22030)) @@ -2420,10 +2512,10 @@ - Regression: Visual issue on sort list item ([#22158](https://github.com/RocketChat/Rocket.Chat/pull/22158)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/119743703-d84d1400-be60-11eb-97cc-c8256b2c8b07.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/119743703-d84d1400-be60-11eb-97cc-c8256b2c8b07.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/119743638-b18edd80-be60-11eb-828d-22cc5e1b2f5b.png) - Release 3.14.2 ([#22135](https://github.com/RocketChat/Rocket.Chat/pull/22135)) @@ -2609,12 +2701,12 @@ - Paginated and Filtered selects on new/edit unit ([#22052](https://github.com/RocketChat/Rocket.Chat/pull/22052) by [@rafaelblink](https://github.com/rafaelblink)) - REQUIRES https://github.com/RocketChat/Rocket.Chat.Fuselage/pull/447 - - Adds infinite scrolling selects to the units edit/create with the ability to be filtered by text as well - - ![Screen Shot 2021-05-17 at 9 24 19 AM](https://user-images.githubusercontent.com/20868078/118487999-abc32a80-b6f1-11eb-8d58-d031111ea0fb.png) - + REQUIRES https://github.com/RocketChat/Rocket.Chat.Fuselage/pull/447 + + Adds infinite scrolling selects to the units edit/create with the ability to be filtered by text as well + + ![Screen Shot 2021-05-17 at 9 24 19 AM](https://user-images.githubusercontent.com/20868078/118487999-abc32a80-b6f1-11eb-8d58-d031111ea0fb.png) + This Affects the monitors and departments inputs ### 🚀 Improvements @@ -2690,18 +2782,24 @@ - New set of rules for client code ([#21318](https://github.com/RocketChat/Rocket.Chat/pull/21318)) - This _small_ PR does the following: - - - Now **React** is the web client's first-class citizen, being **loaded before Blaze**. Thus, `BlazeLayout` calls render templates inside of a React component (`BlazeLayoutWrapper`); - - Main client startup code, including polyfills, is written in **TypeScript**; - - At the moment, routes are treated as regular startup code; it's expected that `FlowRouter` will be deprecated in favor of a new routing library; - - **React** was updated to major version **17**, deprecating the usage of `React` as namespace (e.g. use `memo()` instead of `React.memo()`); - - The `client/` and `ee/client/` directory are linted with a **custom ESLint configuration** that includes: - - **Prettier**; - - `react-hooks/*` rules for TypeScript files; - - `react/no-multi-comp`, enforcing the rule of **one single React component per module**; - - `react/display-name`, which enforces that **React components must have a name for debugging**; - - `import/named`, avoiding broken named imports. + This _small_ PR does the following: + + + - Now **React** is the web client's first-class citizen, being **loaded before Blaze**. Thus, `BlazeLayout` calls render templates inside of a React component (`BlazeLayoutWrapper`); + + - Main client startup code, including polyfills, is written in **TypeScript**; + + - At the moment, routes are treated as regular startup code; it's expected that `FlowRouter` will be deprecated in favor of a new routing library; + + - **React** was updated to major version **17**, deprecating the usage of `React` as namespace (e.g. use `memo()` instead of `React.memo()`); + + - The `client/` and `ee/client/` directory are linted with a **custom ESLint configuration** that includes: + - **Prettier**; + - `react-hooks/*` rules for TypeScript files; + - `react/no-multi-comp`, enforcing the rule of **one single React component per module**; + - `react/display-name`, which enforces that **React components must have a name for debugging**; + - `import/named`, avoiding broken named imports. + - A bunch of components were refactored to match the new ESLint rules. - On Hold system messages ([#21360](https://github.com/RocketChat/Rocket.Chat/pull/21360) by [@rafaelblink](https://github.com/rafaelblink)) @@ -2710,12 +2808,15 @@ - Password history ([#21607](https://github.com/RocketChat/Rocket.Chat/pull/21607)) - - Store each user's previously used passwords in a `passwordHistory` field (in the `users` record); - - Users' previously used passwords are stored in their `passwordHistory` even when the setting is disabled; - - Add "Password History" setting -- when enabled, it blocks users from reusing their most recent passwords; - - Convert `comparePassword` file to TypeScript. - - ![Password_Change](https://user-images.githubusercontent.com/36537004/115035168-ac726200-9ea2-11eb-93c6-fc8182ba5f3f.png) + - Store each user's previously used passwords in a `passwordHistory` field (in the `users` record); + + - Users' previously used passwords are stored in their `passwordHistory` even when the setting is disabled; + + - Add "Password History" setting -- when enabled, it blocks users from reusing their most recent passwords; + + - Convert `comparePassword` file to TypeScript. + + ![Password_Change](https://user-images.githubusercontent.com/36537004/115035168-ac726200-9ea2-11eb-93c6-fc8182ba5f3f.png) ![Password_History](https://user-images.githubusercontent.com/36537004/115035175-ad0af880-9ea2-11eb-9f40-94c6327a9854.png) - REST endpoint `teams.update` ([#21134](https://github.com/RocketChat/Rocket.Chat/pull/21134) by [@g-thome](https://github.com/g-thome)) @@ -2733,14 +2834,18 @@ - Add error messages to the creation of channels or usernames containing reserved words ([#21016](https://github.com/RocketChat/Rocket.Chat/pull/21016)) - Display error messages when the user attempts to create or edit users' or channels' names with any of the following words (**case-insensitive**): - - admin; - - administrator; - - system; - - user. - ![create-channel](https://user-images.githubusercontent.com/36537004/110132223-b421ef80-7da9-11eb-82bc-f0d4e1df967f.png) - ![register-username](https://user-images.githubusercontent.com/36537004/110132234-b71ce000-7da9-11eb-904e-580233625951.png) - ![change-channel](https://user-images.githubusercontent.com/36537004/110143057-96f31e00-7db5-11eb-994a-39ae9e63392e.png) + Display error messages when the user attempts to create or edit users' or channels' names with any of the following words (**case-insensitive**): + + - admin; + + - administrator; + + - system; + + - user. + ![create-channel](https://user-images.githubusercontent.com/36537004/110132223-b421ef80-7da9-11eb-82bc-f0d4e1df967f.png) + ![register-username](https://user-images.githubusercontent.com/36537004/110132234-b71ce000-7da9-11eb-904e-580233625951.png) + ![change-channel](https://user-images.githubusercontent.com/36537004/110143057-96f31e00-7db5-11eb-994a-39ae9e63392e.png) ![change-username](https://user-images.githubusercontent.com/36537004/110143065-98244b00-7db5-11eb-9d13-afc5dc9866de.png) - add permission check when adding a channel to a team ([#21689](https://github.com/RocketChat/Rocket.Chat/pull/21689) by [@g-thome](https://github.com/g-thome)) @@ -2765,7 +2870,8 @@ - Resize custom emojis on upload instead of saving at max res ([#21593](https://github.com/RocketChat/Rocket.Chat/pull/21593)) - - Create new MediaService (ideally, should be in charge of all media-related operations) + - Create new MediaService (ideally, should be in charge of all media-related operations) + - Resize emojis to 128x128 ### 🐛 Bug fixes @@ -2785,25 +2891,25 @@ - Allows more than 25 discussions/files to be loaded in the contextualbar ([#21511](https://github.com/RocketChat/Rocket.Chat/pull/21511) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - In some places, you could not load more than 25 threads/discussions/files on the screen when searching the lists in the contextualbar. - Threads & list are numbered for a better view of the solution - - + In some places, you could not load more than 25 threads/discussions/files on the screen when searching the lists in the contextualbar. + Threads & list are numbered for a better view of the solution + + https://user-images.githubusercontent.com/45966964/114222225-93335800-996e-11eb-833f-568e83129aae.mp4 - Allows more than 25 threads to be loaded, fixes #21507 ([#21508](https://github.com/RocketChat/Rocket.Chat/pull/21508) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - Allows to display more than 25 users maximum in the users list ([#21518](https://github.com/RocketChat/Rocket.Chat/pull/21518) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - Now when you scroll to the bottom of the users list, it shows more users. Before the fix, the limit for the query for loadMore was calculated so that no additional users could be loaded. - - Before - - https://user-images.githubusercontent.com/45966964/114249739-baece500-999b-11eb-9bb0-3a5bcee18ad8.mp4 - - After - - + Now when you scroll to the bottom of the users list, it shows more users. Before the fix, the limit for the query for loadMore was calculated so that no additional users could be loaded. + + Before + + https://user-images.githubusercontent.com/45966964/114249739-baece500-999b-11eb-9bb0-3a5bcee18ad8.mp4 + + After + + https://user-images.githubusercontent.com/45966964/114249895-364e9680-999c-11eb-985c-47aedc763488.mp4 - App installation from marketplace not correctly displaying the permissions ([#21470](https://github.com/RocketChat/Rocket.Chat/pull/21470)) @@ -2870,19 +2976,19 @@ - Margins on contextual bar information ([#21457](https://github.com/RocketChat/Rocket.Chat/pull/21457)) - ### Room - **Before** - ![image](https://user-images.githubusercontent.com/27704687/115080812-ba8fa500-9ed9-11eb-9078-3625603bf92b.png) - - **After** - ![image](https://user-images.githubusercontent.com/27704687/115080966-e9a61680-9ed9-11eb-929f-6516c1563e99.png) - - ### Livechat + ### Room + **Before** + ![image](https://user-images.githubusercontent.com/27704687/115080812-ba8fa500-9ed9-11eb-9078-3625603bf92b.png) + + **After** + ![image](https://user-images.githubusercontent.com/27704687/115080966-e9a61680-9ed9-11eb-929f-6516c1563e99.png) + + ### Livechat ![image](https://user-images.githubusercontent.com/27704687/113640101-1859fc80-9651-11eb-88f8-09a899953988.png) - Message Block ordering ([#21464](https://github.com/RocketChat/Rocket.Chat/pull/21464)) - Reactions should come before reply button. + Reactions should come before reply button. ![image](https://user-images.githubusercontent.com/40830821/113748926-6f0e1780-96df-11eb-93a5-ddcfa891413e.png) - Message link null corrupts message rendering ([#21579](https://github.com/RocketChat/Rocket.Chat/pull/21579) by [@g-thome](https://github.com/g-thome)) @@ -2935,15 +3041,19 @@ - Typos/missing elements in the French translation ([#21525](https://github.com/RocketChat/Rocket.Chat/pull/21525) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - - I have corrected some typos in the translation - - I added a translation for missing words - - I took the opportunity to correct a mistranslated word - - Test_Desktop_Notifications was missing in the EN and FR file + - I have corrected some typos in the translation + + - I added a translation for missing words + + - I took the opportunity to correct a mistranslated word + + - Test_Desktop_Notifications was missing in the EN and FR file ![image](https://user-images.githubusercontent.com/45966964/114290186-e7792d80-9a7d-11eb-8164-3b5e72e93703.png) - Updating a message causing URLs to be parsed even within markdown code ([#21489](https://github.com/RocketChat/Rocket.Chat/pull/21489)) - - Fix `updateMessage` to avoid parsing URLs inside markdown + - Fix `updateMessage` to avoid parsing URLs inside markdown + - Honor `parseUrls` property when updating messages - Use async await in TeamChannels delete channel action ([#21534](https://github.com/RocketChat/Rocket.Chat/pull/21534)) @@ -2956,8 +3066,8 @@ - Wrong user in user info ([#21451](https://github.com/RocketChat/Rocket.Chat/pull/21451)) - Fixed some race conditions in admin. - + Fixed some race conditions in admin. + Self DMs used to be created with the userId duplicated. Sometimes rooms can have 2 equal uids, but it's a self DM. Fixed a getter so this isn't a problem anymore.
@@ -2966,22 +3076,30 @@ - Doc: Corrected links to documentation of rocket.chat README.md ([#20478](https://github.com/RocketChat/Rocket.Chat/pull/20478) by [@joshi008](https://github.com/joshi008)) - The link for documentation in the readme was previously https://rocket.chat/docs/ while that was not working and according to the website it was https://docs.rocket.chat/ - The link for deployment methods in readme was corrected from https://rocket.chat/docs/installation/paas-deployments/ to https://docs.rocket.chat/installation/paas-deployments + The link for documentation in the readme was previously https://rocket.chat/docs/ while that was not working and according to the website it was https://docs.rocket.chat/ + The link for deployment methods in readme was corrected from https://rocket.chat/docs/installation/paas-deployments/ to https://docs.rocket.chat/installation/paas-deployments Some more links to the documentations were giving 404 error which hence updated. - [Improve] Remove useless tabbar options from Omnichannel rooms ([#21561](https://github.com/RocketChat/Rocket.Chat/pull/21561) by [@rafaelblink](https://github.com/rafaelblink)) - A React-based replacement for BlazeLayout ([#21527](https://github.com/RocketChat/Rocket.Chat/pull/21527)) - - The Meteor package **`kadira:blaze-layout` was removed**; - - A **global subscription** for the current application layout (**`appLayout`**) replaces `BlazeLayout` entirely; - - The **`#react-root` element** is rendered on server-side instead of dynamically injected into the DOM tree; - - The **"page loading" throbber** is now rendered on the React tree; - - The **`renderRouteComponent` helper was removed**; - - Some code run without any criteria on **`main` template** module was moved into **client startup modules**; - - React portals used to embed Blaze templates have their own subscription (**`blazePortals`**); - - Some **route components were refactored** to remove a URL path trap originally disabled by `renderRouteComponent`; + - The Meteor package **`kadira:blaze-layout` was removed**; + + - A **global subscription** for the current application layout (**`appLayout`**) replaces `BlazeLayout` entirely; + + - The **`#react-root` element** is rendered on server-side instead of dynamically injected into the DOM tree; + + - The **"page loading" throbber** is now rendered on the React tree; + + - The **`renderRouteComponent` helper was removed**; + + - Some code run without any criteria on **`main` template** module was moved into **client startup modules**; + + - React portals used to embed Blaze templates have their own subscription (**`blazePortals`**); + + - Some **route components were refactored** to remove a URL path trap originally disabled by `renderRouteComponent`; + - A new component to embed the DOM nodes generated by **`RoomManager`** was created. - Add ')' after Date and Time in DB migration ([#21519](https://github.com/RocketChat/Rocket.Chat/pull/21519) by [@im-adithya](https://github.com/im-adithya)) @@ -3004,8 +3122,8 @@ - Chore: Meteor update to 2.1.1 ([#21494](https://github.com/RocketChat/Rocket.Chat/pull/21494)) - Basically Node update to version 12.22.1 - + Basically Node update to version 12.22.1 + Meteor change log https://github.com/meteor/meteor/blob/devel/History.md#v211-2021-04-06 - Chore: Remove control character from room model operation ([#21493](https://github.com/RocketChat/Rocket.Chat/pull/21493)) @@ -3014,7 +3132,8 @@ - Fix: Missing module `eventemitter3` for micro services ([#21611](https://github.com/RocketChat/Rocket.Chat/pull/21611)) - - Fix error when running micro services after version 3.12 + - Fix error when running micro services after version 3.12 + - Fix build of docker image version latest for micro services - Language update from LingoHub 🤖 on 2021-04-05Z ([#21446](https://github.com/RocketChat/Rocket.Chat/pull/21446)) @@ -3027,9 +3146,12 @@ - QoL improvements to add channel to team flow ([#21778](https://github.com/RocketChat/Rocket.Chat/pull/21778)) - - Fixed canAccessRoom validation - - Added e2e tests - - Removed channels that user cannot add to the team from autocomplete suggestions + - Fixed canAccessRoom validation + + - Added e2e tests + + - Removed channels that user cannot add to the team from autocomplete suggestions + - Improved error messages - Regression: Bold, italic and strike render (Original markdown) ([#21747](https://github.com/RocketChat/Rocket.Chat/pull/21747)) @@ -3052,10 +3174,10 @@ - Regression: Legacy Banner Position ([#21598](https://github.com/RocketChat/Rocket.Chat/pull/21598)) - ### Before: - ![image](https://user-images.githubusercontent.com/27704687/114961773-dc3c4e00-9e3f-11eb-9a32-e882db3fbfbc.png) - - ### After + ### Before: + ![image](https://user-images.githubusercontent.com/27704687/114961773-dc3c4e00-9e3f-11eb-9a32-e882db3fbfbc.png) + + ### After ![image](https://user-images.githubusercontent.com/27704687/114961673-a6976500-9e3f-11eb-9238-a12870d7db8f.png) - regression: Markdown broken on safari ([#21780](https://github.com/RocketChat/Rocket.Chat/pull/21780)) @@ -3265,56 +3387,62 @@ - **APPS:** New event interfaces for pre/post user leaving a room ([#20917](https://github.com/RocketChat/Rocket.Chat/pull/20917) by [@lucassartor](https://github.com/lucassartor)) - Added events and errors that trigger when a user leaves a room. + Added events and errors that trigger when a user leaves a room. That way it can communicate with the Apps-Engine by the `IPreRoomUserLeave` and `IPostRoomUserLeave` event interfaces. - **Enterprise:** Omnichannel On-Hold Queue ([#20945](https://github.com/RocketChat/Rocket.Chat/pull/20945)) - ### About this feature - This feature has been introduced to deal with Inactive chats. A chat is considered Inactive if an Omnichannel End User (aka Visitor) has not replied back to an agent in some time. These types of inactive chats become very important when an organisation has a limit set for `Max Simultaneous Chats per agent` which is defined by the following setting :point_down: , as more number of Inactive chats would directly affect an agent's productivity. - ![image](https://user-images.githubusercontent.com/34130764/111533003-4d7ad980-878c-11eb-8c1c-2796678a07db.png) - - Before this feature, we only had one option to deal with such Inactive/Abandoned chats - which was to auto close abandoned chats via this setting :point_down: - ![image](https://user-images.githubusercontent.com/34130764/111534353-e65e2480-878d-11eb-82a5-71368064ef45.png) - - however closing a chat isn't a best option for some cases. Let me take an example to explain a scenario - - > An agent is assisting a customer for installing a very huge software which is likely to take more than 20-30 minutes to download. In such scenarios closing a chat isn't the best approach since even after the lengthy download the customer might still need some assist from the agent. - > So basically this chat is going to block the agent's queue until the customer is able to finish his time-consuming download task in which he/she doesn't require any agent's assistance. Due to the `Max Simultaneous Chats per agent` limit, the agent is also not able to use this extra time to help other customer thus affecting his overall productivity. - - **So how does the On-Hold feature solve this problem?** - With the On-Hold feature, an agent is now able to place a chat on-hold. On-Hold chats **don’t count towards the maximum number of concurrent chats** an agent can have. So in our above example, the agent can simply now place the customer on-hold for 20-30 minutes until the customer downloads the software and within this time, the agent can serve other customers - hence increasing the productivity of an agent. - - ---------------------------------------- - ### Working of the new On-Hold feature - - #### How can you place a chat on Hold ? - - A chat can be placed on-hold via 2 means - 1. Automatically place Abandoned chats On-hold - ![image](https://user-images.githubusercontent.com/34130764/111537074-06431780-8791-11eb-8d23-99f5d9f8ec45.png) - Via this :top: option you can define a timer which will get started when a customer sends a message. If we don't receive any message from the customer within this timer, the timer will get expired and the chat will be considered as Abandoned. - ![image](https://user-images.githubusercontent.com/34130764/111537346-53bf8480-8791-11eb-8dc7-260633b4e98f.png) - The via this :top: setting you can choose to automatically place this abandoned chat On Hold - 2. Manually place a chat On Hold - As an admin, you can allow an agent to manually place a chat on-hold. To do so, you'll need to turn on this :point_down: setting - ![image](https://user-images.githubusercontent.com/34130764/111537545-97b28980-8791-11eb-86fd-db45b87e9cc1.png) - Now an agent will be able to see a new `On Hold` button within their `Visitor Info Panel` like this :point_down: , provided the agent has sent the last message - ![image](https://user-images.githubusercontent.com/34130764/111537853-f24be580-8791-11eb-9561-d77ba430c625.png) - - #### How can you resume a On Hold chat ? - An On Hold chat can be resumed via 2 means - - 1. If the Customer sends a message - If the Customer / Omnichannel End User sends a message to the On Hold chat, the On Hold chat will get automatically resumed. - 2. Manually by agent - An Agent can manually resume the On Hold chat via clicking the `Resume` button in the bottom of a chat room. - ![image](https://user-images.githubusercontent.com/34130764/111538666-f88e9180-8792-11eb-8d14-01453b8e3db0.png) - - #### What would happen if the agent already reached maximum chats, and a On-Hold chat gets resumed ? - Based on how the chat was resumed, there are multiple cases are each case is dealt differently - - - If an agent manually tries to resume the On Hold chat, he/she will get an error saying `Maximum Simultaneous chat limit reached` + ### About this feature + This feature has been introduced to deal with Inactive chats. A chat is considered Inactive if an Omnichannel End User (aka Visitor) has not replied back to an agent in some time. These types of inactive chats become very important when an organisation has a limit set for `Max Simultaneous Chats per agent` which is defined by the following setting :point_down: , as more number of Inactive chats would directly affect an agent's productivity. + ![image](https://user-images.githubusercontent.com/34130764/111533003-4d7ad980-878c-11eb-8c1c-2796678a07db.png) + + Before this feature, we only had one option to deal with such Inactive/Abandoned chats - which was to auto close abandoned chats via this setting :point_down: + ![image](https://user-images.githubusercontent.com/34130764/111534353-e65e2480-878d-11eb-82a5-71368064ef45.png) + + however closing a chat isn't a best option for some cases. Let me take an example to explain a scenario + + > An agent is assisting a customer for installing a very huge software which is likely to take more than 20-30 minutes to download. In such scenarios closing a chat isn't the best approach since even after the lengthy download the customer might still need some assist from the agent. + > So basically this chat is going to block the agent's queue until the customer is able to finish his time-consuming download task in which he/she doesn't require any agent's assistance. Due to the `Max Simultaneous Chats per agent` limit, the agent is also not able to use this extra time to help other customer thus affecting his overall productivity. + + **So how does the On-Hold feature solve this problem?** + With the On-Hold feature, an agent is now able to place a chat on-hold. On-Hold chats **don’t count towards the maximum number of concurrent chats** an agent can have. So in our above example, the agent can simply now place the customer on-hold for 20-30 minutes until the customer downloads the software and within this time, the agent can serve other customers - hence increasing the productivity of an agent. + + ---------------------------------------- + ### Working of the new On-Hold feature + + #### How can you place a chat on Hold ? + + A chat can be placed on-hold via 2 means + + 1. Automatically place Abandoned chats On-hold + ![image](https://user-images.githubusercontent.com/34130764/111537074-06431780-8791-11eb-8d23-99f5d9f8ec45.png) + Via this :top: option you can define a timer which will get started when a customer sends a message. If we don't receive any message from the customer within this timer, the timer will get expired and the chat will be considered as Abandoned. + ![image](https://user-images.githubusercontent.com/34130764/111537346-53bf8480-8791-11eb-8dc7-260633b4e98f.png) + The via this :top: setting you can choose to automatically place this abandoned chat On Hold + + 2. Manually place a chat On Hold + As an admin, you can allow an agent to manually place a chat on-hold. To do so, you'll need to turn on this :point_down: setting + ![image](https://user-images.githubusercontent.com/34130764/111537545-97b28980-8791-11eb-86fd-db45b87e9cc1.png) + Now an agent will be able to see a new `On Hold` button within their `Visitor Info Panel` like this :point_down: , provided the agent has sent the last message + ![image](https://user-images.githubusercontent.com/34130764/111537853-f24be580-8791-11eb-9561-d77ba430c625.png) + + #### How can you resume a On Hold chat ? + An On Hold chat can be resumed via 2 means + + + 1. If the Customer sends a message + If the Customer / Omnichannel End User sends a message to the On Hold chat, the On Hold chat will get automatically resumed. + + 2. Manually by agent + An Agent can manually resume the On Hold chat via clicking the `Resume` button in the bottom of a chat room. + ![image](https://user-images.githubusercontent.com/34130764/111538666-f88e9180-8792-11eb-8d14-01453b8e3db0.png) + + #### What would happen if the agent already reached maximum chats, and a On-Hold chat gets resumed ? + Based on how the chat was resumed, there are multiple cases are each case is dealt differently + + + - If an agent manually tries to resume the On Hold chat, he/she will get an error saying `Maximum Simultaneous chat limit reached` + - If a customer replies back on an On Hold chat and the last serving agent has reached maximum capacity, then this customer will be placed on the queue again from where based on the Routing Algorithm selected, the chat will get transferred to any available agent - Ability to hide 'Room topic changed' system messages ([#21062](https://github.com/RocketChat/Rocket.Chat/pull/21062) by [@Tirieru](https://github.com/Tirieru)) @@ -3325,33 +3453,39 @@ - Teams ([#20966](https://github.com/RocketChat/Rocket.Chat/pull/20966) by [@g-thome](https://github.com/g-thome)) - ## Teams - - - - You can easily group your users as Teams on Rocket.Chat. The feature takes the hassle out of managing multiple users one by one and allows you to handle them at the same time efficiently. - - - - Teams can be public or private and each team can have its own channels, which also can be public or private. - - It's possible to add existing channels to a Team or create new ones inside a Team. - - It's possible to invite people outside a Team to join Team's channels. - - It's possible to convert channels to Teams - - It's possible to add all team members to a channel at once - - Team members have roles - - - ![image](https://user-images.githubusercontent.com/70927132/113421955-4f56b680-93a2-11eb-80dc-9b70a3f09b3e.png) - - - - **Quickly onboard new users with Autojoin channels** - - Teams can have Auto-join channels – channels to which the team members are automatically added, so you don’t need to go through the manual process of adding users repetitively - - ![image](https://user-images.githubusercontent.com/70927132/113419284-81194e80-939d-11eb-9fff-aeb05cbc8089.png) - - **Instantly mention multiple members at once** (available in EE) - + ## Teams + + + + You can easily group your users as Teams on Rocket.Chat. The feature takes the hassle out of managing multiple users one by one and allows you to handle them at the same time efficiently. + + + + - Teams can be public or private and each team can have its own channels, which also can be public or private. + + - It's possible to add existing channels to a Team or create new ones inside a Team. + + - It's possible to invite people outside a Team to join Team's channels. + + - It's possible to convert channels to Teams + + - It's possible to add all team members to a channel at once + + - Team members have roles + + + ![image](https://user-images.githubusercontent.com/70927132/113421955-4f56b680-93a2-11eb-80dc-9b70a3f09b3e.png) + + + + **Quickly onboard new users with Autojoin channels** + + Teams can have Auto-join channels – channels to which the team members are automatically added, so you don’t need to go through the manual process of adding users repetitively + + ![image](https://user-images.githubusercontent.com/70927132/113419284-81194e80-939d-11eb-9fff-aeb05cbc8089.png) + + **Instantly mention multiple members at once** (available in EE) + With Teams, you don’t need to remember everyone’s name to communicate with a team quickly. Just mention a Team — @engineers, for instance — and all members will be instantly notified. ### 🚀 Improvements @@ -3361,22 +3495,22 @@ - Added modal-box for preview after recording audio. ([#20370](https://github.com/RocketChat/Rocket.Chat/pull/20370) by [@Darshilp326](https://github.com/Darshilp326)) - A modal box will be displayed so that users can change the filename and add description. - - **Before** - - https://user-images.githubusercontent.com/55157259/105687301-4e2a8880-5f1e-11eb-873d-dc8a880a2fc8.mp4 - - **After** - + A modal box will be displayed so that users can change the filename and add description. + + **Before** + + https://user-images.githubusercontent.com/55157259/105687301-4e2a8880-5f1e-11eb-873d-dc8a880a2fc8.mp4 + + **After** + https://user-images.githubusercontent.com/55157259/105687342-597db400-5f1e-11eb-8b61-8f9d9ebad0c4.mp4 - Adds toast after follow/unfollow messages and following icon for followed messages without threads. ([#20025](https://github.com/RocketChat/Rocket.Chat/pull/20025) by [@RonLek](https://github.com/RonLek)) - There was no alert on following/unfollowing a message previously. Also, it was impossible to make out a followed message with no threads from an unfollowed one. - - This PR would show an alert on following/unfollowing a message and also display a small bell icon (similar to the ones for starred and pinned messages) when a message with no thread is followed. - + There was no alert on following/unfollowing a message previously. Also, it was impossible to make out a followed message with no threads from an unfollowed one. + + This PR would show an alert on following/unfollowing a message and also display a small bell icon (similar to the ones for starred and pinned messages) when a message with no thread is followed. + https://user-images.githubusercontent.com/28918901/103813540-43e73e00-5086-11eb-8592-2877eb650f3e.mp4 - Back to threads list button on threads contextual bar ([#20882](https://github.com/RocketChat/Rocket.Chat/pull/20882)) @@ -3389,12 +3523,12 @@ - Improve Apps permission modal ([#21193](https://github.com/RocketChat/Rocket.Chat/pull/21193) by [@lucassartor](https://github.com/lucassartor)) - Improve the UI of the Apps permission modal when installing an App that requires permissions. - - **New UI:** - ![after](https://user-images.githubusercontent.com/49413772/111685622-e817fe80-8806-11eb-998d-b56623560e74.PNG) - - **Old UI:** + Improve the UI of the Apps permission modal when installing an App that requires permissions. + + **New UI:** + ![after](https://user-images.githubusercontent.com/49413772/111685622-e817fe80-8806-11eb-998d-b56623560e74.PNG) + + **Old UI:** ![before](https://user-images.githubusercontent.com/49413772/111685897-375e2f00-8807-11eb-814e-cb8060dc1830.PNG) - Make debug logs of Apps configurable via Log_Level setting in the Admin panel ([#21000](https://github.com/RocketChat/Rocket.Chat/pull/21000) by [@cuonghuunguyen](https://github.com/cuonghuunguyen)) @@ -3405,15 +3539,15 @@ - Sort Users List In Case Insensitive Manner ([#20790](https://github.com/RocketChat/Rocket.Chat/pull/20790) by [@aditya-mitra](https://github.com/aditya-mitra)) - The users listed in the admin panel were sorted in a case-sensitive manner , where the capitals came first and then the small letters (like - *A B C a b c*). This Change fixes this by sorting the names in a caseinsensitive manner (now - *A a B b C c*). - - ### Before - - ![before](https://user-images.githubusercontent.com/55396651/108189880-3fa74980-7137-11eb-99da-6498707b4bf8.png) - - - ### With This Change - + The users listed in the admin panel were sorted in a case-sensitive manner , where the capitals came first and then the small letters (like - *A B C a b c*). This Change fixes this by sorting the names in a caseinsensitive manner (now - *A a B b C c*). + + ### Before + + ![before](https://user-images.githubusercontent.com/55396651/108189880-3fa74980-7137-11eb-99da-6498707b4bf8.png) + + + ### With This Change + ![after](https://user-images.githubusercontent.com/55396651/108190177-9dd42c80-7137-11eb-8b4e-b7cef4ba512f.png) ### 🐛 Bug fixes @@ -3427,12 +3561,12 @@ - **APPS:** Warn message while installing app in air-gapped environment ([#20992](https://github.com/RocketChat/Rocket.Chat/pull/20992) by [@lucassartor](https://github.com/lucassartor)) - Change **error** message to a **warn** message when uploading a `.zip` file app into a air-gapped environment. - - The **error** message was giving the impression for the user that the app wasn't properly being installed , which it wasn't the case: - ![error](https://user-images.githubusercontent.com/49413772/109855273-d3e4d680-7c36-11eb-824b-ad455d24710c.PNG) - - A more detailed **warn** message can fix that impression for the user: + Change **error** message to a **warn** message when uploading a `.zip` file app into a air-gapped environment. + + The **error** message was giving the impression for the user that the app wasn't properly being installed , which it wasn't the case: + ![error](https://user-images.githubusercontent.com/49413772/109855273-d3e4d680-7c36-11eb-824b-ad455d24710c.PNG) + + A more detailed **warn** message can fix that impression for the user: ![warn](https://user-images.githubusercontent.com/49413772/109855383-f2e36880-7c36-11eb-8d61-c442980bd8fd.PNG) - Add missing `unreads` field to `users.info` REST endpoint ([#20905](https://github.com/RocketChat/Rocket.Chat/pull/20905)) @@ -3447,10 +3581,10 @@ - Correct direction for admin mapview text ([#20897](https://github.com/RocketChat/Rocket.Chat/pull/20897) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) - ![Screenshot from 2021-02-25 02-49-21](https://user-images.githubusercontent.com/38764067/109068512-f8602080-7715-11eb-8e22-d610f9d046d8.png) - ![Screenshot from 2021-02-25 02-49-46](https://user-images.githubusercontent.com/38764067/109068516-fa29e400-7715-11eb-9119-1c79abce278f.png) - ![Screenshot from 2021-02-25 02-49-57](https://user-images.githubusercontent.com/38764067/109068519-fbf3a780-7715-11eb-8b3d-0dc32f898725.png) - + ![Screenshot from 2021-02-25 02-49-21](https://user-images.githubusercontent.com/38764067/109068512-f8602080-7715-11eb-8e22-d610f9d046d8.png) + ![Screenshot from 2021-02-25 02-49-46](https://user-images.githubusercontent.com/38764067/109068516-fa29e400-7715-11eb-9119-1c79abce278f.png) + ![Screenshot from 2021-02-25 02-49-57](https://user-images.githubusercontent.com/38764067/109068519-fbf3a780-7715-11eb-8b3d-0dc32f898725.png) + The text says the share button will be on the left of the messagebox once enabled. However, it actually is on the right. - Correct ignored message CSS ([#20928](https://github.com/RocketChat/Rocket.Chat/pull/20928) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) @@ -3467,13 +3601,13 @@ - Custom emojis to override default ([#20359](https://github.com/RocketChat/Rocket.Chat/pull/20359) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) - Due to the sequence of the imports and how the emojiRenderer prioritizes lists, the custom emojis could not override the emojione emojis. Making two small changes fixed the issue. - - With the custom emoji for `:facepalm:` added, you can check out the result below: - ### Before - ![Screenshot from 2021-01-25 02-20-04](https://user-images.githubusercontent.com/38764067/105643088-dfb0e080-5eb3-11eb-8a00-582c53fbe9a4.png) - - ### After + Due to the sequence of the imports and how the emojiRenderer prioritizes lists, the custom emojis could not override the emojione emojis. Making two small changes fixed the issue. + + With the custom emoji for `:facepalm:` added, you can check out the result below: + ### Before + ![Screenshot from 2021-01-25 02-20-04](https://user-images.githubusercontent.com/38764067/105643088-dfb0e080-5eb3-11eb-8a00-582c53fbe9a4.png) + + ### After ![Screenshot from 2021-01-25 02-18-58](https://user-images.githubusercontent.com/38764067/105643076-cdcf3d80-5eb3-11eb-84b8-5dbc4f1135df.png) - Empty URL in user avatar doesn't show error and enables save ([#20440](https://github.com/RocketChat/Rocket.Chat/pull/20440) by [@im-adithya](https://github.com/im-adithya)) @@ -3486,12 +3620,12 @@ - Fix the search list showing the last channel ([#21160](https://github.com/RocketChat/Rocket.Chat/pull/21160) by [@shrinish123](https://github.com/shrinish123)) - The search list now also properly shows the last channel - Before : - - ![searchlist](https://user-images.githubusercontent.com/56491104/111471487-f3a7ee80-874e-11eb-9c6e-19bbf0731d60.png) - - After : + The search list now also properly shows the last channel + Before : + + ![searchlist](https://user-images.githubusercontent.com/56491104/111471487-f3a7ee80-874e-11eb-9c6e-19bbf0731d60.png) + + After : ![search_final](https://user-images.githubusercontent.com/56491104/111471521-fe628380-874e-11eb-8fa3-d1edb57587e1.png) - Follow thread action on threads list ([#20881](https://github.com/RocketChat/Rocket.Chat/pull/20881)) @@ -3516,13 +3650,13 @@ - Multi Select isn't working in Export Messages ([#21236](https://github.com/RocketChat/Rocket.Chat/pull/21236) by [@PriyaBihani](https://github.com/PriyaBihani)) - While exporting messages, we were not able to select multiple Users like this: - - https://user-images.githubusercontent.com/69837339/111953057-169a2000-8b0c-11eb-94a4-0e1657683f96.mp4 - - Now we can select multiple users: - - + While exporting messages, we were not able to select multiple Users like this: + + https://user-images.githubusercontent.com/69837339/111953057-169a2000-8b0c-11eb-94a4-0e1657683f96.mp4 + + Now we can select multiple users: + + https://user-images.githubusercontent.com/69837339/111953097-274a9600-8b0c-11eb-9177-bec388b042bd.mp4 - New Channel popover not closing ([#21080](https://github.com/RocketChat/Rocket.Chat/pull/21080)) @@ -3531,31 +3665,31 @@ - OEmbedURLWidget - Show Full Embedded Text Description ([#20569](https://github.com/RocketChat/Rocket.Chat/pull/20569) by [@aditya-mitra](https://github.com/aditya-mitra)) - Embeds were cutoff when either _urls had a long description_. - This was handled by removing `overflow:hidden;text-overflow:ellipsis;` from the inline styles in [`oembedUrlWidget.html`](https://github.com/RocketChat/Rocket.Chat/blob/develop/app/oembed/client/oembedUrlWidget.html#L28). - - ### Earlier - - ![earlier](https://user-images.githubusercontent.com/55396651/107110825-00dcde00-6871-11eb-866e-13cabc5b0d05.png) - - ### Now - + Embeds were cutoff when either _urls had a long description_. + This was handled by removing `overflow:hidden;text-overflow:ellipsis;` from the inline styles in [`oembedUrlWidget.html`](https://github.com/RocketChat/Rocket.Chat/blob/develop/app/oembed/client/oembedUrlWidget.html#L28). + + ### Earlier + + ![earlier](https://user-images.githubusercontent.com/55396651/107110825-00dcde00-6871-11eb-866e-13cabc5b0d05.png) + + ### Now + ![now](https://user-images.githubusercontent.com/55396651/107110794-ca06c800-6870-11eb-9b3b-168679936612.png) - Reactions list showing users in reactions option of message action. ([#20753](https://github.com/RocketChat/Rocket.Chat/pull/20753) by [@Darshilp326](https://github.com/Darshilp326)) - Reactions list shows emojis with respected users who have reacted with that emoji. - + Reactions list shows emojis with respected users who have reacted with that emoji. + https://user-images.githubusercontent.com/55157259/107857609-5870e000-6e55-11eb-8137-494a9f71b171.mp4 - Removing truncation from profile ([#20352](https://github.com/RocketChat/Rocket.Chat/pull/20352) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) - Truncating text in profile view was making some information completely inaccessible. Removed it from the user status and the custom fields where if the information is longer, the user would actually want to see all of it. - - ### Before - ![Screenshot from 2021-01-24 20-54-44](https://user-images.githubusercontent.com/38764067/105634935-7e264d00-5e86-11eb-8a6c-9f2a363e0f6c.png) - - ### After + Truncating text in profile view was making some information completely inaccessible. Removed it from the user status and the custom fields where if the information is longer, the user would actually want to see all of it. + + ### Before + ![Screenshot from 2021-01-24 20-54-44](https://user-images.githubusercontent.com/38764067/105634935-7e264d00-5e86-11eb-8a6c-9f2a363e0f6c.png) + + ### After ![Screenshot from 2021-01-24 20-54-06](https://user-images.githubusercontent.com/38764067/105634940-82eb0100-5e86-11eb-8b90-e97a43c5e938.png) - Replace wrong field description on Room Information panel ([#21395](https://github.com/RocketChat/Rocket.Chat/pull/21395) by [@rafaelblink](https://github.com/rafaelblink)) @@ -3566,8 +3700,8 @@ - Set establishing to false if OTR timeouts ([#21183](https://github.com/RocketChat/Rocket.Chat/pull/21183) by [@Darshilp326](https://github.com/Darshilp326)) - Set establishing false if OTR timeouts. - + Set establishing false if OTR timeouts. + https://user-images.githubusercontent.com/55157259/111617086-b30cab80-8808-11eb-8740-3b4ffacfc322.mp4 - Sidebar scroll missing full height ([#21071](https://github.com/RocketChat/Rocket.Chat/pull/21071)) @@ -3606,20 +3740,33 @@ - Chore: Add tests for Meteor methods ([#20901](https://github.com/RocketChat/Rocket.Chat/pull/20901)) - Add end-to-end tests for the following meteor methods - - - [x] public-settings:get - - [x] rooms:get - - [x] subscriptions:get - - [x] permissions:get - - [x] loadMissedMessages - - [x] loadHistory - - [x] listCustomUserStatus - - [x] getUserRoles - - [x] getRoomRoles (called by the API, already covered) - - [x] getMessages - - [x] getUsersOfRoom - - [x] loadNextMessages + Add end-to-end tests for the following meteor methods + + + - [x] public-settings:get + + - [x] rooms:get + + - [x] subscriptions:get + + - [x] permissions:get + + - [x] loadMissedMessages + + - [x] loadHistory + + - [x] listCustomUserStatus + + - [x] getUserRoles + + - [x] getRoomRoles (called by the API, already covered) + + - [x] getMessages + + - [x] getUsersOfRoom + + - [x] loadNextMessages + - [x] getThreadMessages - Chore: Meteor update 2.1 ([#21061](https://github.com/RocketChat/Rocket.Chat/pull/21061)) @@ -3632,8 +3779,10 @@ - Improve: Increase testing coverage ([#21015](https://github.com/RocketChat/Rocket.Chat/pull/21015)) - Add test for - - settings/raw + Add test for + + - settings/raw + - minimongo/comparisons - Improve: NPS survey fetch ([#21263](https://github.com/RocketChat/Rocket.Chat/pull/21263)) @@ -3652,17 +3801,19 @@ - Regression: Add scope to permission checks in Team's endpoints ([#21369](https://github.com/RocketChat/Rocket.Chat/pull/21369)) - - Include scope (team's main room ID) in the permission checks; + - Include scope (team's main room ID) in the permission checks; - Remove the `teamName` parameter from the `members`, `addMembers`, `updateMember` and `removeMembers` methods (since `teamId` will always be defined). - Regression: Add support to filter on `teams.listRooms` endpoint ([#21327](https://github.com/RocketChat/Rocket.Chat/pull/21327)) - - Add support for queries (within the `query` parameter); + - Add support for queries (within the `query` parameter); + - Add support to pagination (`offset` and `count`) when an user doesn't have the permission to get all rooms. - Regression: Add teams support to directory ([#21351](https://github.com/RocketChat/Rocket.Chat/pull/21351)) - - Change `directory.js` to reduce function complexity + - Change `directory.js` to reduce function complexity + - Add `teams` type of item. Directory will return all public teams & private teams the user is part of. - Regression: add view room action on Teams Channels ([#21295](https://github.com/RocketChat/Rocket.Chat/pull/21295)) @@ -3715,18 +3866,19 @@ - Regression: Quick action button missing for Omnichannel On-Hold queue ([#21285](https://github.com/RocketChat/Rocket.Chat/pull/21285)) - - Move the Manual On Hold button to the new Omnichannel Header - ![image](https://user-images.githubusercontent.com/34130764/112291749-6ae10380-8cb6-11eb-94cd-e05efc14b1bf.png) - ![image](https://user-images.githubusercontent.com/34130764/112304146-27d95d00-8cc3-11eb-85db-dde04a110dd1.png) - + - Move the Manual On Hold button to the new Omnichannel Header + ![image](https://user-images.githubusercontent.com/34130764/112291749-6ae10380-8cb6-11eb-94cd-e05efc14b1bf.png) + ![image](https://user-images.githubusercontent.com/34130764/112304146-27d95d00-8cc3-11eb-85db-dde04a110dd1.png) + + - Minor fixes - regression: Remove Breadcrumbs and update Tag component ([#21399](https://github.com/RocketChat/Rocket.Chat/pull/21399)) - Regression: Remove channel action on add channel's modal don't work ([#21356](https://github.com/RocketChat/Rocket.Chat/pull/21356)) - ![removechannel-on-add-existing-modal](https://user-images.githubusercontent.com/27704687/112911017-eda8fa80-90ca-11eb-9c24-47a70be0c314.gif) - + ![removechannel-on-add-existing-modal](https://user-images.githubusercontent.com/27704687/112911017-eda8fa80-90ca-11eb-9c24-47a70be0c314.gif) + ![image](https://user-images.githubusercontent.com/27704687/112911052-02858e00-90cb-11eb-85a2-0ef1f5f9ffd9.png) - Regression: Remove primary color from button in TeamChannels component ([#21293](https://github.com/RocketChat/Rocket.Chat/pull/21293)) @@ -3755,10 +3907,10 @@ - Regression: Unify Contact information displayed on the Room header and Room Info ([#21312](https://github.com/RocketChat/Rocket.Chat/pull/21312) by [@rafaelblink](https://github.com/rafaelblink)) - ![image](https://user-images.githubusercontent.com/34130764/112586659-35592900-8e22-11eb-94be-32bdff7ca883.png) - - ![image](https://user-images.githubusercontent.com/2493803/112913130-788bf400-90cf-11eb-84c6-782b203e100a.png) - + ![image](https://user-images.githubusercontent.com/34130764/112586659-35592900-8e22-11eb-94be-32bdff7ca883.png) + + ![image](https://user-images.githubusercontent.com/2493803/112913130-788bf400-90cf-11eb-84c6-782b203e100a.png) + ![image](https://user-images.githubusercontent.com/2493803/112913146-817cc580-90cf-11eb-87ad-ef79766be2b3.png) - Regression: Unify team actions to add a room to a team ([#21386](https://github.com/RocketChat/Rocket.Chat/pull/21386)) @@ -3767,8 +3919,10 @@ - Regression: Update .invite endpoints to support multiple users at once ([#21328](https://github.com/RocketChat/Rocket.Chat/pull/21328)) - - channels.invite now supports passing an array as a param (either with usernames or userIds) via `usernames` or `userIds` properties. - - You can still use the endpoint to invite only one user via the old params `userId`, `username` or `user`. + - channels.invite now supports passing an array as a param (either with usernames or userIds) via `usernames` or `userIds` properties. + + - You can still use the endpoint to invite only one user via the old params `userId`, `username` or `user`. + - Same changes apply to groups.invite - Regression: user actions in admin ([#21307](https://github.com/RocketChat/Rocket.Chat/pull/21307)) @@ -3903,7 +4057,7 @@ - Close Call contextual bar after starting jitsi call. ([#21004](https://github.com/RocketChat/Rocket.Chat/pull/21004) by [@yash-rajpal](https://github.com/yash-rajpal)) - After jitsi call is started, if the call is started in a new window then we should close contextual tab bar. + After jitsi call is started, if the call is started in a new window then we should close contextual tab bar. So, when 'YES' is pressed on modal, we call handleClose function if openNewWindow is true, as call doesn't starts on tab bar, it starts on new window. ### 🐛 Bug fixes @@ -3913,7 +4067,7 @@ - Stopping Jitsi reload ([#20973](https://github.com/RocketChat/Rocket.Chat/pull/20973) by [@yash-rajpal](https://github.com/yash-rajpal)) - The Function where Jitsi call is started gets called many times due to `room.usernames` dep of useMemo, this dep triggers reloading of this function many times. + The Function where Jitsi call is started gets called many times due to `room.usernames` dep of useMemo, this dep triggers reloading of this function many times. So removing this dep from useMemo dependencies ### 👩‍💻👨‍💻 Contributors 😍 @@ -3941,10 +4095,10 @@ - Cloud Workspace bridge ([#20838](https://github.com/RocketChat/Rocket.Chat/pull/20838)) - Adds the new CloudWorkspace functionality. - - It allows apps to request the access token for the workspace it's installed on, so it can perform actions with other Rocket.Chat services, such as the Omni Gateway. - + Adds the new CloudWorkspace functionality. + + It allows apps to request the access token for the workspace it's installed on, so it can perform actions with other Rocket.Chat services, such as the Omni Gateway. + https://github.com/RocketChat/Rocket.Chat.Apps-engine/pull/382 - Header with Breadcrumbs ([#20609](https://github.com/RocketChat/Rocket.Chat/pull/20609)) @@ -3962,10 +4116,10 @@ - Add symbol to indicate apps' required settings in the UI ([#20447](https://github.com/RocketChat/Rocket.Chat/pull/20447)) - - Apps are able to define **required** settings. These settings should not be left blank by the user and an error will be thrown and shown in the interface if an user attempts to save changes in the app details page leaving any required fields blank; - ![prt_screen_required_app_settings_warning](https://user-images.githubusercontent.com/36537004/106032964-e73cd900-60af-11eb-8eab-c11fd651b593.png) - - - A sign (*) is added to the label of app settings' fields that are required so as to highlight the fields which must not be left blank. + - Apps are able to define **required** settings. These settings should not be left blank by the user and an error will be thrown and shown in the interface if an user attempts to save changes in the app details page leaving any required fields blank; + ![prt_screen_required_app_settings_warning](https://user-images.githubusercontent.com/36537004/106032964-e73cd900-60af-11eb-8eab-c11fd651b593.png) + + - A sign (*) is added to the label of app settings' fields that are required so as to highlight the fields which must not be left blank. ![prt_screen_required_app_settings](https://user-images.githubusercontent.com/36537004/106014879-ae473900-609c-11eb-9b9e-95de7bbf20a5.png) - Add visual validation on users admin forms ([#20308](https://github.com/RocketChat/Rocket.Chat/pull/20308)) @@ -3986,20 +4140,20 @@ - Adds tooltip for sidebar header icons ([#19934](https://github.com/RocketChat/Rocket.Chat/pull/19934) by [@RonLek](https://github.com/RonLek)) - Previously the header icons in the sidebar didn't show a tooltip when hovered over. This PR fixes that. - + Previously the header icons in the sidebar didn't show a tooltip when hovered over. This PR fixes that. + ![Screenshot from 2020-12-22 15-17-41](https://user-images.githubusercontent.com/28918901/102874804-f2756700-4468-11eb-8324-b7f3194e62fe.png) - Better Presentation of Blockquotes ([#20750](https://github.com/RocketChat/Rocket.Chat/pull/20750) by [@aditya-mitra](https://github.com/aditya-mitra)) - Changed the values of `margin-top` and `margin-bottom` for *first* and *last* childs in blockquotes to increase readability. - - ### Before - - ![before](https://user-images.githubusercontent.com/55396651/107858662-3e3a0080-6e5b-11eb-8274-9bd956807235.png) - - ### Now - + Changed the values of `margin-top` and `margin-bottom` for *first* and *last* childs in blockquotes to increase readability. + + ### Before + + ![before](https://user-images.githubusercontent.com/55396651/107858662-3e3a0080-6e5b-11eb-8274-9bd956807235.png) + + ### Now + ![now](https://user-images.githubusercontent.com/55396651/107858471-480f3400-6e5a-11eb-9ccb-3f1be2fed0a4.png) - Change header based on room type ([#20612](https://github.com/RocketChat/Rocket.Chat/pull/20612)) @@ -4020,13 +4174,18 @@ - Replace react-window for react-virtuoso package ([#20392](https://github.com/RocketChat/Rocket.Chat/pull/20392)) - Remove: - - react-window - - react-window-infinite-loader - - simplebar-react - - Include: - - react-virtuoso + Remove: + + - react-window + + - react-window-infinite-loader + + - simplebar-react + + Include: + + - react-virtuoso + - rc-scrollbars - Rewrite Call as React component ([#19778](https://github.com/RocketChat/Rocket.Chat/pull/19778)) @@ -4042,13 +4201,13 @@ - Add debouncing to add users search field. ([#20297](https://github.com/RocketChat/Rocket.Chat/pull/20297) by [@Darshilp326](https://github.com/Darshilp326)) - BEFORE - - https://user-images.githubusercontent.com/55157259/105350722-98a3c080-5c11-11eb-82f3-d9a62a4fa50b.mp4 - - - AFTER - + BEFORE + + https://user-images.githubusercontent.com/55157259/105350722-98a3c080-5c11-11eb-82f3-d9a62a4fa50b.mp4 + + + AFTER + https://user-images.githubusercontent.com/55157259/105350757-a2c5bf00-5c11-11eb-91db-25c0b9e01a28.mp4 - Add tooltips to Thread header buttons ([#20456](https://github.com/RocketChat/Rocket.Chat/pull/20456) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) @@ -4061,8 +4220,8 @@ - Added check for view admin permission page ([#20403](https://github.com/RocketChat/Rocket.Chat/pull/20403) by [@yash-rajpal](https://github.com/yash-rajpal)) - Admin Permission page was visible to all, if you add admin/permissions after the base url. This should not be visible to all user, only people with certain permissions should be able to see this page. - I am also able to see permissions page for open workspace of Rocket chat. + Admin Permission page was visible to all, if you add admin/permissions after the base url. This should not be visible to all user, only people with certain permissions should be able to see this page. + I am also able to see permissions page for open workspace of Rocket chat. ![image](https://user-images.githubusercontent.com/58601732/105829728-bfd00880-5fea-11eb-9121-6c53a752f140.png) - Adding the accidentally deleted tag template, used by other templates ([#20772](https://github.com/RocketChat/Rocket.Chat/pull/20772) by [@yash-rajpal](https://github.com/yash-rajpal)) @@ -4071,8 +4230,8 @@ - Admin cannot clear user details like bio or nickname ([#20785](https://github.com/RocketChat/Rocket.Chat/pull/20785) by [@yash-rajpal](https://github.com/yash-rajpal)) - When the API users.update is called to update user data, it passes data to saveUser function. Here before saving data like bio or nickname we are checking if they are available or not. If data is available then we are saving it, but we are not doing anything when data isn't available. - + When the API users.update is called to update user data, it passes data to saveUser function. Here before saving data like bio or nickname we are checking if they are available or not. If data is available then we are saving it, but we are not doing anything when data isn't available. + So unsetting data if data isn't available to save. Will also fix bio and other fields. :) - Admin Panel pages not visible in Safari ([#20912](https://github.com/RocketChat/Rocket.Chat/pull/20912)) @@ -4089,24 +4248,24 @@ - Blank Personal Access Token Bug ([#20193](https://github.com/RocketChat/Rocket.Chat/pull/20193) by [@RonLek](https://github.com/RonLek)) - Adds error when personal access token is blank thereby disallowing the creation of one. - + Adds error when personal access token is blank thereby disallowing the creation of one. + https://user-images.githubusercontent.com/28918901/104483631-5adde100-55ee-11eb-9938-64146bce127e.mp4 - CAS login failing due to TOTP requirement ([#20840](https://github.com/RocketChat/Rocket.Chat/pull/20840)) - Changed password input field for password access in edit room info. ([#20356](https://github.com/RocketChat/Rocket.Chat/pull/20356) by [@Darshilp326](https://github.com/Darshilp326)) - Password field would be secured with asterisks in edit room info - - https://user-images.githubusercontent.com/55157259/105641758-cad04f00-5eab-11eb-90de-0c91263edd55.mp4 - + Password field would be secured with asterisks in edit room info + + https://user-images.githubusercontent.com/55157259/105641758-cad04f00-5eab-11eb-90de-0c91263edd55.mp4 + . - Channel mentions showing user subscribed channels twice ([#20484](https://github.com/RocketChat/Rocket.Chat/pull/20484) by [@Darshilp326](https://github.com/Darshilp326)) - Channel mention shows user subscribed channels twice. - + Channel mention shows user subscribed channels twice. + https://user-images.githubusercontent.com/55157259/106183033-b353d780-61c5-11eb-8aab-1dbb62b02ff8.mp4 - CORS config not accepting multiple origins ([#20696](https://github.com/RocketChat/Rocket.Chat/pull/20696) by [@g-thome](https://github.com/g-thome)) @@ -4117,26 +4276,26 @@ - Default Attachments - Remove Extra Margin in Field Attachments ([#20618](https://github.com/RocketChat/Rocket.Chat/pull/20618) by [@aditya-mitra](https://github.com/aditya-mitra)) - A large amount of unnecessary margin which existed in the **Field Attachments inside the `DefaultAttachments`** has been fixed. - - ### Earlier - - ![earlier](https://user-images.githubusercontent.com/55396651/107056792-ba4b9d00-67f8-11eb-9153-05281416cddb.png) - - ### Now - + A large amount of unnecessary margin which existed in the **Field Attachments inside the `DefaultAttachments`** has been fixed. + + ### Earlier + + ![earlier](https://user-images.githubusercontent.com/55396651/107056792-ba4b9d00-67f8-11eb-9153-05281416cddb.png) + + ### Now + ![now](https://user-images.githubusercontent.com/55396651/107057196-3219c780-67f9-11eb-84db-e4a0addfc168.png) - Default Attachments - Show Full Attachment.Text with Markdown ([#20606](https://github.com/RocketChat/Rocket.Chat/pull/20606) by [@aditya-mitra](https://github.com/aditya-mitra)) - Removed truncating of text in `Attachment.Text`. - Added `Attachment.Text` to be parsed to markdown by default. - - ### Earlier - ![earlier](https://user-images.githubusercontent.com/55396651/106910781-92d8cf80-6727-11eb-82ec-818df7544ff0.png) - - ### Now - + Removed truncating of text in `Attachment.Text`. + Added `Attachment.Text` to be parsed to markdown by default. + + ### Earlier + ![earlier](https://user-images.githubusercontent.com/55396651/106910781-92d8cf80-6727-11eb-82ec-818df7544ff0.png) + + ### Now + ![now](https://user-images.githubusercontent.com/55396651/106910840-a126eb80-6727-11eb-8bd6-d86383dd9181.png) - Don't ask again not rendering ([#20745](https://github.com/RocketChat/Rocket.Chat/pull/20745)) @@ -4157,21 +4316,21 @@ - Feedback on bulk invite ([#20339](https://github.com/RocketChat/Rocket.Chat/pull/20339) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) - Resolved structure where no response was being received. Changed from callback to async/await. - Added error in case of empty submission, or if no valid emails were found. - + Resolved structure where no response was being received. Changed from callback to async/await. + Added error in case of empty submission, or if no valid emails were found. + https://user-images.githubusercontent.com/38764067/105613964-dfe5a900-5deb-11eb-80f2-21fc8dee57c0.mp4 - Filters are not being applied correctly in Omnichannel Current Chats list ([#20320](https://github.com/RocketChat/Rocket.Chat/pull/20320) by [@rafaelblink](https://github.com/rafaelblink)) - ### Before - ![image](https://user-images.githubusercontent.com/2493803/105537672-082cb500-5cd1-11eb-8f1b-1726ba60420a.png) - - ### After - ![image](https://user-images.githubusercontent.com/2493803/105537773-2d212800-5cd1-11eb-8746-048deb9502d9.png) - - ![image](https://user-images.githubusercontent.com/2493803/106494728-88090b00-6499-11eb-922e-5386107e2389.png) - + ### Before + ![image](https://user-images.githubusercontent.com/2493803/105537672-082cb500-5cd1-11eb-8f1b-1726ba60420a.png) + + ### After + ![image](https://user-images.githubusercontent.com/2493803/105537773-2d212800-5cd1-11eb-8746-048deb9502d9.png) + + ![image](https://user-images.githubusercontent.com/2493803/106494728-88090b00-6499-11eb-922e-5386107e2389.png) + ![image](https://user-images.githubusercontent.com/2493803/106494751-90f9dc80-6499-11eb-901b-5e4dbdc678ba.png) - Fix Empty highlighted words field ([#20329](https://github.com/RocketChat/Rocket.Chat/pull/20329) by [@yash-rajpal](https://github.com/yash-rajpal)) @@ -4200,11 +4359,11 @@ - List of Omnichannel triggers is not listing data ([#20624](https://github.com/RocketChat/Rocket.Chat/pull/20624) by [@rafaelblink](https://github.com/rafaelblink)) - ### Before - ![image](https://user-images.githubusercontent.com/2493803/107095379-7308e080-67e7-11eb-8251-7e7ff891087a.png) - - - ### After + ### Before + ![image](https://user-images.githubusercontent.com/2493803/107095379-7308e080-67e7-11eb-8251-7e7ff891087a.png) + + + ### After ![image](https://user-images.githubusercontent.com/2493803/107095261-3b019d80-67e7-11eb-8425-8612b03ac50a.png) - Livechat bridge permission checkers ([#20653](https://github.com/RocketChat/Rocket.Chat/pull/20653) by [@lolimay](https://github.com/lolimay)) @@ -4227,7 +4386,8 @@ - Missing setting to control when to send the ReplyTo field in email notifications ([#20744](https://github.com/RocketChat/Rocket.Chat/pull/20744)) - - Add a new setting ("Add Reply-To header") in the Email settings' page to control when the Reply-To header is used in e-mail notifications; + - Add a new setting ("Add Reply-To header") in the Email settings' page to control when the Reply-To header is used in e-mail notifications; + - The new setting is turned off (`false` value) by default. - New Integration page was not being displayed ([#20670](https://github.com/RocketChat/Rocket.Chat/pull/20670) by [@yash-rajpal](https://github.com/yash-rajpal)) @@ -4260,15 +4420,15 @@ - Remove duplicate getCommonRoomEvents() event binding for starredMessages ([#20185](https://github.com/RocketChat/Rocket.Chat/pull/20185) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) - The getCommonRoomEvents() returned functions were bound to the starredMessages template twice. This was causing some bugs, as detailed in the Issue mentioned below. + The getCommonRoomEvents() returned functions were bound to the starredMessages template twice. This was causing some bugs, as detailed in the Issue mentioned below. I removed the top events call that only bound the getCommonRoomEvents(). Therefore, only one call for the same is left, which is at the end of the file. Having the events bound just once removes the bugs mentioned. - Remove warning problems from console ([#20800](https://github.com/RocketChat/Rocket.Chat/pull/20800)) - Removed tooltip in kebab menu options. ([#20498](https://github.com/RocketChat/Rocket.Chat/pull/20498) by [@Darshilp326](https://github.com/Darshilp326)) - Removed tooltip as it was not needed. - + Removed tooltip as it was not needed. + https://user-images.githubusercontent.com/55157259/106246146-a53ca000-6233-11eb-9874-cbd1b4331bc0.mp4 - Retry icon comes out of the div ([#20390](https://github.com/RocketChat/Rocket.Chat/pull/20390) by [@im-adithya](https://github.com/im-adithya)) @@ -4283,8 +4443,8 @@ - Room's last message's update date format on IE ([#20680](https://github.com/RocketChat/Rocket.Chat/pull/20680)) - The proposed change fixes a bug when updates the cached records on Internet Explorer and it breaks the sidebar as shown on the screenshot below: - + The proposed change fixes a bug when updates the cached records on Internet Explorer and it breaks the sidebar as shown on the screenshot below: + ![image](https://user-images.githubusercontent.com/27704687/107578007-f2285b00-6bd1-11eb-9250-1e76ae67f9c9.png) - Save user password and email from My Account ([#20737](https://github.com/RocketChat/Rocket.Chat/pull/20737)) @@ -4293,8 +4453,8 @@ - Selected hide system messages would now be viewed in vertical bar. ([#20358](https://github.com/RocketChat/Rocket.Chat/pull/20358) by [@Darshilp326](https://github.com/Darshilp326)) - All selected hide system messages are now in vertical Bar. - + All selected hide system messages are now in vertical Bar. + https://user-images.githubusercontent.com/55157259/105642624-d5411780-5eb0-11eb-8848-93e4b02629cb.mp4 - Selected messages don't get unselected ([#20408](https://github.com/RocketChat/Rocket.Chat/pull/20408) by [@im-adithya](https://github.com/im-adithya)) @@ -4309,14 +4469,22 @@ - Several Slack Importer issues ([#20216](https://github.com/RocketChat/Rocket.Chat/pull/20216)) - - Fix: Slack Importer crashes when importing a large users.json file - - Fix: Slack importer crashes when messages have invalid mentions - - Skip listing all users on the preparation screen when the user count is too large. - - Split avatar download into a separate process. - - Update room's last message when the import is complete. - - Prevent invalid or duplicated channel names - - Improve message error handling. - - Reduce max allowed BSON size to avoid possible issues in some servers. + - Fix: Slack Importer crashes when importing a large users.json file + + - Fix: Slack importer crashes when messages have invalid mentions + + - Skip listing all users on the preparation screen when the user count is too large. + + - Split avatar download into a separate process. + + - Update room's last message when the import is complete. + + - Prevent invalid or duplicated channel names + + - Improve message error handling. + + - Reduce max allowed BSON size to avoid possible issues in some servers. + - Improve handling of very large channel files. - star icon was visible after unstarring a message ([#19645](https://github.com/RocketChat/Rocket.Chat/pull/19645) by [@bhavayAnand9](https://github.com/bhavayAnand9)) @@ -4335,15 +4503,15 @@ - User statuses in admin user info panel ([#20341](https://github.com/RocketChat/Rocket.Chat/pull/20341) by [@RonLek](https://github.com/RonLek)) - Modifies user statuses in admin info panel based on their actual status instead of their `statusConnection`. This enables correct and consistent change in user statuses. - Also, bot users having status as online were classified as offline, with this change they are now correctly classified based on their corresponding statuses. - + Modifies user statuses in admin info panel based on their actual status instead of their `statusConnection`. This enables correct and consistent change in user statuses. + Also, bot users having status as online were classified as offline, with this change they are now correctly classified based on their corresponding statuses. + https://user-images.githubusercontent.com/28918901/105624438-b8bcc500-5e47-11eb-8d1e-3a4180da1304.mp4 - Users autocomplete showing duplicated results ([#20481](https://github.com/RocketChat/Rocket.Chat/pull/20481) by [@Darshilp326](https://github.com/Darshilp326)) - Added new query for outside room users so that room members are not shown twice. - + Added new query for outside room users so that room members are not shown twice. + https://user-images.githubusercontent.com/55157259/106174582-33c10b00-61bb-11eb-9716-377ef7bba34e.mp4
@@ -4364,7 +4532,7 @@ - Chore: Disable Sessions Aggregates tests locally ([#20607](https://github.com/RocketChat/Rocket.Chat/pull/20607)) - Disable Session aggregates tests in local environments + Disable Session aggregates tests in local environments For context, refer to: #20161 - Chore: Improve performance of messages’ watcher ([#20519](https://github.com/RocketChat/Rocket.Chat/pull/20519)) @@ -4573,18 +4741,20 @@ - **ENTERPRISE:** Omnichannel Contact Manager as preferred agent for routing ([#20244](https://github.com/RocketChat/Rocket.Chat/pull/20244)) - If the `Contact-Manager` is assigned to a Visitor, the chat will automatically get transferred to the respective Contact-Manager, provided the Contact-Manager is online. In-case the Contact-Manager is offline, the chat will be transferred to any other online agent. - We have provided a setting to control this auto-assignment feature - ![image](https://user-images.githubusercontent.com/34130764/104880961-8104d780-5986-11eb-9d87-82b99814b028.png) - - Behavior based-on Routing method - - 1. Auto-selection, Load-Balancing, or External Service (`autoAssignAgent = true`) - This is straightforward, - - if the Contact-manager is online, the chat will be transferred to the Contact-Manger only - - if the Contact-manager is offline, the chat will be transferred to any other online-agent based on the Routing system - 2. Manual-selection (`autoAssignAgent = false`) - - If the Contact-Manager is online, the chat will appear in the Queue of Contact-Manager **ONLY** + If the `Contact-Manager` is assigned to a Visitor, the chat will automatically get transferred to the respective Contact-Manager, provided the Contact-Manager is online. In-case the Contact-Manager is offline, the chat will be transferred to any other online agent. + We have provided a setting to control this auto-assignment feature + ![image](https://user-images.githubusercontent.com/34130764/104880961-8104d780-5986-11eb-9d87-82b99814b028.png) + + Behavior based-on Routing method + + + 1. Auto-selection, Load-Balancing, or External Service (`autoAssignAgent = true`) + This is straightforward, + - if the Contact-manager is online, the chat will be transferred to the Contact-Manger only + - if the Contact-manager is offline, the chat will be transferred to any other online-agent based on the Routing system + + 2. Manual-selection (`autoAssignAgent = false`) + - If the Contact-Manager is online, the chat will appear in the Queue of Contact-Manager **ONLY** - If the Contact-Manager is offline, the chat will appear in the Queue of all related Agents/Manager ( like it's done right now ) - Banner system and NPS ([#20221](https://github.com/RocketChat/Rocket.Chat/pull/20221)) @@ -4593,34 +4763,34 @@ - Email Inboxes for Omnichannel ([#20101](https://github.com/RocketChat/Rocket.Chat/pull/20101) by [@rafaelblink](https://github.com/rafaelblink)) - With this new feature, email accounts will receive email messages(threads) which will be transformed into Omnichannel chats. It'll be possible to set up multiple email accounts, test the connection with email server(email provider) and define the behaviour of each account. - - https://user-images.githubusercontent.com/2493803/105430398-242d4980-5c32-11eb-835a-450c94837d23.mp4 - - ### New item on admin menu - - ![image](https://user-images.githubusercontent.com/2493803/105428723-bc293400-5c2e-11eb-8c02-e8d36ea82726.png) - - - ### Send test email tooltip - - ![image](https://user-images.githubusercontent.com/2493803/104366986-eaa16380-54f8-11eb-9ba7-831cfde2319c.png) - - - ### Inbox Info - - ![image](https://user-images.githubusercontent.com/2493803/104366796-ab731280-54f8-11eb-9941-a3cc8eb610e1.png) - - ### SMTP Info - - ![image](https://user-images.githubusercontent.com/2493803/104366868-c47bc380-54f8-11eb-969e-ccc29070957c.png) - - ### IMAP Info - - ![image](https://user-images.githubusercontent.com/2493803/104366897-cd6c9500-54f8-11eb-80c4-97d5b0c002d5.png) - - ### Messages - + With this new feature, email accounts will receive email messages(threads) which will be transformed into Omnichannel chats. It'll be possible to set up multiple email accounts, test the connection with email server(email provider) and define the behaviour of each account. + + https://user-images.githubusercontent.com/2493803/105430398-242d4980-5c32-11eb-835a-450c94837d23.mp4 + + ### New item on admin menu + + ![image](https://user-images.githubusercontent.com/2493803/105428723-bc293400-5c2e-11eb-8c02-e8d36ea82726.png) + + + ### Send test email tooltip + + ![image](https://user-images.githubusercontent.com/2493803/104366986-eaa16380-54f8-11eb-9ba7-831cfde2319c.png) + + + ### Inbox Info + + ![image](https://user-images.githubusercontent.com/2493803/104366796-ab731280-54f8-11eb-9941-a3cc8eb610e1.png) + + ### SMTP Info + + ![image](https://user-images.githubusercontent.com/2493803/104366868-c47bc380-54f8-11eb-969e-ccc29070957c.png) + + ### IMAP Info + + ![image](https://user-images.githubusercontent.com/2493803/104366897-cd6c9500-54f8-11eb-80c4-97d5b0c002d5.png) + + ### Messages + ![image](https://user-images.githubusercontent.com/2493803/105428971-45d90180-5c2f-11eb-992a-022a3df94471.png) - Encrypted Discussions and new Encryption Permissions ([#20201](https://github.com/RocketChat/Rocket.Chat/pull/20201)) @@ -4632,7 +4802,7 @@ - Add extra SAML settings to update room subs and add private room subs. ([#19489](https://github.com/RocketChat/Rocket.Chat/pull/19489) by [@tlskinneriv](https://github.com/tlskinneriv)) - Added a SAML setting to support updating room subscriptions each time a user logs in via SAML. + Added a SAML setting to support updating room subscriptions each time a user logs in via SAML. Added a SAML setting to support including private rooms in SAML updated subscriptions (whether initial or on each logon). - Autofocus on directory ([#20509](https://github.com/RocketChat/Rocket.Chat/pull/20509)) @@ -4659,7 +4829,7 @@ - Tooltip added for Kebab menu on chat header ([#20116](https://github.com/RocketChat/Rocket.Chat/pull/20116) by [@yash-rajpal](https://github.com/yash-rajpal)) - Added the missing Tooltip for kebab menu on chat header. + Added the missing Tooltip for kebab menu on chat header. ![tooltip after](https://user-images.githubusercontent.com/58601732/104031406-b07f4b80-51f2-11eb-87a4-1e8da78a254f.gif) ### 🐛 Bug fixes @@ -4681,7 +4851,7 @@ - Added context check for closing active tabbar for member-list ([#20228](https://github.com/RocketChat/Rocket.Chat/pull/20228) by [@yash-rajpal](https://github.com/yash-rajpal)) - When we click on a username and then click on see user's full profile, a tab gets active and shows us the user's profile, the problem occurs when the tab is still active and we try to see another user's profile. In this case, tabbar gets closed. + When we click on a username and then click on see user's full profile, a tab gets active and shows us the user's profile, the problem occurs when the tab is still active and we try to see another user's profile. In this case, tabbar gets closed. To resolve this, added context check for closing action of active tabbar. - Added Margin between status bullet and status label ([#20199](https://github.com/RocketChat/Rocket.Chat/pull/20199) by [@yash-rajpal](https://github.com/yash-rajpal)) @@ -4690,8 +4860,8 @@ - Added success message on saving notification preference. ([#20220](https://github.com/RocketChat/Rocket.Chat/pull/20220) by [@Darshilp326](https://github.com/Darshilp326)) - Added success message after saving notification preferences. - + Added success message after saving notification preferences. + https://user-images.githubusercontent.com/55157259/104774617-03ca3e80-579d-11eb-8fa4-990b108dd8d9.mp4 - Admin User Info email verified status ([#20110](https://github.com/RocketChat/Rocket.Chat/pull/20110) by [@bdelwood](https://github.com/bdelwood)) @@ -4700,10 +4870,10 @@ - Change header's favorite icon to filled star ([#20174](https://github.com/RocketChat/Rocket.Chat/pull/20174)) - ### Before: - ![image](https://user-images.githubusercontent.com/27704687/104351819-a60bcd00-54e4-11eb-8b43-7d281a6e5dcb.png) - - ### After: + ### Before: + ![image](https://user-images.githubusercontent.com/27704687/104351819-a60bcd00-54e4-11eb-8b43-7d281a6e5dcb.png) + + ### After: ![image](https://user-images.githubusercontent.com/27704687/104351632-67761280-54e4-11eb-87ba-25b940494bb5.png) - Changed success message for adding custom sound. ([#20272](https://github.com/RocketChat/Rocket.Chat/pull/20272) by [@Darshilp326](https://github.com/Darshilp326)) @@ -4712,24 +4882,24 @@ - Changed success message for ignoring member. ([#19996](https://github.com/RocketChat/Rocket.Chat/pull/19996) by [@Darshilp326](https://github.com/Darshilp326)) - Different messages for ignoring/unignoring will be displayed. - + Different messages for ignoring/unignoring will be displayed. + https://user-images.githubusercontent.com/55157259/103310307-4241c880-4a3d-11eb-8c6c-4c9b99d023db.mp4 - Creation of Omnichannel rooms not working correctly through the Apps when the agent parameter is set ([#19997](https://github.com/RocketChat/Rocket.Chat/pull/19997)) - Engagement dashboard graphs labels superposing each other ([#20267](https://github.com/RocketChat/Rocket.Chat/pull/20267)) - Now after a certain breakpoint, the graphs should stack vertically, and overlapping text rotated. - + Now after a certain breakpoint, the graphs should stack vertically, and overlapping text rotated. + ![image](https://user-images.githubusercontent.com/40830821/105098926-93b40500-5a89-11eb-9a56-2fc3b1552914.png) - Fields overflowing page ([#20287](https://github.com/RocketChat/Rocket.Chat/pull/20287)) - ### Before - ![image](https://user-images.githubusercontent.com/40830821/105246952-c1b14c00-5b52-11eb-8671-cff88edf242d.png) - - ### After + ### Before + ![image](https://user-images.githubusercontent.com/40830821/105246952-c1b14c00-5b52-11eb-8671-cff88edf242d.png) + + ### After ![image](https://user-images.githubusercontent.com/40830821/105247125-0a690500-5b53-11eb-9f3c-d6a68108e336.png) - Fix error that occurs on changing archive status of room ([#20098](https://github.com/RocketChat/Rocket.Chat/pull/20098) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) @@ -4746,7 +4916,7 @@ - Livechat.RegisterGuest method removing unset fields ([#20124](https://github.com/RocketChat/Rocket.Chat/pull/20124) by [@rafaelblink](https://github.com/rafaelblink)) - After changes made on https://github.com/RocketChat/Rocket.Chat/pull/19931, the `Livechat.RegisterGuest` method started removing properties from the visitor inappropriately. The properties that did not receive value were removed from the object. + After changes made on https://github.com/RocketChat/Rocket.Chat/pull/19931, the `Livechat.RegisterGuest` method started removing properties from the visitor inappropriately. The properties that did not receive value were removed from the object. Those changes were made to support the new Contact Form, but now the form has its own method to deal with Contact data so those changes are no longer necessary. - Markdown added for Header Room topic ([#20021](https://github.com/RocketChat/Rocket.Chat/pull/20021) by [@yash-rajpal](https://github.com/yash-rajpal)) @@ -4767,18 +4937,18 @@ - Omnichannel - Contact Center form is not validating custom fields properly ([#20196](https://github.com/RocketChat/Rocket.Chat/pull/20196) by [@rafaelblink](https://github.com/rafaelblink)) - The contact form is accepting undefined values in required custom fields when creating or editing contacts, and, the errror message isn't following Rocket.chat design system. - - ### Before - ![image](https://user-images.githubusercontent.com/2493803/104522668-31688980-55dd-11eb-92c5-83f96073edc4.png) - - ### After - - #### New - ![image](https://user-images.githubusercontent.com/2493803/104770494-68f74300-574f-11eb-94a3-c8fd73365308.png) - - - #### Edit + The contact form is accepting undefined values in required custom fields when creating or editing contacts, and, the errror message isn't following Rocket.chat design system. + + ### Before + ![image](https://user-images.githubusercontent.com/2493803/104522668-31688980-55dd-11eb-92c5-83f96073edc4.png) + + ### After + + #### New + ![image](https://user-images.githubusercontent.com/2493803/104770494-68f74300-574f-11eb-94a3-c8fd73365308.png) + + + #### Edit ![image](https://user-images.githubusercontent.com/2493803/104770538-7b717c80-574f-11eb-829f-1ae304103369.png) - Omnichannel Agents unable to take new chats in the queue ([#20022](https://github.com/RocketChat/Rocket.Chat/pull/20022) by [@rafaelblink](https://github.com/rafaelblink)) @@ -4799,15 +4969,15 @@ - Room special name in prompts ([#20277](https://github.com/RocketChat/Rocket.Chat/pull/20277) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) - The "Hide room" and "Leave Room" confirmation prompts use the "name" key from the room info. When the setting " - Allow Special Characters in Room Names" is enabled, the prompts show the normalized names instead of those that contain the special characters. - - Changed the value being used from name to fname, which always has the user-set name. - - Previous: - ![Screenshot from 2021-01-20 15-52-29](https://user-images.githubusercontent.com/38764067/105161642-9b31e780-5b37-11eb-8b0c-ec4b1414c948.png) - - Updated: + The "Hide room" and "Leave Room" confirmation prompts use the "name" key from the room info. When the setting " + Allow Special Characters in Room Names" is enabled, the prompts show the normalized names instead of those that contain the special characters. + + Changed the value being used from name to fname, which always has the user-set name. + + Previous: + ![Screenshot from 2021-01-20 15-52-29](https://user-images.githubusercontent.com/38764067/105161642-9b31e780-5b37-11eb-8b0c-ec4b1414c948.png) + + Updated: ![Screenshot from 2021-01-20 15-50-19](https://user-images.githubusercontent.com/38764067/105161627-966d3380-5b37-11eb-9812-3dd9352b4f95.png) - Room's list showing all rooms with same name ([#20176](https://github.com/RocketChat/Rocket.Chat/pull/20176)) @@ -4818,9 +4988,9 @@ - Saving with blank email in edit user ([#20259](https://github.com/RocketChat/Rocket.Chat/pull/20259) by [@RonLek](https://github.com/RonLek)) - Disallows showing a success popup when email field is made blank in Edit User and instead shows the relevant error popup. - - + Disallows showing a success popup when email field is made blank in Edit User and instead shows the relevant error popup. + + https://user-images.githubusercontent.com/28918901/104960749-dbd81680-59fa-11eb-9c7b-2b257936f894.mp4 - Search list filter ([#19937](https://github.com/RocketChat/Rocket.Chat/pull/19937)) @@ -4867,7 +5037,7 @@ - Add translation of Edit Status in all languages ([#19916](https://github.com/RocketChat/Rocket.Chat/pull/19916) by [@sushant52](https://github.com/sushant52)) - Closes [#19915](https://github.com/RocketChat/Rocket.Chat/issues/19915) + Closes [#19915](https://github.com/RocketChat/Rocket.Chat/issues/19915) The profile options menu is well translated in many languages. However, Edit Status is the only button which is not well translated. With this change, the whole profile options will be properly translated in a lot of languages. - Bump axios from 0.18.0 to 0.18.1 ([#20055](https://github.com/RocketChat/Rocket.Chat/pull/20055) by [@dependabot[bot]](https://github.com/dependabot[bot])) @@ -4902,10 +5072,10 @@ - Regression: Announcement bar not showing properly Markdown content ([#20290](https://github.com/RocketChat/Rocket.Chat/pull/20290)) - **Before**: - ![image](https://user-images.githubusercontent.com/27704687/105273746-a4907380-5b7a-11eb-8121-aff665251c44.png) - - **After**: + **Before**: + ![image](https://user-images.githubusercontent.com/27704687/105273746-a4907380-5b7a-11eb-8121-aff665251c44.png) + + **After**: ![image](https://user-images.githubusercontent.com/27704687/105274050-2e404100-5b7b-11eb-93b2-b6282a7bed95.png) - regression: Announcement link open in new tab ([#20435](https://github.com/RocketChat/Rocket.Chat/pull/20435)) @@ -4920,23 +5090,23 @@ - Regression: Change sort icon ([#20177](https://github.com/RocketChat/Rocket.Chat/pull/20177)) - ### Before - ![image](https://user-images.githubusercontent.com/40830821/104366414-1bcd6400-54f8-11eb-9fc7-c6f13f07a61e.png) - - ### After + ### Before + ![image](https://user-images.githubusercontent.com/40830821/104366414-1bcd6400-54f8-11eb-9fc7-c6f13f07a61e.png) + + ### After ![image](https://user-images.githubusercontent.com/40830821/104366542-4cad9900-54f8-11eb-83ca-acb99899515a.png) - Regression: Custom field labels are not displayed properly on Omnichannel Contact Profile form ([#20393](https://github.com/RocketChat/Rocket.Chat/pull/20393) by [@rafaelblink](https://github.com/rafaelblink)) - ### Before - ![image](https://user-images.githubusercontent.com/2493803/105780399-20116c80-5f4f-11eb-9620-0901472e453b.png) - - ![image](https://user-images.githubusercontent.com/2493803/105780420-2e5f8880-5f4f-11eb-8e93-8115ebc685be.png) - - ### After - - ![image](https://user-images.githubusercontent.com/2493803/105780832-1ccab080-5f50-11eb-8042-188dd0c41904.png) - + ### Before + ![image](https://user-images.githubusercontent.com/2493803/105780399-20116c80-5f4f-11eb-9620-0901472e453b.png) + + ![image](https://user-images.githubusercontent.com/2493803/105780420-2e5f8880-5f4f-11eb-8e93-8115ebc685be.png) + + ### After + + ![image](https://user-images.githubusercontent.com/2493803/105780832-1ccab080-5f50-11eb-8042-188dd0c41904.png) + ![image](https://user-images.githubusercontent.com/2493803/105780911-500d3f80-5f50-11eb-96e0-7df3f179dbd5.png) - Regression: ESLint Warning - explicit-function-return-type ([#20434](https://github.com/RocketChat/Rocket.Chat/pull/20434) by [@aditya-mitra](https://github.com/aditya-mitra)) @@ -4953,8 +5123,8 @@ - Regression: Fixed update room avatar issue. ([#20433](https://github.com/RocketChat/Rocket.Chat/pull/20433) by [@Darshilp326](https://github.com/Darshilp326)) - Users can now update their room avatar without any error. - + Users can now update their room avatar without any error. + https://user-images.githubusercontent.com/55157259/105951602-560d3880-6096-11eb-97a5-b5eb9a28b58d.mp4 - Regression: Info Page Icon style and usage graph breaking ([#20180](https://github.com/RocketChat/Rocket.Chat/pull/20180)) @@ -4971,11 +5141,11 @@ - Regression: Unread superposing announcement. ([#20306](https://github.com/RocketChat/Rocket.Chat/pull/20306)) - ### Before - ![image](https://user-images.githubusercontent.com/40830821/105412619-c2f67d80-5c13-11eb-8204-5932ea880c8a.png) - - - ### After + ### Before + ![image](https://user-images.githubusercontent.com/40830821/105412619-c2f67d80-5c13-11eb-8204-5932ea880c8a.png) + + + ### After ![image](https://user-images.githubusercontent.com/40830821/105411176-d1439a00-5c11-11eb-8d1b-ea27c8485214.png) - Regression: User Dropdown margin ([#20222](https://github.com/RocketChat/Rocket.Chat/pull/20222)) @@ -5263,8 +5433,8 @@ - Hightlights validation on Account Preferences page ([#19902](https://github.com/RocketChat/Rocket.Chat/pull/19902) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) - This PR fixes two issues in the account settings "preferences" panel. - Once set, the "Highlighted Words" setting cannot be reset to an empty string. This was fixed by changing the string validation from checking the length to checking the type of variable. + This PR fixes two issues in the account settings "preferences" panel. + Once set, the "Highlighted Words" setting cannot be reset to an empty string. This was fixed by changing the string validation from checking the length to checking the type of variable. Secondly, it tracks the changes to correctly identify if changes after the last "save changes" action have been made, using an "updates" state variable, instead of just comparing against the initialValue that does not change on clicking "save changes". - Image preview for image URLs on messages ([#19734](https://github.com/RocketChat/Rocket.Chat/pull/19734) by [@g-thome](https://github.com/g-thome)) @@ -5323,10 +5493,14 @@ - Chore: Update Pull Request template ([#19768](https://github.com/RocketChat/Rocket.Chat/pull/19768)) - Improve the template of Pull Requests in order to make it clear reducing duplicated information and removing the visible checklists that were generating noise and misunderstanding with the PR progress. - - Moved the checklists to inside comments - - Merge the changelog and proposed changes sections to have a single source of description that goes to the changelog - - Remove the screenshot section, they can be added inside the description + Improve the template of Pull Requests in order to make it clear reducing duplicated information and removing the visible checklists that were generating noise and misunderstanding with the PR progress. + + - Moved the checklists to inside comments + + - Merge the changelog and proposed changes sections to have a single source of description that goes to the changelog + + - Remove the screenshot section, they can be added inside the description + - Changed the proposed changes title to incentivizing the usage of images and videos - Frontend folder structure ([#19631](https://github.com/RocketChat/Rocket.Chat/pull/19631)) @@ -5361,11 +5535,11 @@ - Regression: Double Scrollbars on tables ([#19980](https://github.com/RocketChat/Rocket.Chat/pull/19980)) - Before: - ![image](https://user-images.githubusercontent.com/40830821/103242719-0ec84680-4936-11eb-87a7-68b6eea8de7b.png) - - - After: + Before: + ![image](https://user-images.githubusercontent.com/40830821/103242719-0ec84680-4936-11eb-87a7-68b6eea8de7b.png) + + + After: ![image](https://user-images.githubusercontent.com/40830821/103242680-ee988780-4935-11eb-99e2-a95de99f78f1.png) - Regression: Failed autolinker and markdown rendering ([#19831](https://github.com/RocketChat/Rocket.Chat/pull/19831)) @@ -5384,7 +5558,7 @@ - Regression: Omnichannel Custom Fields Form no longer working after refactoring ([#19948](https://github.com/RocketChat/Rocket.Chat/pull/19948)) - The Omnichannel `Custom Fields` form is not working anymore after some refactorings on client-side. + The Omnichannel `Custom Fields` form is not working anymore after some refactorings on client-side. When the user clicks on `Custom Field` in the Omnichannel menu, a blank page appears. - Regression: polishing licenses endpoints ([#19981](https://github.com/RocketChat/Rocket.Chat/pull/19981) by [@g-thome](https://github.com/g-thome)) @@ -5583,8 +5757,8 @@ - Bundle Size Client ([#19533](https://github.com/RocketChat/Rocket.Chat/pull/19533)) - temporarily removes some codeblock languages - Moved some libraries to dynamic imports + temporarily removes some codeblock languages + Moved some libraries to dynamic imports Removed some shared code not used on the client side - Forward Omnichannel room to agent in another department ([#19576](https://github.com/RocketChat/Rocket.Chat/pull/19576) by [@mrfigueiredo](https://github.com/mrfigueiredo)) @@ -6665,8 +6839,10 @@ - **2FA:** Password enforcement setting and 2FA protection when saving settings or resetting E2E encryption ([#18640](https://github.com/RocketChat/Rocket.Chat/pull/18640)) - - Increase the 2FA remembering time from 5min to 30min - - Add new setting to enforce 2FA password fallback (enabled only for new installations) + - Increase the 2FA remembering time from 5min to 30min + + - Add new setting to enforce 2FA password fallback (enabled only for new installations) + - Require 2FA to save settings and reset E2E Encryption keys - **Omnichannel:** Allow set other agent status via method `livechat:changeLivechatStatus ` ([#18571](https://github.com/RocketChat/Rocket.Chat/pull/18571)) @@ -6684,7 +6860,7 @@ - 2FA by Email setting showing for the user even when disabled by the admin ([#18473](https://github.com/RocketChat/Rocket.Chat/pull/18473)) - The option to disable/enable the **Two-factor authentication via Email** at `Account > Security > Two Factor Authentication + The option to disable/enable the **Two-factor authentication via Email** at `Account > Security > Two Factor Authentication ` was visible even when the setting **Enable Two Factor Authentication via Email** at `Admin > Accounts > Two Factor Authentication` was disabled leading to misbehavior since the functionality was disabled. - Agents enabledDepartment attribute not set on collection ([#18614](https://github.com/RocketChat/Rocket.Chat/pull/18614) by [@paulobernardoaf](https://github.com/paulobernardoaf)) @@ -7034,13 +7210,16 @@ - Mention autocomplete UI and performance improvements ([#18309](https://github.com/RocketChat/Rocket.Chat/pull/18309)) - * New setting to configure the number of suggestions `Admin > Layout > User Interface > Number of users' autocomplete suggestions` (default 5) - * The UI shows whenever the user is not a member of the room - * The UI shows when the suggestion came from the last messages for quick selection/reply - * The suggestions follow this order: - * The user with the exact username and member of the room - * The user with the exact username but not a member of the room (if allowed to list non-members) - * The users containing the text in username, name or nickname and member of the room + * New setting to configure the number of suggestions `Admin > Layout > User Interface > Number of users' autocomplete suggestions` (default 5) + + * The UI shows whenever the user is not a member of the room + + * The UI shows when the suggestion came from the last messages for quick selection/reply + + * The suggestions follow this order: + * The user with the exact username and member of the room + * The user with the exact username but not a member of the room (if allowed to list non-members) + * The users containing the text in username, name or nickname and member of the room * The users containing the text in username, name or nickname and not a member of the room (if allowed to list non-members) - Message action styles ([#18190](https://github.com/RocketChat/Rocket.Chat/pull/18190)) @@ -7382,10 +7561,10 @@ - Split NOTIFICATIONS_SCHEDULE_DELAY into three separate variables ([#17669](https://github.com/RocketChat/Rocket.Chat/pull/17669) by [@jazztickets](https://github.com/jazztickets)) - Email notification delay can now be customized with the following environment variables: - NOTIFICATIONS_SCHEDULE_DELAY_ONLINE - NOTIFICATIONS_SCHEDULE_DELAY_AWAY - NOTIFICATIONS_SCHEDULE_DELAY_OFFLINE + Email notification delay can now be customized with the following environment variables: + NOTIFICATIONS_SCHEDULE_DELAY_ONLINE + NOTIFICATIONS_SCHEDULE_DELAY_AWAY + NOTIFICATIONS_SCHEDULE_DELAY_OFFLINE Setting the value to -1 disable notifications for that type. - Threads ([#17416](https://github.com/RocketChat/Rocket.Chat/pull/17416)) @@ -7785,11 +7964,11 @@ - **ENTERPRISE:** Omnichannel Last-Chatted Agent Preferred option ([#17666](https://github.com/RocketChat/Rocket.Chat/pull/17666)) - If activated, this feature will store the last agent that assisted each Omnichannel visitor when a conversation is taken. So, when a visitor returns(it works with any entry point, Livechat, Facebook, REST API, and so on) and starts a new chat, the routing system checks: - - 1 - The visitor object for any stored agent that the visitor has previously talked to; - 2 - If a previous agent is not found, the system will try to find a previous conversation of the same visitor. If a room is found, the system will get the previous agent from the room; - + If activated, this feature will store the last agent that assisted each Omnichannel visitor when a conversation is taken. So, when a visitor returns(it works with any entry point, Livechat, Facebook, REST API, and so on) and starts a new chat, the routing system checks: + + 1 - The visitor object for any stored agent that the visitor has previously talked to; + 2 - If a previous agent is not found, the system will try to find a previous conversation of the same visitor. If a room is found, the system will get the previous agent from the room; + After this process, if an agent has been found, the system will check the agent's availability to assist the new chat. If it's not available, then the routing system will get the next available agent in the queue. - **ENTERPRISE:** Support for custom Livechat registration form fields ([#17581](https://github.com/RocketChat/Rocket.Chat/pull/17581)) @@ -7894,9 +8073,12 @@ - Notification sounds ([#17616](https://github.com/RocketChat/Rocket.Chat/pull/17616)) - * Global CDN config was ignored when loading the sound files - * Upload of custom sounds wasn't getting the file extension correctly - * Some translations were missing + * Global CDN config was ignored when loading the sound files + + * Upload of custom sounds wasn't getting the file extension correctly + + * Some translations were missing + * Edit and delete of custom sounds were not working correctly - Omnichannel departments are not saved when the offline channel name is not defined ([#17553](https://github.com/RocketChat/Rocket.Chat/pull/17553)) @@ -8184,14 +8366,19 @@ - Better Push and Email Notification logic ([#17357](https://github.com/RocketChat/Rocket.Chat/pull/17357)) - We are still using the same logic to define which notifications every new message will generate, it takes some servers' settings, users's preferences and subscriptions' settings in consideration to determine who will receive each notification type (desktop, audio, email and mobile push), but now it doesn't check the user's status (online, away, offline) for email and mobile push notifications but send those notifications to a new queue with the following rules: - - - When the user is online the notification is scheduled to be sent in 120 seconds - - When the user is away the notification is scheduled to be sent in 120 seconds minus the amount of time he is away - - When the user is offline the notification is scheduled to be sent right away - - When the user reads a channel all the notifications for that user are removed (clear queue) - - When a notification is processed to be sent to a user and there are other scheduled notifications: - - All the scheduled notifications for that user are rescheduled to now + We are still using the same logic to define which notifications every new message will generate, it takes some servers' settings, users's preferences and subscriptions' settings in consideration to determine who will receive each notification type (desktop, audio, email and mobile push), but now it doesn't check the user's status (online, away, offline) for email and mobile push notifications but send those notifications to a new queue with the following rules: + + + - When the user is online the notification is scheduled to be sent in 120 seconds + + - When the user is away the notification is scheduled to be sent in 120 seconds minus the amount of time he is away + + - When the user is offline the notification is scheduled to be sent right away + + - When the user reads a channel all the notifications for that user are removed (clear queue) + + - When a notification is processed to be sent to a user and there are other scheduled notifications: + - All the scheduled notifications for that user are rescheduled to now - The current notification goes back to the queue to be processed ordered by creation date - Buttons to check/uncheck all users and channels on import ([#17207](https://github.com/RocketChat/Rocket.Chat/pull/17207)) @@ -8554,7 +8741,7 @@ - Translation via MS translate ([#16363](https://github.com/RocketChat/Rocket.Chat/pull/16363) by [@mrsimpson](https://github.com/mrsimpson)) - Adds Microsoft's translation service (https://translator.microsoft.com/) as a provider for translation of messages. + Adds Microsoft's translation service (https://translator.microsoft.com/) as a provider for translation of messages. In addition to implementing the interface (similar to google and DeepL), a small change has been done in order to display the translation provider on the UI. - Two Factor authentication via email ([#15949](https://github.com/RocketChat/Rocket.Chat/pull/15949)) @@ -8733,7 +8920,7 @@ - Slash command preview: Wrong item being selected, Horizontal scroll ([#16750](https://github.com/RocketChat/Rocket.Chat/pull/16750)) -- Text formatted to remain within button even on screen resize ([#14136](https://github.com/RocketChat/Rocket.Chat/pull/14136) by [@Rodriq](https://github.com/Rodriq)) +- Text formatted to remain within button even on screen resize ([#14136](https://github.com/RocketChat/Rocket.Chat/pull/14136)) - There is no option to pin a thread message by admin ([#16457](https://github.com/RocketChat/Rocket.Chat/pull/16457) by [@ashwaniYDV](https://github.com/ashwaniYDV)) @@ -8939,7 +9126,6 @@ - [@GOVINDDIXIT](https://github.com/GOVINDDIXIT) - [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@Nikhil713](https://github.com/Nikhil713) -- [@Rodriq](https://github.com/Rodriq) - [@aKn1ghtOut](https://github.com/aKn1ghtOut) - [@antkaz](https://github.com/antkaz) - [@aryamanpuri](https://github.com/aryamanpuri) @@ -8967,6 +9153,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 - [@PrajvalRaval](https://github.com/PrajvalRaval) +- [@Rodriq](https://github.com/Rodriq) - [@Sing-Li](https://github.com/Sing-Li) - [@d-gubert](https://github.com/d-gubert) - [@engelgabriel](https://github.com/engelgabriel) @@ -20093,4 +20280,4 @@ - [@graywolf336](https://github.com/graywolf336) - [@marceloschmidt](https://github.com/marceloschmidt) - [@rodrigok](https://github.com/rodrigok) -- [@sampaiodiego](https://github.com/sampaiodiego) \ No newline at end of file +- [@sampaiodiego](https://github.com/sampaiodiego) diff --git a/app/utils/rocketchat.info b/app/utils/rocketchat.info index 450fef947ac7b..9832fdaa3dc8b 100644 --- a/app/utils/rocketchat.info +++ b/app/utils/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "4.0.0" + "version": "4.0.1" } diff --git a/package-lock.json b/package-lock.json index ade23032cf4f1..3a22f5a3beefa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "Rocket.Chat", - "version": "4.0.0", + "version": "4.0.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 69405a6e4eb56..a6512913bb1eb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Rocket.Chat", "description": "The Ultimate Open Source WebChat Platform", - "version": "4.0.0", + "version": "4.0.1", "author": { "name": "Rocket.Chat", "url": "https://rocket.chat/" From f023a5254915af4a1071247ea8fe8cd1bb6cfad0 Mon Sep 17 00:00:00 2001 From: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> Date: Thu, 14 Oct 2021 19:13:03 +0530 Subject: [PATCH 010/137] Prevent starting Omni-Queue if Omnichannel is disabled (#23396) --- ee/app/livechat-enterprise/server/lib/LivechatEnterprise.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.js b/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.js index 2662bf4efe2ec..bca34b743ed4d 100644 --- a/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.js +++ b/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.js @@ -267,7 +267,7 @@ function shouldQueueStart() { : 'Stopping' } queue`); - routingSupportsAutoAssign ? queueWorker.start() : queueWorker.stop(); + routingSupportsAutoAssign && settings.get('Livechat_enabled') ? queueWorker.start() : queueWorker.stop(); } settings.get('Livechat_enabled', (_, value) => { From 914daa9edc513582fb2ac2a20bee58dea246ab52 Mon Sep 17 00:00:00 2001 From: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> Date: Tue, 12 Oct 2021 20:24:36 +0530 Subject: [PATCH 011/137] [FIX][ENTERPRISE] Omnichannel agent is not leaving the room when a forwarded chat is queued (#23404) * [FIX][EE] Omni agent is not leaving the room when a forwarded chat is queued * Apply suggestions from code review * Apply suggestions from code review --- app/livechat/server/lib/Helper.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/livechat/server/lib/Helper.js b/app/livechat/server/lib/Helper.js index 5edaee2494563..fb42f39497243 100644 --- a/app/livechat/server/lib/Helper.js +++ b/app/livechat/server/lib/Helper.js @@ -437,7 +437,9 @@ export const forwardRoomToDepartment = async (room, guest, transferData) => { Livechat.saveTransferHistory(room, transferData); if (oldServedBy) { - RoutingManager.removeAllRoomSubscriptions(room, servedBy); + // if chat is queued then we don't ignore the new servedBy agent bcs at this + // point the chat is not assigned to him/her and it is still in the queue + RoutingManager.removeAllRoomSubscriptions(room, !chatQueued && servedBy); } if (!chatQueued && servedBy) { Messages.createUserJoinWithRoomIdAndUser(rid, servedBy); @@ -448,6 +450,8 @@ export const forwardRoomToDepartment = async (room, guest, transferData) => { if (chatQueued) { logger.debug(`Forwarding succesful. Marking inquiry ${ inquiry._id } as ready`); LivechatInquiry.readyInquiry(inquiry._id); + LivechatRooms.removeAgentByRoomId(rid); + dispatchAgentDelegated(rid, null); const newInquiry = LivechatInquiry.findOneById(inquiry._id); await queueInquiry(room, newInquiry); From 6dba71ca563788faeadfe3d66e9eeae9250d9922 Mon Sep 17 00:00:00 2001 From: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> Date: Fri, 8 Oct 2021 22:49:56 +0530 Subject: [PATCH 012/137] [FIX] user/agent upload not working via Apps Engine after 3.16.0 (#23393) --- app/apps/server/bridges/uploads.ts | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/app/apps/server/bridges/uploads.ts b/app/apps/server/bridges/uploads.ts index 27dcbbff3dd45..ab7d6bb2da14d 100644 --- a/app/apps/server/bridges/uploads.ts +++ b/app/apps/server/bridges/uploads.ts @@ -7,7 +7,6 @@ import { FileUpload } from '../../../file-upload/server'; import { determineFileType } from '../../lib/misc/determineFileType'; import { AppServerOrchestrator } from '../orchestrator'; - const getUploadDetails = (details: IUploadDetails): Partial => { if (details.visitorToken) { const { userId, ...result } = details; @@ -56,17 +55,16 @@ export class AppUploadBridge extends UploadBridge { return new Promise(Meteor.bindEnvironment((resolve, reject) => { try { - const uploadedFile = fileStore.insertSync(getUploadDetails(details), buffer); - - if (details.visitorToken) { - Meteor.call('sendFileLivechatMessage', details.rid, details.visitorToken, uploadedFile); - } else { - Meteor.runAsUser(details.userId, () => { + Meteor.runAsUser(details.userId, () => { + const uploadedFile = fileStore.insertSync(getUploadDetails(details), buffer); + this.orch.debugLog(`The App ${ appId } has created an upload`, uploadedFile); + if (details.visitorToken) { + Meteor.call('sendFileLivechatMessage', details.rid, details.visitorToken, uploadedFile); + } else { Meteor.call('sendFileMessage', details.rid, null, uploadedFile); - }); - } - - resolve(this.orch.getConverters()?.get('uploads').convertToApp(uploadedFile)); + } + resolve(this.orch.getConverters()?.get('uploads').convertToApp(uploadedFile)); + }); } catch (err) { reject(err); } From 6e56144d45d750b99a1dac0fa2b360330ed98286 Mon Sep 17 00:00:00 2001 From: Aman-Maheshwari <50165440+Aman-Maheshwari@users.noreply.github.com> Date: Wed, 6 Oct 2021 23:21:31 +0530 Subject: [PATCH 013/137] [FIX] Attachment buttons overlap in mobile view (#23377) --- app/theme/client/imports/components/message-box.css | 1 - 1 file changed, 1 deletion(-) diff --git a/app/theme/client/imports/components/message-box.css b/app/theme/client/imports/components/message-box.css index 319e7fecb5eee..79a435d072bab 100644 --- a/app/theme/client/imports/components/message-box.css +++ b/app/theme/client/imports/components/message-box.css @@ -53,7 +53,6 @@ } &__action { - position: absolute; top: 4px; left: 0; From 32d1f4e2e102f6d40ff33a43637e613a357247ce Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 14 Oct 2021 14:10:30 -0300 Subject: [PATCH 014/137] Bump version to 4.0.2 --- .docker/Dockerfile.rhel | 2 +- .github/history.json | 55 +- .snapcraft/resources/prepareRocketChat | 2 +- .snapcraft/snap/snapcraft.yaml | 2 +- HISTORY.md | 2058 +++++++++++------------- app/utils/rocketchat.info | 2 +- package-lock.json | 2 +- package.json | 2 +- 8 files changed, 1036 insertions(+), 1089 deletions(-) diff --git a/.docker/Dockerfile.rhel b/.docker/Dockerfile.rhel index 4ced28448f5ee..e43e3b2b9e03f 100644 --- a/.docker/Dockerfile.rhel +++ b/.docker/Dockerfile.rhel @@ -1,6 +1,6 @@ FROM registry.access.redhat.com/ubi8/nodejs-12 -ENV RC_VERSION 4.0.1 +ENV RC_VERSION 4.0.2 MAINTAINER buildmaster@rocket.chat diff --git a/.github/history.json b/.github/history.json index 47437eee92fda..bf48c2f617f92 100644 --- a/.github/history.json +++ b/.github/history.json @@ -66133,6 +66133,59 @@ ] } ] + }, + "4.0.2": { + "node_version": "12.22.1", + "npm_version": "6.14.1", + "apps_engine_version": "1.28.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "23377", + "title": "[FIX] Attachment buttons overlap in mobile view", + "userLogin": "Aman-Maheshwari", + "milestone": "4.0.2", + "contributors": [ + "Aman-Maheshwari" + ] + }, + { + "pr": "23393", + "title": "[FIX] user/agent upload not working via Apps Engine after 3.16.0", + "userLogin": "murtaza98", + "description": "Fixes #22974", + "milestone": "4.0.2", + "contributors": [ + "murtaza98" + ] + }, + { + "pr": "23404", + "title": "[FIX][ENTERPRISE] Omnichannel agent is not leaving the room when a forwarded chat is queued", + "userLogin": "murtaza98", + "milestone": "4.0.2", + "contributors": [ + "murtaza98", + "web-flow" + ] + }, + { + "pr": "23396", + "title": "[FIX] Prevent starting Omni-Queue if Omnichannel is disabled", + "userLogin": "murtaza98", + "description": "Whenever the Routing system setting changes, and omnichannel is disabled, then we shouldn't start the queue.", + "milestone": "4.0.2", + "contributors": [ + "murtaza98" + ] + } + ] } } -} +} \ No newline at end of file diff --git a/.snapcraft/resources/prepareRocketChat b/.snapcraft/resources/prepareRocketChat index 18f24421facab..921e8e6e8ecb3 100755 --- a/.snapcraft/resources/prepareRocketChat +++ b/.snapcraft/resources/prepareRocketChat @@ -1,6 +1,6 @@ #!/bin/bash -curl -SLf "https://releases.rocket.chat/4.0.1/download/" -o rocket.chat.tgz +curl -SLf "https://releases.rocket.chat/4.0.2/download/" -o rocket.chat.tgz tar xf rocket.chat.tgz --strip 1 diff --git a/.snapcraft/snap/snapcraft.yaml b/.snapcraft/snap/snapcraft.yaml index f7948b8e2cfa5..a1a7d2cf2b034 100644 --- a/.snapcraft/snap/snapcraft.yaml +++ b/.snapcraft/snap/snapcraft.yaml @@ -7,7 +7,7 @@ # 5. `snapcraft snap` name: rocketchat-server -version: 4.0.1 +version: 4.0.2 summary: Rocket.Chat server description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ confinement: strict diff --git a/HISTORY.md b/HISTORY.md index 8327c691dd69f..77808f2288c73 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,4 +1,36 @@ +# 4.0.2 +`2021-10-14 · 4 🐛 · 2 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.22.1` +- NPM: `6.14.1` +- MongoDB: `3.6, 4.0, 4.2, 4.4, 5.0` +- Apps-Engine: `1.28.0` + +### 🐛 Bug fixes + + +- **ENTERPRISE:** Omnichannel agent is not leaving the room when a forwarded chat is queued ([#23404](https://github.com/RocketChat/Rocket.Chat/pull/23404)) + +- Attachment buttons overlap in mobile view ([#23377](https://github.com/RocketChat/Rocket.Chat/pull/23377) by [@Aman-Maheshwari](https://github.com/Aman-Maheshwari)) + +- Prevent starting Omni-Queue if Omnichannel is disabled ([#23396](https://github.com/RocketChat/Rocket.Chat/pull/23396)) + + Whenever the Routing system setting changes, and omnichannel is disabled, then we shouldn't start the queue. + +- user/agent upload not working via Apps Engine after 3.16.0 ([#23393](https://github.com/RocketChat/Rocket.Chat/pull/23393)) + + Fixes #22974 + +### 👩‍💻👨‍💻 Contributors 😍 + +- [@Aman-Maheshwari](https://github.com/Aman-Maheshwari) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@murtaza98](https://github.com/murtaza98) + # 4.0.1 `2021-10-06 · 7 🐛 · 1 🔍 · 7 👩‍💻👨‍💻` @@ -62,20 +94,18 @@ - **ENTERPRISE:** "Download CSV" button doesn't work in the Engagement Dashboard's Active Users section ([#23013](https://github.com/RocketChat/Rocket.Chat/pull/23013)) - - Fix "Download CSV" button in the Engagement Dashboard's Active Users section; - - - Add column headers to the CSV file downloaded from the Engagement Dashboard's Active Users section; - + - Fix "Download CSV" button in the Engagement Dashboard's Active Users section; + - Add column headers to the CSV file downloaded from the Engagement Dashboard's Active Users section; - Split the data in multiple CSV files. - **ENTERPRISE:** CSV file downloaded in the Engagement Dashboard's New Users section contains undefined data ([#23014](https://github.com/RocketChat/Rocket.Chat/pull/23014)) - - Fix CSV file downloaded in the Engagement Dashboard's New Users section; + - Fix CSV file downloaded in the Engagement Dashboard's New Users section; - Add column headers to the CSV file downloaded from the Engagement Dashboard's New Users section. - **ENTERPRISE:** Missing headers in CSV files downloaded from the Engagement Dashboard ([#23223](https://github.com/RocketChat/Rocket.Chat/pull/23223)) - - Add headers to all CSV files downloaded from the "Messages" and "Channels" tabs from the Engagement Dashboard; + - Add headers to all CSV files downloaded from the "Messages" and "Channels" tabs from the Engagement Dashboard; - Add headers to the CSV file downloaded from the "Users by time of day" section (in the "Users" tab). - LDAP Refactoring ([#23171](https://github.com/RocketChat/Rocket.Chat/pull/23171)) @@ -90,24 +120,17 @@ - Remove deprecated endpoints ([#23162](https://github.com/RocketChat/Rocket.Chat/pull/23162)) - The following REST endpoints were removed: - - - - `/api/v1/emoji-custom` - - - `/api/v1/info` - - - `/api/v1/permissions` - - - `/api/v1/permissions.list` - - The following Real time API Methods were removed: - - - - `getFullUserData` - - - `getServerInfo` - + The following REST endpoints were removed: + + - `/api/v1/emoji-custom` + - `/api/v1/info` + - `/api/v1/permissions` + - `/api/v1/permissions.list` + + The following Real time API Methods were removed: + + - `getFullUserData` + - `getServerInfo` - `livechat:saveOfficeHours` - Remove Google Vision features ([#23160](https://github.com/RocketChat/Rocket.Chat/pull/23160)) @@ -116,8 +139,8 @@ - Remove old migrations up to version 2.4.14 ([#23277](https://github.com/RocketChat/Rocket.Chat/pull/23277)) - To update to version 4.0.0 you'll need to be running at least version 3.0.0, otherwise you might loose some database migrations which might have unexpected effects. - + To update to version 4.0.0 you'll need to be running at least version 3.0.0, otherwise you might loose some database migrations which might have unexpected effects. + This aims to clean up the code, since upgrades jumping 2 major versions are too risky and hard to maintain, we'll keep only migration from that last major (in this case 3.x). - Remove patch info from endpoint /api/info for non-logged in users ([#16050](https://github.com/RocketChat/Rocket.Chat/pull/16050) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) @@ -126,18 +149,18 @@ - Stop sending audio notifications via stream ([#23108](https://github.com/RocketChat/Rocket.Chat/pull/23108)) - Remove audio preferences and make them tied to desktop notification preferences. - + Remove audio preferences and make them tied to desktop notification preferences. + TL;DR: new message sounds will play only if you receive a desktop notification. you'll still be able to chose to not play any sound though - Webhook will fail if user is not part of the channel ([#23310](https://github.com/RocketChat/Rocket.Chat/pull/23310)) - Remove deprecated behavior added by https://github.com/RocketChat/Rocket.Chat/pull/18024 that accepts webhook integrations sending messages even if the user is not part of the channel. - - Starting from 4.0.0 the webhook request will fail with `error-not-allowed` error: - - ``` - {"success":false,"error":"error-not-allowed"} + Remove deprecated behavior added by https://github.com/RocketChat/Rocket.Chat/pull/18024 that accepts webhook integrations sending messages even if the user is not part of the channel. + + Starting from 4.0.0 the webhook request will fail with `error-not-allowed` error: + + ``` + {"success":false,"error":"error-not-allowed"} ``` ### 🎉 New features @@ -155,26 +178,23 @@ - Seats Cap ([#23017](https://github.com/RocketChat/Rocket.Chat/pull/23017) by [@g-thome](https://github.com/g-thome)) - - Adding New Members - - Awareness of seats usage while adding new members - - Seats Cap about to be reached - - Seats Cap reached - - Request more seats - - - Warning Admins - - System telling admins max seats are about to exceed - - System telling admins max seats were exceed - - Metric on Info Page - - Request more seats - - - Warning Members - - Invite link - - Block creating new invite links - - Block existing invite links (feedback on register process) - - Register to Workspaces - - - Emails - - System telling admins max seats are about to exceed + - Adding New Members + - Awareness of seats usage while adding new members + - Seats Cap about to be reached + - Seats Cap reached + - Request more seats + - Warning Admins + - System telling admins max seats are about to exceed + - System telling admins max seats were exceed + - Metric on Info Page + - Request more seats + - Warning Members + - Invite link + - Block creating new invite links + - Block existing invite links (feedback on register process) + - Register to Workspaces + - Emails + - System telling admins max seats are about to exceed - System telling admins max seats were exceed ### 🚀 Improvements @@ -182,10 +202,10 @@ - **APPS:** New storage strategy for Apps-Engine file packages ([#22657](https://github.com/RocketChat/Rocket.Chat/pull/22657)) - This is an enabler for our initiative to support NPM packages in the Apps-Engine. - - Currently, the packages (zip files) for Rocket.Chat Apps are stored as a base64 encoded string in a document in the database, which constrains us due to the size limit of a document in MongoDB (16Mb). - + This is an enabler for our initiative to support NPM packages in the Apps-Engine. + + Currently, the packages (zip files) for Rocket.Chat Apps are stored as a base64 encoded string in a document in the database, which constrains us due to the size limit of a document in MongoDB (16Mb). + When we allow apps to include NPM packages, the size of the App package itself will be potentially _very large_ (I'm looking at you `node_modules`). Thus we'll be changing the strategy to store apps either with GridFS or the host's File System itself. - **APPS:** Return task ids when using the scheduler api ([#23023](https://github.com/RocketChat/Rocket.Chat/pull/23023)) @@ -225,9 +245,9 @@ - "Read Only" and "Allow Reacting" system messages are missing in rooms ([#23037](https://github.com/RocketChat/Rocket.Chat/pull/23037)) - - Add system message to notify changes on the **"Read Only"** setting; - - Add system message to notify changes on the **"Allow Reacting"** setting; - - Fix "Allow Reacting" setting's description (updated from "Only authorized users can write new messages" to "Only authorized users can react to messages"). + - Add system message to notify changes on the **"Read Only"** setting; + - Add system message to notify changes on the **"Allow Reacting"** setting; + - Fix "Allow Reacting" setting's description (updated from "Only authorized users can write new messages" to "Only authorized users can react to messages"). ![system-messages](https://user-images.githubusercontent.com/36537004/130883527-9eb47fcd-c8e5-41fb-af34-5d99bd0a6780.PNG) - Add check before placing chat on-hold to confirm that contact sent last message ([#23053](https://github.com/RocketChat/Rocket.Chat/pull/23053)) @@ -242,9 +262,9 @@ - Inaccurate use of 'Mobile notifications' instead of 'Push notifications' in i18n strings ([#22978](https://github.com/RocketChat/Rocket.Chat/pull/22978)) - - Fix inaccurate use of 'Mobile notifications' (which is misleading in German) by 'Push notifications'; - - Update `'Notification_Mobile_Default_For'` key to `'Notification_Push_Default_For'` (and text to 'Send Push Notifications For' for English Language); - - Update `'Accounts_Default_User_Preferences_mobileNotifications'` key to `'Accounts_Default_User_Preferences_pushNotifications'`; + - Fix inaccurate use of 'Mobile notifications' (which is misleading in German) by 'Push notifications'; + - Update `'Notification_Mobile_Default_For'` key to `'Notification_Push_Default_For'` (and text to 'Send Push Notifications For' for English Language); + - Update `'Accounts_Default_User_Preferences_mobileNotifications'` key to `'Accounts_Default_User_Preferences_pushNotifications'`; - Update `'Mobile_Notifications_Default_Alert'` key to `'Mobile_Push_Notifications_Default_Alert'`; - Logging out from other clients ([#23276](https://github.com/RocketChat/Rocket.Chat/pull/23276)) @@ -253,7 +273,7 @@ - Modals is cutting pixels of the content ([#23243](https://github.com/RocketChat/Rocket.Chat/pull/23243)) - Fuselage Dependency: [543](https://github.com/RocketChat/Rocket.Chat.Fuselage/pull/543) + Fuselage Dependency: [543](https://github.com/RocketChat/Rocket.Chat.Fuselage/pull/543) ![image](https://user-images.githubusercontent.com/27704687/134049227-3cd1deed-34ba-454f-a95e-e99b79a7a7b9.png) - Omnichannel On hold chats being forwarded to offline agents ([#23185](https://github.com/RocketChat/Rocket.Chat/pull/23185)) @@ -262,15 +282,15 @@ - Prevent users to edit an existing role when adding a new one with the same name used before. ([#22407](https://github.com/RocketChat/Rocket.Chat/pull/22407) by [@lucassartor](https://github.com/lucassartor)) - ### before - ![Peek 2021-07-13 16-31](https://user-images.githubusercontent.com/27704687/125513721-953d84f4-1c95-45ca-80e1-b00992b874f6.gif) - - ### after + ### before + ![Peek 2021-07-13 16-31](https://user-images.githubusercontent.com/27704687/125513721-953d84f4-1c95-45ca-80e1-b00992b874f6.gif) + + ### after ![Peek 2021-07-13 16-34](https://user-images.githubusercontent.com/27704687/125514098-91ee8014-51e5-4c62-9027-5538acf57d08.gif) - Remove doubled "Canned Responses" strings ([#23056](https://github.com/RocketChat/Rocket.Chat/pull/23056)) - - Remove doubled canned response setting introduced in #22703 (by setting id change); + - Remove doubled canned response setting introduced in #22703 (by setting id change); - Update "Canned Responses" keys to "Canned_Responses". - Remove margin from quote inside quote ([#21779](https://github.com/RocketChat/Rocket.Chat/pull/21779)) @@ -281,21 +301,16 @@ - Sidebar not closing when clicking in Home or Directory on mobile view ([#23218](https://github.com/RocketChat/Rocket.Chat/pull/23218)) - ### Additional fixed - - - Merge Burger menu components into a single component - - - Show a badge with no-read messages in the Burger Button: - ![image](https://user-images.githubusercontent.com/27704687/133679378-20fea2c0-4ac1-4b4e-886e-45154cc6afea.png) - + ### Additional fixed + - Merge Burger menu components into a single component + - Show a badge with no-read messages in the Burger Button: + ![image](https://user-images.githubusercontent.com/27704687/133679378-20fea2c0-4ac1-4b4e-886e-45154cc6afea.png) - remove useSidebarClose hook - Stop queue when Omnichannel is disabled or the routing method does not support it ([#23261](https://github.com/RocketChat/Rocket.Chat/pull/23261)) - - Add missing key logs - - - Stop queue (and logs) when livechat is disabled or when routing method does not support queue - + - Add missing key logs + - Stop queue (and logs) when livechat is disabled or when routing method does not support queue - Stop ignoring offline bot agents from delegation (previously, if a bot was offline, even with "Assign new conversations to bot agent" enabled, bot will be ignored and chat will be left in limbo (since bot was assigned, but offline). - Toolbox click not working on Safari(iOS) ([#23244](https://github.com/RocketChat/Rocket.Chat/pull/23244)) @@ -402,17 +417,17 @@ - Regression: Blank screen in Jitsi video calls ([#23322](https://github.com/RocketChat/Rocket.Chat/pull/23322)) - - Fix Jitsi calls being disposed even when "Open in new window" setting is disabled; + - Fix Jitsi calls being disposed even when "Open in new window" setting is disabled; - Fix misspelling on `CallJitsWithData.js` file name. - Regression: Create new loggers based on server log level ([#23297](https://github.com/RocketChat/Rocket.Chat/pull/23297)) - Regression: Fix app storage migration ([#23286](https://github.com/RocketChat/Rocket.Chat/pull/23286)) - The previous version of this migration didn't take into consideration apps that were installed prior to [Rocket.Chat@3.8.0](https://github.com/RocketChat/Rocket.Chat/releases/tag/3.8.0), which [removed the typescript compiler from the server](https://github.com/RocketChat/Rocket.Chat/pull/18687) and into the CLI. As a result, the zip files inside each installed app's document in the database had typescript files in them instead of the now required javascript files. - - As the new strategy of source code storage for apps changes the way the app is loaded, those zip files containing the source code are read everytime the app is started (or [in this particular case, updated](https://github.com/RocketChat/Rocket.Chat/pull/23286/files#diff-caf9f7a22478639e58d6514be039140a42ce1ab2d999c3efe5678c38ee36d0ccR43)), and as the zips' contents were wrong, the operation was failing. - + The previous version of this migration didn't take into consideration apps that were installed prior to [Rocket.Chat@3.8.0](https://github.com/RocketChat/Rocket.Chat/releases/tag/3.8.0), which [removed the typescript compiler from the server](https://github.com/RocketChat/Rocket.Chat/pull/18687) and into the CLI. As a result, the zip files inside each installed app's document in the database had typescript files in them instead of the now required javascript files. + + As the new strategy of source code storage for apps changes the way the app is loaded, those zip files containing the source code are read everytime the app is started (or [in this particular case, updated](https://github.com/RocketChat/Rocket.Chat/pull/23286/files#diff-caf9f7a22478639e58d6514be039140a42ce1ab2d999c3efe5678c38ee36d0ccR43)), and as the zips' contents were wrong, the operation was failing. + The fix extract the data from old apps and creates new zip files with the compiled `js` already present. - Regression: Fix Bugsnag not started error ([#23308](https://github.com/RocketChat/Rocket.Chat/pull/23308)) @@ -589,10 +604,8 @@ - **ENTERPRISE:** Maximum waiting time for chats in Omnichannel queue ([#22955](https://github.com/RocketChat/Rocket.Chat/pull/22955)) - - Add new settings to support closing chats that have been too long on waiting queue - - - Moved old settings to new "Queue Management" section - + - Add new settings to support closing chats that have been too long on waiting queue + - Moved old settings to new "Queue Management" section - Fix issue when closing a livechat room that caused client to not to know if room was open or not - Banner for the updates regarding authentication services ([#23055](https://github.com/RocketChat/Rocket.Chat/pull/23055) by [@g-thome](https://github.com/g-thome)) @@ -607,10 +620,10 @@ - Separate RegEx Settings for Channels and Usernames validation ([#21937](https://github.com/RocketChat/Rocket.Chat/pull/21937) by [@aditya-mitra](https://github.com/aditya-mitra)) - Now, there are 2 separate settings for validating names - One for **channels** and another for **usernames**. - - This change also removes the old `UTF8_Names_Validation` setting and adds 2 new settings `UTF8_User_Names_Validation` and `UTF8_Channel_Names_Validation`. - + Now, there are 2 separate settings for validating names - One for **channels** and another for **usernames**. + + This change also removes the old `UTF8_Names_Validation` setting and adds 2 new settings `UTF8_User_Names_Validation` and `UTF8_Channel_Names_Validation`. + https://user-images.githubusercontent.com/55396651/116969904-af5bb800-acd4-11eb-9fc4-dacac60cb08f.mp4 ### 🚀 Improvements @@ -626,13 +639,13 @@ - Rewrite File Upload Modal ([#22750](https://github.com/RocketChat/Rocket.Chat/pull/22750)) - Image preview: - ![image](https://user-images.githubusercontent.com/40830821/127223432-dccd2182-aec0-430f-8d70-03ac88aec791.png) - - Video preview: - ![image](https://user-images.githubusercontent.com/40830821/127225982-f8b21840-0d9c-4aff-a354-16188c7ed66e.png) - - Files larger than 10mb: + Image preview: + ![image](https://user-images.githubusercontent.com/40830821/127223432-dccd2182-aec0-430f-8d70-03ac88aec791.png) + + Video preview: + ![image](https://user-images.githubusercontent.com/40830821/127225982-f8b21840-0d9c-4aff-a354-16188c7ed66e.png) + + Files larger than 10mb: ![image](https://user-images.githubusercontent.com/40830821/127222611-5265040f-a06b-4ec5-b528-89b40e6a9072.png) - Types from currentChatsPage.tsx ([#22967](https://github.com/RocketChat/Rocket.Chat/pull/22967)) @@ -648,14 +661,14 @@ - "Users By Time of the Day" chart displays incorrect data for Local Timezone ([#22836](https://github.com/RocketChat/Rocket.Chat/pull/22836)) - - Add local timezone conversion to the "Users By Time of the Day" chart in the Engagement Dashboard; + - Add local timezone conversion to the "Users By Time of the Day" chart in the Engagement Dashboard; - Simplify date creations by using `endOf` and `startOf` methods. - Atlassian Crowd connection not working ([#22996](https://github.com/RocketChat/Rocket.Chat/pull/22996) by [@piotrkochan](https://github.com/piotrkochan)) - Audio recording doesn't stop in direct messages on channel switch ([#22880](https://github.com/RocketChat/Rocket.Chat/pull/22880)) - - Cancel audio recordings on message bar destroy event. + - Cancel audio recordings on message bar destroy event. ![test-22372](https://user-images.githubusercontent.com/36537004/128569780-d83747b0-fb9c-4dc6-9bc5-7ae573e720c8.gif) - Bad words falling if message is empty ([#22930](https://github.com/RocketChat/Rocket.Chat/pull/22930)) @@ -680,23 +693,21 @@ - Return transcript/dashboards based on timezone settings ([#22850](https://github.com/RocketChat/Rocket.Chat/pull/22850)) - - Added new setting to manage timezones - - - Applied new setting to omnichannel dashboards (realtime, analytics) [NOTE: Other dashboards aren't using this setting actually) - + - Added new setting to manage timezones + - Applied new setting to omnichannel dashboards (realtime, analytics) [NOTE: Other dashboards aren't using this setting actually) - Change getAnalyticsBetweenDate query to filter out system messages instead of substracting them - Tab margin style ([#22851](https://github.com/RocketChat/Rocket.Chat/pull/22851)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/128103848-2a25ba7e-0e59-4502-9bcd-2569cad9379a.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/128103848-2a25ba7e-0e59-4502-9bcd-2569cad9379a.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/128103633-ec7b93fc-4667-4dc9-bad3-bfffaff3974e.png) - Threads and discussions searches don't display proper results ([#22914](https://github.com/RocketChat/Rocket.Chat/pull/22914)) - - _Fix_ issue in discussions search (which wasn't working after a search with no results was made); + - _Fix_ issue in discussions search (which wasn't working after a search with no results was made); - _Improve_ discussions and threads searches: both searches (`chat.getDiscussions` and `chat.getThreadsList`) are now case insensitive (do NOT differ capital from lower letters) and match incomplete words or terms. - Threads List being requested more than expected ([#22879](https://github.com/RocketChat/Rocket.Chat/pull/22879)) @@ -731,8 +742,8 @@ - Chore: Script to start Rocket.Chat in HA mode during development ([#22398](https://github.com/RocketChat/Rocket.Chat/pull/22398)) - Sometimes we need to start Rocket.Chat in High-Availability mode (cluster) during development to test how a feature behaves or hunt down a bug. Currently, this involves a lot of commands with details that might be lost if you haven't done it in a while. - + Sometimes we need to start Rocket.Chat in High-Availability mode (cluster) during development to test how a feature behaves or hunt down a bug. Currently, this involves a lot of commands with details that might be lost if you haven't done it in a while. + This PR intends to provide a really simple way for us to start many instances of Rocket.Chat connected in a cluster. - Chore: Update Livechat widget to 1.9.4 ([#22990](https://github.com/RocketChat/Rocket.Chat/pull/22990)) @@ -749,13 +760,13 @@ - Regression: File upload name suggestion ([#22953](https://github.com/RocketChat/Rocket.Chat/pull/22953)) - Before: - ![image](https://user-images.githubusercontent.com/40830821/129774936-ecdbe9a1-5e3f-4a0a-ad1e-6f13eb15c60b.png) - ![image](https://user-images.githubusercontent.com/40830821/129775011-fb0df01d-74e4-41ae-bb47-dcf4cc17735e.png) - - - After: - ![image](https://user-images.githubusercontent.com/40830821/129774877-928a8aa0-c003-4e57-8b33-ea6accc32774.png) + Before: + ![image](https://user-images.githubusercontent.com/40830821/129774936-ecdbe9a1-5e3f-4a0a-ad1e-6f13eb15c60b.png) + ![image](https://user-images.githubusercontent.com/40830821/129775011-fb0df01d-74e4-41ae-bb47-dcf4cc17735e.png) + + + After: + ![image](https://user-images.githubusercontent.com/40830821/129774877-928a8aa0-c003-4e57-8b33-ea6accc32774.png) ![image](https://user-images.githubusercontent.com/40830821/129774972-d67debaf-0ce9-44fb-93cb-d7612dd18edf.png) - Regression: Fix creation of self-DMs ([#23015](https://github.com/RocketChat/Rocket.Chat/pull/23015)) @@ -823,8 +834,7 @@ - Fix Auto Selection algorithm on community edition ([#22991](https://github.com/RocketChat/Rocket.Chat/pull/22991)) - - When using the autoselection algo on community editions, all agents were marked as unavailable due to an unapplied filter - + - When using the autoselection algo on community editions, all agents were marked as unavailable due to an unapplied filter - Fixed an issue when both user & system setting to manange EE max number of chats allowed were set to 0
@@ -864,7 +874,7 @@ - Apps-Engine's scheduler failing to update run tasks ([#22882](https://github.com/RocketChat/Rocket.Chat/pull/22882)) - [Agenda](https://github.com/agenda/agenda), the library that manages scheduling, depended on setting a job property named `nextRunAt` as `undefined` to signal whether it should be run on schedule or not. [Rocket.Chat's current Mongo driver](https://github.com/RocketChat/Rocket.Chat/pull/22399) ignores `undefined` values when updating documents and this was causing jobs to never stop running as Agenda couldn't clear that property (set them as `undefined`). + [Agenda](https://github.com/agenda/agenda), the library that manages scheduling, depended on setting a job property named `nextRunAt` as `undefined` to signal whether it should be run on schedule or not. [Rocket.Chat's current Mongo driver](https://github.com/RocketChat/Rocket.Chat/pull/22399) ignores `undefined` values when updating documents and this was causing jobs to never stop running as Agenda couldn't clear that property (set them as `undefined`). This updates Rocket.Chat's dependency on Agenda.js to point to [a fork that fixes the problem](https://github.com/RocketChat/agenda/releases/tag/3.1.2). - Close omnichannel conversations when agent is deactivated ([#22917](https://github.com/RocketChat/Rocket.Chat/pull/22917)) @@ -918,7 +928,7 @@ - Monitoring Track messages' round trip time ([#22676](https://github.com/RocketChat/Rocket.Chat/pull/22676)) - Track messages' roundtrip time from backend saves time to the time when received back from the oplog allowing track of oplog slowness. + Track messages' roundtrip time from backend saves time to the time when received back from the oplog allowing track of oplog slowness. Prometheus metric: `rocketchat_messages_roundtrip_time` - REST endpoint to remove User from Role ([#20485](https://github.com/RocketChat/Rocket.Chat/pull/20485) by [@Cosnavel](https://github.com/Cosnavel) & [@lucassartor](https://github.com/lucassartor)) @@ -930,22 +940,19 @@ - Change message deletion confirmation modal to toast ([#22544](https://github.com/RocketChat/Rocket.Chat/pull/22544)) - Changed a timed modal for a toast message + Changed a timed modal for a toast message ![image](https://user-images.githubusercontent.com/40830821/124192670-0646f900-da9c-11eb-941c-9ae35421f6ef.png) - Configuration for indices in Apps-Engine models ([#22705](https://github.com/RocketChat/Rocket.Chat/pull/22705)) - * Add `appId` field to the data saved by the Scheduler - - * Add `appId` index to `rocketchat_apps_persistence` model - - * Skip "trash collection" when deleting records from `rocketchat_apps_persistence` - - * Add a new setting to control for how long we should keep logs from the apps - - ![image](https://user-images.githubusercontent.com/1810309/126246666-907f9d98-1d84-4dfe-a80a-7dd874d36fa8.png) - - + * Add `appId` field to the data saved by the Scheduler + * Add `appId` index to `rocketchat_apps_persistence` model + * Skip "trash collection" when deleting records from `rocketchat_apps_persistence` + * Add a new setting to control for how long we should keep logs from the apps + + ![image](https://user-images.githubusercontent.com/1810309/126246666-907f9d98-1d84-4dfe-a80a-7dd874d36fa8.png) + + ![image](https://user-images.githubusercontent.com/1810309/126246655-2ce3cb5f-b2f5-456e-a9c4-beccd9b3ef41.png) - Make `shortcut` field of canned responses unique ([#22700](https://github.com/RocketChat/Rocket.Chat/pull/22700)) @@ -968,38 +975,37 @@ - Replace remaing discussion creation modals with React modal. ([#22448](https://github.com/RocketChat/Rocket.Chat/pull/22448)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/123840524-cbe72b80-d8e4-11eb-9ddb-23a9f9d90aac.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/123840524-cbe72b80-d8e4-11eb-9ddb-23a9f9d90aac.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/123840219-74e15680-d8e4-11eb-95aa-00a990ffe0e7.png) - Return open room if available for visitors ([#22742](https://github.com/RocketChat/Rocket.Chat/pull/22742)) - Rewrite Enter Encryption Password Modal ([#22456](https://github.com/RocketChat/Rocket.Chat/pull/22456)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/123182889-bbf3c580-d466-11eb-8d4d-9cfc3d224e33.png) - - ### after - ![image](https://user-images.githubusercontent.com/27704687/123182916-cada7800-d466-11eb-96ee-850be190d419.png) - - ### Aditional Improves: - + ### before + ![image](https://user-images.githubusercontent.com/27704687/123182889-bbf3c580-d466-11eb-8d4d-9cfc3d224e33.png) + + ### after + ![image](https://user-images.githubusercontent.com/27704687/123182916-cada7800-d466-11eb-96ee-850be190d419.png) + + ### Aditional Improves: - Added a visual validation in the password field - Rewrite OTR modals ([#22583](https://github.com/RocketChat/Rocket.Chat/pull/22583)) - ![image](https://user-images.githubusercontent.com/40830821/124513267-cb510800-ddb0-11eb-8165-f103029c348f.png) - ![image](https://user-images.githubusercontent.com/40830821/124513354-04897800-ddb1-11eb-96f4-41fe906ca0d7.png) + ![image](https://user-images.githubusercontent.com/40830821/124513267-cb510800-ddb0-11eb-8165-f103029c348f.png) + ![image](https://user-images.githubusercontent.com/40830821/124513354-04897800-ddb1-11eb-96f4-41fe906ca0d7.png) ![image](https://user-images.githubusercontent.com/40830821/124513395-1b2fcf00-ddb1-11eb-83e4-3f8f9b4676ba.png) - Rewrite Save Encryption Password Modal ([#22447](https://github.com/RocketChat/Rocket.Chat/pull/22447)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/122980201-c337a800-d36e-11eb-8e2b-68534cea8e1e.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/122980201-c337a800-d36e-11eb-8e2b-68534cea8e1e.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/122980409-f8dc9100-d36e-11eb-9c15-aff779c84a91.png) - Rewrite sidebar footer as React Component ([#22687](https://github.com/RocketChat/Rocket.Chat/pull/22687)) @@ -1014,12 +1020,12 @@ - Wrong error message when trying to create a blocked username ([#22452](https://github.com/RocketChat/Rocket.Chat/pull/22452) by [@lucassartor](https://github.com/lucassartor)) - When trying to create a user with a blocked username, the UI was showing generic error message that it wasn't very detailed. - - Old error message: - ![image](https://user-images.githubusercontent.com/49413772/123120080-6d203e80-d41a-11eb-8c87-64e34334c856.png) - - New error message: + When trying to create a user with a blocked username, the UI was showing generic error message that it wasn't very detailed. + + Old error message: + ![image](https://user-images.githubusercontent.com/49413772/123120080-6d203e80-d41a-11eb-8c87-64e34334c856.png) + + New error message: ![aaa](https://user-images.githubusercontent.com/49413772/123120251-8c1ed080-d41a-11eb-8dc2-d7484923d851.PNG) ### 🐛 Bug fixes @@ -1027,19 +1033,19 @@ - **ENTERPRISE:** Engagement Dashboard displaying incorrect data about active users ([#22381](https://github.com/RocketChat/Rocket.Chat/pull/22381)) - - Fix sessions' and users' grouping in the Engagement Dashboard API endpoints; - - Fix the data displayed in the charts from the "Active users", "Users by time of day" and "When is the chat busier?" sections of the Engagement Dashboard; + - Fix sessions' and users' grouping in the Engagement Dashboard API endpoints; + - Fix the data displayed in the charts from the "Active users", "Users by time of day" and "When is the chat busier?" sections of the Engagement Dashboard; - Replace label used to describe the amount of Active Users in the License section of the Info page. - **ENTERPRISE:** Make AutoSelect algo take current agent load in consideration ([#22611](https://github.com/RocketChat/Rocket.Chat/pull/22611)) - **ENTERPRISE:** Race condition on Omnichannel visitor abandoned callback ([#22413](https://github.com/RocketChat/Rocket.Chat/pull/22413)) - As you can see [here](https://github.com/RocketChat/Rocket.Chat/blob/857791c39c97b51b5b6fd3718e0c816959a81c3b/ee/app/livechat-enterprise/server/lib/Helper.js#L127) the `predictedVisitorAbandonment` flag is not set if the room object doesn't have `v.lastMessageTs` property. So we need to always make sure the `v.lastMessageTs` is set before this method is called. - - Currently the `v.lastMessageTs` is being set in [this](https://github.com/RocketChat/Rocket.Chat/blob/857791c39c97b51b5b6fd3718e0c816959a81c3b/app/livechat/server/hooks/saveLastVisitorMessageTs.js#L4) (lets call this **hook-1**) hook which has `HIGH` priority - and the `predictedVisitorAbandonment` check is inturn performed in [this](https://github.com/RocketChat/Rocket.Chat/blob/857791c39c97b51b5b6fd3718e0c816959a81c3b/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.js#L5) (let call this **hook-2**) hook which is also `HIGH` priority. - + As you can see [here](https://github.com/RocketChat/Rocket.Chat/blob/857791c39c97b51b5b6fd3718e0c816959a81c3b/ee/app/livechat-enterprise/server/lib/Helper.js#L127) the `predictedVisitorAbandonment` flag is not set if the room object doesn't have `v.lastMessageTs` property. So we need to always make sure the `v.lastMessageTs` is set before this method is called. + + Currently the `v.lastMessageTs` is being set in [this](https://github.com/RocketChat/Rocket.Chat/blob/857791c39c97b51b5b6fd3718e0c816959a81c3b/app/livechat/server/hooks/saveLastVisitorMessageTs.js#L4) (lets call this **hook-1**) hook which has `HIGH` priority + and the `predictedVisitorAbandonment` check is inturn performed in [this](https://github.com/RocketChat/Rocket.Chat/blob/857791c39c97b51b5b6fd3718e0c816959a81c3b/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.js#L5) (let call this **hook-2**) hook which is also `HIGH` priority. + So ideally we'd except the **hook-1** to be called b4 **hook-2**, however currently since both of them are at same priority, there is no way to control which one is executed first. Hence in this PR, I'm making the priority of **hook-2** as `MEDIUM` to keeping the priority of **hook-1** the same as b4, i.e. `HIGH`. This should make sure that the **hook-1** is always executed b4 **hook-2** - Admin page crashing when commit hash is null ([#22057](https://github.com/RocketChat/Rocket.Chat/pull/22057) by [@cprice-kgi](https://github.com/cprice-kgi)) @@ -1048,41 +1054,39 @@ - Blank screen in message auditing DM tab ([#22763](https://github.com/RocketChat/Rocket.Chat/pull/22763)) - The DM tab in message auditing was displaying a blank screen, instead of the actual tab. - + The DM tab in message auditing was displaying a blank screen, instead of the actual tab. + ![image](https://user-images.githubusercontent.com/28611993/127041404-dfca7f6a-2b8b-4c15-9cbd-c6238fac0063.png) - Bugs in AutoCompleteDepartment ([#22414](https://github.com/RocketChat/Rocket.Chat/pull/22414)) - Call button is still displayed when the user doesn't have permission to use it ([#22170](https://github.com/RocketChat/Rocket.Chat/pull/22170)) - - Hide 'Call' buttons from the tab bar for muted users; - + - Hide 'Call' buttons from the tab bar for muted users; - Display an error when a muted user attempts to enter a call using the 'Click to Join!' button. - Can't see full user profile on team's room ([#22355](https://github.com/RocketChat/Rocket.Chat/pull/22355)) - ### before - ![before](https://user-images.githubusercontent.com/27704687/121966860-bbac4980-cd45-11eb-8d48-2b0457110fc7.gif) - - ### after - ![after](https://user-images.githubusercontent.com/27704687/121966870-bea73a00-cd45-11eb-9c89-ec52ac17e20f.gif) - - ### aditional fix :rocket: - + ### before + ![before](https://user-images.githubusercontent.com/27704687/121966860-bbac4980-cd45-11eb-8d48-2b0457110fc7.gif) + + ### after + ![after](https://user-images.githubusercontent.com/27704687/121966870-bea73a00-cd45-11eb-9c89-ec52ac17e20f.gif) + + ### aditional fix :rocket: - unnecessary `TeamsMembers` component removed - Cannot create a discussion from top left sidebar as a user ([#22618](https://github.com/RocketChat/Rocket.Chat/pull/22618) by [@lucassartor](https://github.com/lucassartor)) - When trying to create a discussion using the top left sidebar modal with an role that don't have the `view-other-user-channels ` permission, an empty list would be shown, which is a wrong behavior. - Also, when being able to use this modal, discussions were listed as options, which is also a wrong behavior as there can't be nested discussions. - - This PR looks to fix both these issues. - - **Old behavior:** - ![old](https://user-images.githubusercontent.com/49413772/124960017-3c333280-dff2-11eb-86cd-b2638311517e.png) - - **New behavior:** + When trying to create a discussion using the top left sidebar modal with an role that don't have the `view-other-user-channels ` permission, an empty list would be shown, which is a wrong behavior. + Also, when being able to use this modal, discussions were listed as options, which is also a wrong behavior as there can't be nested discussions. + + This PR looks to fix both these issues. + + **Old behavior:** + ![old](https://user-images.githubusercontent.com/49413772/124960017-3c333280-dff2-11eb-86cd-b2638311517e.png) + + **New behavior:** ![image](https://user-images.githubusercontent.com/49413772/124958882-05a8e800-dff1-11eb-8203-b34a4f1c98a0.png) - Channel is automatically getting added to the first option in move to team feature ([#22670](https://github.com/RocketChat/Rocket.Chat/pull/22670)) @@ -1097,12 +1101,12 @@ - Create discussion modal - cancel button and invite users alignment ([#22718](https://github.com/RocketChat/Rocket.Chat/pull/22718)) - Changes in "open discussion" modal - - > Added cancel button - > Fixed alignment in invite user - - + Changes in "open discussion" modal + + > Added cancel button + > Fixed alignment in invite user + + ![image](https://user-images.githubusercontent.com/28611993/126388304-6ac76574-6924-426e-843d-afd53dc1c874.png) - crush in the getChannelHistory method ([#22667](https://github.com/RocketChat/Rocket.Chat/pull/22667) by [@MaestroArt](https://github.com/MaestroArt)) @@ -1137,29 +1141,27 @@ - Quote message not working for Livechat visitors ([#22586](https://github.com/RocketChat/Rocket.Chat/pull/22586)) - ### Before: - ![image](https://user-images.githubusercontent.com/34130764/124583613-de2b1180-de70-11eb-82aa-18564b317626.png) - ### After: + ### Before: + ![image](https://user-images.githubusercontent.com/34130764/124583613-de2b1180-de70-11eb-82aa-18564b317626.png) + ### After: ![image](https://user-images.githubusercontent.com/34130764/124583775-12063700-de71-11eb-8ab5-b0169fac2d40.png) - Redirect to login after delete own account ([#22499](https://github.com/RocketChat/Rocket.Chat/pull/22499)) - Redirect the user to login after delete own account - - ### Aditional fixes: - - - Visual issue in password input on Delete Own Account Modal - - ### before - ![image](https://user-images.githubusercontent.com/27704687/123711503-f5ea1080-d846-11eb-96aa-8ed638ca665c.png) - - ### after + Redirect the user to login after delete own account + + ### Aditional fixes: + - Visual issue in password input on Delete Own Account Modal + + ### before + ![image](https://user-images.githubusercontent.com/27704687/123711503-f5ea1080-d846-11eb-96aa-8ed638ca665c.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/123711336-b3c0cf00-d846-11eb-9408-a686d8668ba5.png) - Remove stack traces from Meteor errors when debug setting is disabled ([#22699](https://github.com/RocketChat/Rocket.Chat/pull/22699)) - - Fix 'not iterable' errors in the `normalizeMessage` function; - + - Fix 'not iterable' errors in the `normalizeMessage` function; - Remove stack traces from errors thrown by the `jitsi:updateTimeout` (and other `Meteor.Error`s) method. - Rewrite CurrentChats to TS ([#22424](https://github.com/RocketChat/Rocket.Chat/pull/22424)) @@ -1248,16 +1250,15 @@ - Regression: Data in the "Active Users" section is delayed in 1 day ([#22794](https://github.com/RocketChat/Rocket.Chat/pull/22794)) - - Fix 1 day delay in the Engagement Dashboard's "Active Users" section; - - - Downgrade `@nivo/line` version. - **Expected behavior:** + - Fix 1 day delay in the Engagement Dashboard's "Active Users" section; + - Downgrade `@nivo/line` version. + **Expected behavior:** ![active-users-engagement-dashboard](https://user-images.githubusercontent.com/36537004/127372185-390dc42f-bc90-4841-a22b-731f0aafcafe.PNG) - Regression: Data in the "New Users" section is delayed in 1 day ([#22751](https://github.com/RocketChat/Rocket.Chat/pull/22751)) - - Update nivo version (which was causing errors in the bar chart); - - Fix 1 day delay in '7 days' and '30 days' periods; + - Update nivo version (which was causing errors in the bar chart); + - Fix 1 day delay in '7 days' and '30 days' periods; - Update tooltip theme. - Regression: Federation warnings on ci ([#22765](https://github.com/RocketChat/Rocket.Chat/pull/22765) by [@g-thome](https://github.com/g-thome)) @@ -1282,9 +1283,9 @@ - Regression: Fix tooltip style in the "Busiest Chat Times" chart ([#22813](https://github.com/RocketChat/Rocket.Chat/pull/22813)) - - Fix tooltip in the Engagement Dashboard's "Busiest Chat Times" chart (Hours). - - **Expected behavior:** + - Fix tooltip in the Engagement Dashboard's "Busiest Chat Times" chart (Hours). + + **Expected behavior:** ![busiest-times-ed](https://user-images.githubusercontent.com/36537004/127527827-465397ed-f089-4fb7-9ab2-6fa8cea6abdf.PNG) - Regression: Fix users not being able to see the scope of the canned m… ([#22760](https://github.com/RocketChat/Rocket.Chat/pull/22760)) @@ -1301,10 +1302,10 @@ - Regression: Prevent custom status from being visible in sequential messages ([#22733](https://github.com/RocketChat/Rocket.Chat/pull/22733)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/126641946-866dae96-1983-43a5-b689-b24670473ad0.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/126641946-866dae96-1983-43a5-b689-b24670473ad0.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/126641752-3163eb95-1cd4-4d99-a61a-4d06d9e7e13e.png) - Regression: Properly force newline in attachment fields ([#22727](https://github.com/RocketChat/Rocket.Chat/pull/22727)) @@ -1485,32 +1486,30 @@ - Add `teams.convertToChannel` endpoint ([#22188](https://github.com/RocketChat/Rocket.Chat/pull/22188)) - - Add new `teams.converToChannel` endpoint; - - - Update `ConvertToTeam` modal text (since this action can now be reversed); - + - Add new `teams.converToChannel` endpoint; + - Update `ConvertToTeam` modal text (since this action can now be reversed); - Remove corresponding team memberships when a team is deleted or converted to a channel; - Add setting to configure default role for user on manual registration ([#20650](https://github.com/RocketChat/Rocket.Chat/pull/20650) by [@lucassartor](https://github.com/lucassartor)) - Add an `admin` setting to determine the initial `role` for new users who registered manually (through the register form and via API, not using an authentication service), normally all new users are assigned to the `user` role. - - The setting can be found in `Admin`->`Accounts`->`Registration`. - - ![image](https://user-images.githubusercontent.com/49413772/107252603-47b70900-6a14-11eb-9cc6-df76720b7365.png) - The setting initial value is false, so the default behaviour stays the same while creating a new server or upgrading one. - - https://user-images.githubusercontent.com/49413772/107253220-ddeb2f00-6a14-11eb-85b4-f770dbbe4970.mp4 - + Add an `admin` setting to determine the initial `role` for new users who registered manually (through the register form and via API, not using an authentication service), normally all new users are assigned to the `user` role. + + The setting can be found in `Admin`->`Accounts`->`Registration`. + + ![image](https://user-images.githubusercontent.com/49413772/107252603-47b70900-6a14-11eb-9cc6-df76720b7365.png) + The setting initial value is false, so the default behaviour stays the same while creating a new server or upgrading one. + + https://user-images.githubusercontent.com/49413772/107253220-ddeb2f00-6a14-11eb-85b4-f770dbbe4970.mp4 + Video showing an example of the setting being used and creating an new user with the default roles via API. - Content-Security-Policy for inline scripts ([#20724](https://github.com/RocketChat/Rocket.Chat/pull/20724)) - Security policies were applied for inline scripts cases. Due to the libraries and components we use it is not possible to disable inline styles and images as they would break Oembeds and other libraries. - - - basically the inline scripts were moved to a js file - + Security policies were applied for inline scripts cases. Due to the libraries and components we use it is not possible to disable inline styles and images as they would break Oembeds and other libraries. + + + basically the inline scripts were moved to a js file + and besides that some suggars syntax like `addScript` and `addStyle` were added, this way the application already takes care of inserting the elements and providing the content automatically. - Open modals in side effects outside React ([#22247](https://github.com/RocketChat/Rocket.Chat/pull/22247)) @@ -1526,17 +1525,15 @@ - Add BBB and Jitsi to Team ([#22312](https://github.com/RocketChat/Rocket.Chat/pull/22312)) - Added 2 new settings: - - - `Admin > Video Conference > Big Blue Button > Enable for teams` - + Added 2 new settings: + - `Admin > Video Conference > Big Blue Button > Enable for teams` - `Admin > Video Conference > Jitsi > Enable in teams` - Add debouncing to units selects filters ([#22097](https://github.com/RocketChat/Rocket.Chat/pull/22097)) - Add modal to close chats when tags/comments are not required ([#22245](https://github.com/RocketChat/Rocket.Chat/pull/22245) by [@rafaelblink](https://github.com/rafaelblink)) - When neither tags or comments are required to close a livechat, show this modal instead: + When neither tags or comments are required to close a livechat, show this modal instead: ![Screen Shot 2021-05-20 at 7 33 19 PM](https://user-images.githubusercontent.com/20868078/119057741-6af23c80-b9a3-11eb-902f-f8a7458ad11c.png) - Fallback messages on contextual bar ([#22376](https://github.com/RocketChat/Rocket.Chat/pull/22376)) @@ -1559,10 +1556,10 @@ - Remove differentiation between public x private channels in sidebar ([#22160](https://github.com/RocketChat/Rocket.Chat/pull/22160)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/119752184-e7d55880-be72-11eb-9167-be2f305ddb3f.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/119752184-e7d55880-be72-11eb-9167-be2f305ddb3f.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/119752125-c8d6c680-be72-11eb-8444-2e0c7cb1c600.png) - Rewrite create direct modal ([#22209](https://github.com/RocketChat/Rocket.Chat/pull/22209)) @@ -1571,8 +1568,8 @@ - Rewrite Create Discussion Modal (only through sidebar) ([#22224](https://github.com/RocketChat/Rocket.Chat/pull/22224)) - This is only available by creating a new discussion when clicking on the sidebar button. Other places will be implemented afterwards. - + This is only available by creating a new discussion when clicking on the sidebar button. Other places will be implemented afterwards. + ![image](https://user-images.githubusercontent.com/40830821/120556093-6af63180-c3d2-11eb-97ea-63c5423049dc.png) - Send only relevant data via WebSocket ([#22258](https://github.com/RocketChat/Rocket.Chat/pull/22258)) @@ -1586,12 +1583,12 @@ - **EE:** Canned responses can't be deleted ([#22095](https://github.com/RocketChat/Rocket.Chat/pull/22095) by [@rafaelblink](https://github.com/rafaelblink)) - Deletion button has been removed from the edition option. - - ## Before - ![image](https://user-images.githubusercontent.com/2493803/119059416-9f1b2c80-b9a6-11eb-933a-4efa1ac0552a.png) - - ### After + Deletion button has been removed from the edition option. + + ## Before + ![image](https://user-images.githubusercontent.com/2493803/119059416-9f1b2c80-b9a6-11eb-933a-4efa1ac0552a.png) + + ### After ![Rocket Chat (2)](https://user-images.githubusercontent.com/2493803/119172517-72b1ef80-ba3c-11eb-9178-04a12176f312.gif) - **ENTERPRISE:** Omnichannel enterprise permissions being added back to its default roles ([#22322](https://github.com/RocketChat/Rocket.Chat/pull/22322)) @@ -1600,19 +1597,19 @@ - **ENTERPRISE:** Prevent Visitor Abandonment after forwarding chat ([#22243](https://github.com/RocketChat/Rocket.Chat/pull/22243)) - Currently the Visitor Abandonment timer isn't affected when the chat is forwarded. However this is affecting the UX in certain situations like eg: A bot forwarding a chat to an human agent - ![image](https://user-images.githubusercontent.com/34130764/120896383-e4925780-c63e-11eb-937e-ffd7c4836159.png) - + Currently the Visitor Abandonment timer isn't affected when the chat is forwarded. However this is affecting the UX in certain situations like eg: A bot forwarding a chat to an human agent + ![image](https://user-images.githubusercontent.com/34130764/120896383-e4925780-c63e-11eb-937e-ffd7c4836159.png) + To solve this issue, we'll now be stoping the Visitor Abandonment timer once a chat is forwarded. - **IMPROVE:** Prevent creation of duplicated roles and new `roles.update` endpoint ([#22279](https://github.com/RocketChat/Rocket.Chat/pull/22279) by [@lucassartor](https://github.com/lucassartor)) - Currently, the action of updating a role is broken: because roles have their `_id` = `name`, when updating a role there's no way to validate if the user is trying to update or create a new role with a name that already exists - which causes wrong behaviors, such as roles with the same name and not being able to update them. - - To proper fix this, this PR looks to change the creation of roles. Now, roles have a unique `_id` value and there's a endpoint to update roles: `/api/v1/roles.update`. - - Doing so, it's possible to validate on both endpoints (`roles.create` and `roles.update`) to not allow roles with duplicated names. - + Currently, the action of updating a role is broken: because roles have their `_id` = `name`, when updating a role there's no way to validate if the user is trying to update or create a new role with a name that already exists - which causes wrong behaviors, such as roles with the same name and not being able to update them. + + To proper fix this, this PR looks to change the creation of roles. Now, roles have a unique `_id` value and there's a endpoint to update roles: `/api/v1/roles.update`. + + Doing so, it's possible to validate on both endpoints (`roles.create` and `roles.update`) to not allow roles with duplicated names. + **OBS:** The unique id changes only reflect new roles, the standard roles (such as admin and user) still have `_id` = `name`, but new roles now **can't** have the same name as them. - `channels.history`, `groups.history` and `im.history` REST endpoints not respecting hide system message config ([#22364](https://github.com/RocketChat/Rocket.Chat/pull/22364)) @@ -1629,10 +1626,10 @@ - Can't delete file from Room's file list ([#22191](https://github.com/RocketChat/Rocket.Chat/pull/22191)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/120215931-bb239700-c20c-11eb-9494-d4bc017df390.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/120215931-bb239700-c20c-11eb-9494-d4bc017df390.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/120216113-f8882480-c20c-11eb-9afb-b127e66a43da.png) - Cancel button and success toast at Leave Team modal ([#22373](https://github.com/RocketChat/Rocket.Chat/pull/22373)) @@ -1643,10 +1640,10 @@ - Convert and Move team permission ([#22350](https://github.com/RocketChat/Rocket.Chat/pull/22350)) - ### before - https://user-images.githubusercontent.com/45966964/114909360-5c04f100-9e1d-11eb-9363-f308e5d0be68.mp4 - - ### after + ### before + https://user-images.githubusercontent.com/45966964/114909360-5c04f100-9e1d-11eb-9363-f308e5d0be68.mp4 + + ### after https://user-images.githubusercontent.com/45966964/114909388-61fad200-9e1d-11eb-9bbe-114b55954a9f.mp4 - CORS error while interacting with any action button on Livechat ([#22150](https://github.com/RocketChat/Rocket.Chat/pull/22150)) @@ -1665,50 +1662,50 @@ - Members tab visual issues ([#22138](https://github.com/RocketChat/Rocket.Chat/pull/22138)) - ## Before - ![image](https://user-images.githubusercontent.com/27704687/119558283-95fbd800-bd77-11eb-91b4-91821f365bf3.png) - - ## After + ## Before + ![image](https://user-images.githubusercontent.com/27704687/119558283-95fbd800-bd77-11eb-91b4-91821f365bf3.png) + + ## After ![image](https://user-images.githubusercontent.com/27704687/119558120-6947c080-bd77-11eb-8ecb-7fedc07afa82.png) - Memory leak generated by Stream Cast usage ([#22329](https://github.com/RocketChat/Rocket.Chat/pull/22329)) - Stream Cast uses a different approach to broadcast data to the instances, it uses the DDP subscription method that requires a collection on the other side, if no collection exists with the given name `broadcast-stream` it caches in memory waiting for the collection to be set later. The cache is cleared only when a reconnection happens. - + Stream Cast uses a different approach to broadcast data to the instances, it uses the DDP subscription method that requires a collection on the other side, if no collection exists with the given name `broadcast-stream` it caches in memory waiting for the collection to be set later. The cache is cleared only when a reconnection happens. + This PR overrides the function that processes the data for that specific connection, preventing the cache and everything else to be processed since we already have our low-level listener to process the data. - Message box hiding on mobile view (Safari) ([#22212](https://github.com/RocketChat/Rocket.Chat/pull/22212)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/120404256-5b1c1600-c31c-11eb-96e9-860e4132db5f.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/120404256-5b1c1600-c31c-11eb-96e9-860e4132db5f.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/120404406-acc4a080-c31c-11eb-9efb-c2ad88664fda.png) - Missing burger menu on direct messages ([#22211](https://github.com/RocketChat/Rocket.Chat/pull/22211)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/120403671-09bf5700-c31b-11eb-92a1-a2f589bd85fc.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/120403671-09bf5700-c31b-11eb-92a1-a2f589bd85fc.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/120403693-1643af80-c31b-11eb-8027-dbdc4f560647.png) - Missing Throbber while thread list is loading ([#22316](https://github.com/RocketChat/Rocket.Chat/pull/22316)) - ### before - List was starting with no results even if there's results: - - ![image](https://user-images.githubusercontent.com/27704687/121606744-1e8ba100-ca25-11eb-9b31-706fb998d05f.png) - - ### after + ### before + List was starting with no results even if there's results: + + ![image](https://user-images.githubusercontent.com/27704687/121606744-1e8ba100-ca25-11eb-9b31-706fb998d05f.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/121606635-e97f4e80-ca24-11eb-81f7-af8b0cc41c89.png) - Not possible to edit some messages inside threads ([#22325](https://github.com/RocketChat/Rocket.Chat/pull/22325)) - ### Before - ![before](https://user-images.githubusercontent.com/27704687/121755733-4eeb4200-caee-11eb-9d77-1b498c38c478.gif) - - ### After + ### Before + ![before](https://user-images.githubusercontent.com/27704687/121755733-4eeb4200-caee-11eb-9d77-1b498c38c478.gif) + + ### After ![after](https://user-images.githubusercontent.com/27704687/121755736-514d9c00-caee-11eb-9897-78fcead172f2.gif) - Notifications not using user's name ([#22309](https://github.com/RocketChat/Rocket.Chat/pull/22309)) @@ -1735,10 +1732,10 @@ - Sidebar not closing when clicking on a channel ([#22271](https://github.com/RocketChat/Rocket.Chat/pull/22271)) - ### before - ![before](https://user-images.githubusercontent.com/27704687/121074843-c6e20100-c7aa-11eb-88db-76e39b57b064.gif) - - ### after + ### before + ![before](https://user-images.githubusercontent.com/27704687/121074843-c6e20100-c7aa-11eb-88db-76e39b57b064.gif) + + ### after ![after](https://user-images.githubusercontent.com/27704687/121074860-cb0e1e80-c7aa-11eb-9e96-06d75044b763.gif) - Sound notification is not emitted when the Omnichannel chat comes from another department ([#22291](https://github.com/RocketChat/Rocket.Chat/pull/22291)) @@ -1749,9 +1746,9 @@ - Undefined error when forwarding chats to offline department ([#22154](https://github.com/RocketChat/Rocket.Chat/pull/22154) by [@rafaelblink](https://github.com/rafaelblink)) - ![Screen Shot 2021-05-26 at 5 29 17 PM](https://user-images.githubusercontent.com/59577424/119727520-c495b380-be48-11eb-88a2-158017c7ad0a.png) - - Omnichannel agents are facing the error shown above when forwarding chats to offline departments. + ![Screen Shot 2021-05-26 at 5 29 17 PM](https://user-images.githubusercontent.com/59577424/119727520-c495b380-be48-11eb-88a2-158017c7ad0a.png) + + Omnichannel agents are facing the error shown above when forwarding chats to offline departments. The error usually takes place when the routing system algorithm is **Manual Selection**. - Unread bar in channel flash quickly and then disappear ([#22275](https://github.com/RocketChat/Rocket.Chat/pull/22275)) @@ -1782,15 +1779,15 @@ - Chore: Change modals for remove user from team && leave team ([#22141](https://github.com/RocketChat/Rocket.Chat/pull/22141)) - ![image](https://user-images.githubusercontent.com/40830821/119576154-93f14380-bd8e-11eb-8885-f889f2939bf4.png) + ![image](https://user-images.githubusercontent.com/40830821/119576154-93f14380-bd8e-11eb-8885-f889f2939bf4.png) ![image](https://user-images.githubusercontent.com/40830821/119576219-b5eac600-bd8e-11eb-832c-ea7a17a56bdd.png) - Chore: Check PR Title on every submission ([#22140](https://github.com/RocketChat/Rocket.Chat/pull/22140)) - Chore: Enable push gateway only if the server is registered ([#22346](https://github.com/RocketChat/Rocket.Chat/pull/22346) by [@lucassartor](https://github.com/lucassartor)) - Currently, when creating an unregistered server, the default value of the push gateway setting is set to true and is disabled (it can't be changed unless the server is registered). This is a wrong behavior as an unregistered server **can't** use the push gateway. - + Currently, when creating an unregistered server, the default value of the push gateway setting is set to true and is disabled (it can't be changed unless the server is registered). This is a wrong behavior as an unregistered server **can't** use the push gateway. + This PR creates a validation to check if the server is registered when enabling the push gateway. That way, even if the push gateway setting is turned on, but the server is unregistered, the push gateway **won't** work - it will behave like it is off. - Chore: Enforce TypeScript on Storybook ([#22317](https://github.com/RocketChat/Rocket.Chat/pull/22317)) @@ -1807,7 +1804,7 @@ - Chore: Update delete team modal to new design ([#22127](https://github.com/RocketChat/Rocket.Chat/pull/22127)) - Now the modal has only 2 steps (steps 1 and 2 were merged) + Now the modal has only 2 steps (steps 1 and 2 were merged) ![image](https://user-images.githubusercontent.com/40830821/119414580-2e398480-bcc6-11eb-9a47-515568257974.png) - Language update from LingoHub 🤖 on 2021-05-31Z ([#22196](https://github.com/RocketChat/Rocket.Chat/pull/22196)) @@ -1834,10 +1831,10 @@ - Regression: Missing flexDirection on select field ([#22300](https://github.com/RocketChat/Rocket.Chat/pull/22300)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/121425905-532a2a80-c949-11eb-885f-e8ddaf5c8d5c.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/121425905-532a2a80-c949-11eb-885f-e8ddaf5c8d5c.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/121425770-283fd680-c949-11eb-8d94-86886f174599.png) - Regression: RoomProvider using wrong types ([#22370](https://github.com/RocketChat/Rocket.Chat/pull/22370)) @@ -1978,50 +1975,50 @@ - **ENTERPRISE:** Introduce Load Rotation routing algorithm for Omnichannel ([#22090](https://github.com/RocketChat/Rocket.Chat/pull/22090) by [@rafaelblink](https://github.com/rafaelblink)) - This PR introduces a new Auto Chat Distribution (ACD) algorithm for Omnichannel: **Load Rotation**. - The algorithm distributes chats to agents one by one, which means that when a new chat arrives, the agent with the oldest routing assignment time will be selected to serve the chat, regardless of the number of chats in progress each agent has. - + This PR introduces a new Auto Chat Distribution (ACD) algorithm for Omnichannel: **Load Rotation**. + The algorithm distributes chats to agents one by one, which means that when a new chat arrives, the agent with the oldest routing assignment time will be selected to serve the chat, regardless of the number of chats in progress each agent has. + ![Screen Shot 2021-05-20 at 5 17 40 PM](https://user-images.githubusercontent.com/59577424/119043752-c61a3400-b98f-11eb-8543-f3176879af1d.png) - Back button for Omnichannel ([#21647](https://github.com/RocketChat/Rocket.Chat/pull/21647) by [@rafaelblink](https://github.com/rafaelblink)) - New Message Parser ([#21962](https://github.com/RocketChat/Rocket.Chat/pull/21962)) - The objective is to put an end to the confusion that we face having multiple parsers, and the problems that this brings, it is still experimental then users need to choose to use it. - - The benefits are multiple. no more unexpected cases or grammatical collisions (in addition to more flexible nested cases like bold within link labels). - Besides, we no longer render raw html, instead we use components, so the xss attacks are over (the easy ones at least). Without further discoveries and at the fronted, we only reder what is delivered thus improving our performance. + The objective is to put an end to the confusion that we face having multiple parsers, and the problems that this brings, it is still experimental then users need to choose to use it. + + The benefits are multiple. no more unexpected cases or grammatical collisions (in addition to more flexible nested cases like bold within link labels). + Besides, we no longer render raw html, instead we use components, so the xss attacks are over (the easy ones at least). Without further discoveries and at the fronted, we only reder what is delivered thus improving our performance. This can be used in multiple places, (message, alert, sidenav and in the entire mobile application.) - Option to notify failed login attempts to a channel ([#21968](https://github.com/RocketChat/Rocket.Chat/pull/21968)) - Option to prevent users from using Invisible status ([#20084](https://github.com/RocketChat/Rocket.Chat/pull/20084) by [@lucassartor](https://github.com/lucassartor)) - Add an `admin` option to allow/disallow the `Invisible` status option from all users. This option is available in the `Accounts` section. - - ![2021-01-06-11-55-22](https://user-images.githubusercontent.com/49413772/103782988-ebc52300-5016-11eb-8a29-dd540c21e11c.gif) - - If the option is turned off, the `users.setStatus` endpoint is also restricted from users trying to change their status to `Invisible`, throwing the following error: - ```json - { - "success": false, - "error": "Invisible status is disabled [error-not-allowed]", - "stack": "Error: Invisible status is disabled [error-not-allowed]\n at DDPCommon.MethodInvocation. (app/api/server/v1/users.js:425:13)\n at packages/dispatch_run-as-user.js:211:14\n at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1234:12)\n at Object.Meteor.runAsUser (packages/dispatch_run-as-user.js:210:33)\n at Object.post (app/api/server/v1/users.js:415:10)\n at app/api/server/api.js:394:82\n at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1234:12)\n at Object._internalRouteActionHandler [as action] (app/api/server/api.js:394:39)\n at Route.share.Route.Route._callEndpoint (packages/nimble_restivus/lib/route.coffee:150:32)\n at packages/nimble_restivus/lib/route.coffee:59:33\n at packages/simple_json-routes.js:98:9", - "errorType": "error-not-allowed", - "details": { - "method": "users.setStatus" - } - } + Add an `admin` option to allow/disallow the `Invisible` status option from all users. This option is available in the `Accounts` section. + + ![2021-01-06-11-55-22](https://user-images.githubusercontent.com/49413772/103782988-ebc52300-5016-11eb-8a29-dd540c21e11c.gif) + + If the option is turned off, the `users.setStatus` endpoint is also restricted from users trying to change their status to `Invisible`, throwing the following error: + ```json + { + "success": false, + "error": "Invisible status is disabled [error-not-allowed]", + "stack": "Error: Invisible status is disabled [error-not-allowed]\n at DDPCommon.MethodInvocation. (app/api/server/v1/users.js:425:13)\n at packages/dispatch_run-as-user.js:211:14\n at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1234:12)\n at Object.Meteor.runAsUser (packages/dispatch_run-as-user.js:210:33)\n at Object.post (app/api/server/v1/users.js:415:10)\n at app/api/server/api.js:394:82\n at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1234:12)\n at Object._internalRouteActionHandler [as action] (app/api/server/api.js:394:39)\n at Route.share.Route.Route._callEndpoint (packages/nimble_restivus/lib/route.coffee:150:32)\n at packages/nimble_restivus/lib/route.coffee:59:33\n at packages/simple_json-routes.js:98:9", + "errorType": "error-not-allowed", + "details": { + "method": "users.setStatus" + } + } ``` - Paginated and Filtered selects on new/edit unit ([#22052](https://github.com/RocketChat/Rocket.Chat/pull/22052) by [@rafaelblink](https://github.com/rafaelblink)) - REQUIRES https://github.com/RocketChat/Rocket.Chat.Fuselage/pull/447 - - Adds infinite scrolling selects to the units edit/create with the ability to be filtered by text as well - - ![Screen Shot 2021-05-17 at 9 24 19 AM](https://user-images.githubusercontent.com/20868078/118487999-abc32a80-b6f1-11eb-8d58-d031111ea0fb.png) - + REQUIRES https://github.com/RocketChat/Rocket.Chat.Fuselage/pull/447 + + Adds infinite scrolling selects to the units edit/create with the ability to be filtered by text as well + + ![Screen Shot 2021-05-17 at 9 24 19 AM](https://user-images.githubusercontent.com/20868078/118487999-abc32a80-b6f1-11eb-8d58-d031111ea0fb.png) + This Affects the monitors and departments inputs - Remove exif metadata from uploaded files ([#22044](https://github.com/RocketChat/Rocket.Chat/pull/22044)) @@ -2049,17 +2046,13 @@ - Inconsistent and misleading 2FA settings ([#22042](https://github.com/RocketChat/Rocket.Chat/pull/22042) by [@lucassartor](https://github.com/lucassartor)) - Currently, there are some inconsistencies and incorrect behaviors on the 2FA settings, such as: - - - - When disabling the TOTP 2FA, all 2FA are disabled; - - - There are no option to disable only the TOTP 2FA; - - - If 2FA are disabled, the other settings aren't blocked (the e-mail 2FA setting, for example); - - - It lacks some labels to warn the user of some specific 2FA options. - + Currently, there are some inconsistencies and incorrect behaviors on the 2FA settings, such as: + + - When disabling the TOTP 2FA, all 2FA are disabled; + - There are no option to disable only the TOTP 2FA; + - If 2FA are disabled, the other settings aren't blocked (the e-mail 2FA setting, for example); + - It lacks some labels to warn the user of some specific 2FA options. + This PR looks to fix those issues. - LDAP port setting input type to allow only numbers ([#21912](https://github.com/RocketChat/Rocket.Chat/pull/21912) by [@Deepak-learner](https://github.com/Deepak-learner)) @@ -2081,16 +2074,16 @@ - **APPS:** Scheduler duplicating recurrent tasks after server restart ([#21866](https://github.com/RocketChat/Rocket.Chat/pull/21866)) - Reintroduces the old method for creating recurring tasks in the apps' scheduler bridge to ensure tasks won't be duplicated. - - By introducing the [`skipImmediate` property option](https://github.com/RocketChat/Rocket.Chat/pull/21353) at the [`scheduleRecurring`](https://github.com/RocketChat/Rocket.Chat/blob/f8171f464ed8a7487795651767695fb33a1c709e/app/apps/server/bridges/scheduler.js#L119) method, the `every` method from _agenda.js_, which ensured no duplicates were created, was removed in favor of a more manual procedure. The new procedure was not taking into account the management of duplicates and as a result multiple copies of the same task could be created and they would get executed at the same time. - + Reintroduces the old method for creating recurring tasks in the apps' scheduler bridge to ensure tasks won't be duplicated. + + By introducing the [`skipImmediate` property option](https://github.com/RocketChat/Rocket.Chat/pull/21353) at the [`scheduleRecurring`](https://github.com/RocketChat/Rocket.Chat/blob/f8171f464ed8a7487795651767695fb33a1c709e/app/apps/server/bridges/scheduler.js#L119) method, the `every` method from _agenda.js_, which ensured no duplicates were created, was removed in favor of a more manual procedure. The new procedure was not taking into account the management of duplicates and as a result multiple copies of the same task could be created and they would get executed at the same time. + In the case of server restarts, every time this event happened and the app had the `startupSetting` configured to use _recurring tasks_, they would get recreated the same number of times. In the case of a server that restarts frequently (_n_ times), there would be the same (_n_) number of tasks duplicated (and running) in the system. - **ENTERPRISE:** Omnichannel Monitors can't forward chats to departments that they are not supervising ([#22128](https://github.com/RocketChat/Rocket.Chat/pull/22128)) - Currently, Omnichannel Monitors just can't forward chats to a department that is part of a `Business Unit` they're not supervising. This issue is causing critical problems on customer operations since this behaviour is not by design. - The reason this issue is taking place is that, by design, Monitors just have access to departments related to the `Business Units` they're monitoring, but this restriction is designed only for Omnichannel management areas, which means in case the monitor is, also, an agent, they're supposed to be able to forward a chat to any available departments regardless the `Business Units` it's associated with. + Currently, Omnichannel Monitors just can't forward chats to a department that is part of a `Business Unit` they're not supervising. This issue is causing critical problems on customer operations since this behaviour is not by design. + The reason this issue is taking place is that, by design, Monitors just have access to departments related to the `Business Units` they're monitoring, but this restriction is designed only for Omnichannel management areas, which means in case the monitor is, also, an agent, they're supposed to be able to forward a chat to any available departments regardless the `Business Units` it's associated with. So, initially, the restriction was implemented on the `Department Model` and, now, we're implementing the logic properly and introducing a new parameter to department endpoints, so the client will define which type of departments it needs. - **ENTERPRISE:** Omnichannel Monitors can't forward chats to departments that they are not supervising ([#22142](https://github.com/RocketChat/Rocket.Chat/pull/22142)) @@ -2129,18 +2122,18 @@ - Correcting a the wrong Archived label in edit room ([#21717](https://github.com/RocketChat/Rocket.Chat/pull/21717) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - ![image](https://user-images.githubusercontent.com/45966964/116584997-3cd78a80-a918-11eb-81fa-8a7eb5318ae9.png) - + ![image](https://user-images.githubusercontent.com/45966964/116584997-3cd78a80-a918-11eb-81fa-8a7eb5318ae9.png) + A label exists for Archived, and it has not been used. So I replaced it with the existing one. the label 'Archived' does not exist. - Custom OAuth not being completely deleted ([#21637](https://github.com/RocketChat/Rocket.Chat/pull/21637) by [@siva2204](https://github.com/siva2204)) - Directory Table's Sort Function ([#21921](https://github.com/RocketChat/Rocket.Chat/pull/21921)) - ### TableRow Margin Issue: - ![image](https://user-images.githubusercontent.com/27704687/116907348-d6a07f80-ac17-11eb-9411-edfe0906bfe1.png) - - ### Table Sort Action Issue: + ### TableRow Margin Issue: + ![image](https://user-images.githubusercontent.com/27704687/116907348-d6a07f80-ac17-11eb-9411-edfe0906bfe1.png) + + ### Table Sort Action Issue: ![directory](https://user-images.githubusercontent.com/27704687/116907441-f20b8a80-ac17-11eb-8790-bfce19e89a67.gif) - Discussion names showing a random value ([#22172](https://github.com/RocketChat/Rocket.Chat/pull/22172)) @@ -2151,54 +2144,54 @@ - Emails being sent with HTML entities getting escaped multiple times ([#21994](https://github.com/RocketChat/Rocket.Chat/pull/21994) by [@bhavayAnand9](https://github.com/bhavayAnand9)) - fixes an issue where if password contains special HTML character like &, in the email it would end up something like `&amp;` - - - password was going through multiple escapeHTML function calls - `secure&123 => secure&123 => secure&amp;123 + fixes an issue where if password contains special HTML character like &, in the email it would end up something like `&amp;` + + + password was going through multiple escapeHTML function calls + `secure&123 => secure&123 => secure&amp;123 ` - Error when you look at the members list of a room in which you are not a member ([#21952](https://github.com/RocketChat/Rocket.Chat/pull/21952) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - Before, when you look at the members of a room in which you are not a member the app crashed, i corrected this problem. - Indeed, there was a check on each currentSubscription. to see if it was not undefined except on currentSubscription.blocker - + Before, when you look at the members of a room in which you are not a member the app crashed, i corrected this problem. + Indeed, there was a check on each currentSubscription. to see if it was not undefined except on currentSubscription.blocker + https://user-images.githubusercontent.com/45966964/117087470-d3101400-ad4f-11eb-8f44-0ebca830a4d8.mp4 - errors when viewing a room that you're not subscribed to ([#21984](https://github.com/RocketChat/Rocket.Chat/pull/21984)) - Files list will not show deleted files. ([#21732](https://github.com/RocketChat/Rocket.Chat/pull/21732) by [@Darshilp326](https://github.com/Darshilp326)) - When you delete files from the header option, deleted files will not be shown. - + When you delete files from the header option, deleted files will not be shown. + https://user-images.githubusercontent.com/55157259/115730786-38552400-a3a4-11eb-9684-7f510920db66.mp4 - Fixed the fact that when a team was deleted, not all channels were unlinked from the team ([#21942](https://github.com/RocketChat/Rocket.Chat/pull/21942) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - Fixed the fact that when a team was deleted, not all channels were unlinked from the team. Only the first room of the rooms list was unlinked. - - After the fix, there is nos more errors: - - + Fixed the fact that when a team was deleted, not all channels were unlinked from the team. Only the first room of the rooms list was unlinked. + + After the fix, there is nos more errors: + + https://user-images.githubusercontent.com/45966964/117055182-2a47c180-ad1b-11eb-806f-07fb3fa7ec12.mp4 - Fixing Jitsi call ended Issue. ([#21808](https://github.com/RocketChat/Rocket.Chat/pull/21808)) - The new rewrite in react of contextual call component broke the Jitsi "click to join" messages. The issue being after 10 seconds of initiating the call, the message "click to join" always returned "Call Ended" even if the call was still going on. - This was due to the fact that after closing the contextual bar, the react component gets unmounted and we are not able to keep track of ongoing call and increase jitsi room timeout. - - This PR solves this issue by using the setInterval methods on component will unmount. When the call component unmounts, we keep on checking the state of jitsi call and based on conditions increase the jitsi room timeout. After the call is ended all setInterval calls are closed. - + The new rewrite in react of contextual call component broke the Jitsi "click to join" messages. The issue being after 10 seconds of initiating the call, the message "click to join" always returned "Call Ended" even if the call was still going on. + This was due to the fact that after closing the contextual bar, the react component gets unmounted and we are not able to keep track of ongoing call and increase jitsi room timeout. + + This PR solves this issue by using the setInterval methods on component will unmount. When the call component unmounts, we keep on checking the state of jitsi call and based on conditions increase the jitsi room timeout. After the call is ended all setInterval calls are closed. + This PR also removes the implementation of HEARTBEAT events of JitsiBridge. This is because this is no longer needed and all logic is being taken care of by the unmount function. - Handle NPS errors instead of throwing them ([#21945](https://github.com/RocketChat/Rocket.Chat/pull/21945)) - Header Tag Visual Issues ([#21991](https://github.com/RocketChat/Rocket.Chat/pull/21991)) - ### Normal - ![image](https://user-images.githubusercontent.com/27704687/117504793-69635600-af59-11eb-8b79-9d8f631490ee.png) - - ### Hover + ### Normal + ![image](https://user-images.githubusercontent.com/27704687/117504793-69635600-af59-11eb-8b79-9d8f631490ee.png) + + ### Hover ![image](https://user-images.githubusercontent.com/27704687/117504934-97489a80-af59-11eb-87c3-0a62731e9ce3.png) - Horizontal scrollbar not showing on tables ([#21852](https://github.com/RocketChat/Rocket.Chat/pull/21852)) @@ -2207,17 +2200,17 @@ - iFrame size on embedded videos ([#21992](https://github.com/RocketChat/Rocket.Chat/pull/21992)) - ### Before - ![image](https://user-images.githubusercontent.com/27704687/117508802-8bf86d80-af5f-11eb-9eb8-29e55b73eac5.png) - - ### After + ### Before + ![image](https://user-images.githubusercontent.com/27704687/117508802-8bf86d80-af5f-11eb-9eb8-29e55b73eac5.png) + + ### After ![image](https://user-images.githubusercontent.com/27704687/117508870-a4688800-af5f-11eb-9176-7f24de5fc424.png) - Incorrect error message when opening channel in anonymous read ([#22066](https://github.com/RocketChat/Rocket.Chat/pull/22066) by [@lucassartor](https://github.com/lucassartor)) - Every time you open a public channel with threads in it when using anonymous read an `Incorrect User` error will be thrown. - This is an incorrect behaviour as everything that is public should be valid for an anonymous user. - + Every time you open a public channel with threads in it when using anonymous read an `Incorrect User` error will be thrown. + This is an incorrect behaviour as everything that is public should be valid for an anonymous user. + Some files are adapted to that and have already removed this kind of incorrect error, but there are some that need some fix, this PR aims to do that. - Incorrect Team's Info spacing ([#22021](https://github.com/RocketChat/Rocket.Chat/pull/22021)) @@ -2230,21 +2223,19 @@ - Make the FR translation consistent with the 'room' translation + typos ([#21913](https://github.com/RocketChat/Rocket.Chat/pull/21913) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - In the FR translation files, there were two terms that were used to refer to **'room'**: - - - 'salon' (149 times used) - - ![image](https://user-images.githubusercontent.com/45966964/116829860-ac62a980-aba6-11eb-8212-e6f15ed0af82.png) - - - - 'salle' (46 times used) - - ![image](https://user-images.githubusercontent.com/45966964/116829871-be444c80-aba6-11eb-9b42-e213fee6586a.png) - - The problem is that both were used in the same context and sometimes even in the same option list. - However, since 'salon' is a better translation and was also in the majority, I used the translation 'salon' wherever 'salle' was marked. - - For example: + In the FR translation files, there were two terms that were used to refer to **'room'**: + - 'salon' (149 times used) + + ![image](https://user-images.githubusercontent.com/45966964/116829860-ac62a980-aba6-11eb-8212-e6f15ed0af82.png) + + - 'salle' (46 times used) + + ![image](https://user-images.githubusercontent.com/45966964/116829871-be444c80-aba6-11eb-9b42-e213fee6586a.png) + + The problem is that both were used in the same context and sometimes even in the same option list. + However, since 'salon' is a better translation and was also in the majority, I used the translation 'salon' wherever 'salle' was marked. + + For example: ![image](https://user-images.githubusercontent.com/45966964/116830523-1da45b80-abab-11eb-81f8-5225d51cecc6.png) - Maximum 25 channels can be loaded in the teams' channels list ([#21708](https://github.com/RocketChat/Rocket.Chat/pull/21708) by [@Jeanstaquet](https://github.com/Jeanstaquet)) @@ -2259,8 +2250,8 @@ - No warning message is sent when user is removed from a team's main channel ([#21949](https://github.com/RocketChat/Rocket.Chat/pull/21949)) - - Send a warning message to a team's main channel when a user is removed from the team; - - Trigger events while removing a user from a team's main channel; + - Send a warning message to a team's main channel when a user is removed from the team; + - Trigger events while removing a user from a team's main channel; - Fix `usersCount` field in the team's main room when a user is removed from the team (`usersCount` is now decreased by 1). - Not possible accept video call if "Hide right sidebar with click" is enabled ([#22175](https://github.com/RocketChat/Rocket.Chat/pull/22175)) @@ -2281,14 +2272,14 @@ - Prevent the userInfo tab to return 'User not found' each time if a certain member of a DM group has been deleted ([#21970](https://github.com/RocketChat/Rocket.Chat/pull/21970) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - Prevent the userInfo tab to return 'User not found' if a member of a DM group has been deleted. - This happens if the user that has been deleted is the one originally displayed on the userInfo tab in a DM group with >2 users. - + Prevent the userInfo tab to return 'User not found' if a member of a DM group has been deleted. + This happens if the user that has been deleted is the one originally displayed on the userInfo tab in a DM group with >2 users. + https://user-images.githubusercontent.com/45966964/117221081-db785580-ae08-11eb-9b33-2314a99eb037.mp4 - Prune messages not cleaning up unread threads ([#21326](https://github.com/RocketChat/Rocket.Chat/pull/21326) by [@renancleyson-dev](https://github.com/renancleyson-dev)) - Fixes permanent unread messages when admin prune at least two different thread messages in the room that were unread by some user. + Fixes permanent unread messages when admin prune at least two different thread messages in the room that were unread by some user. ![screencapture-localhost-3000-channel-general-thread-2021-03-26-13_17_16](https://user-images.githubusercontent.com/43624243/112678973-62b9cd00-8e4a-11eb-9af9-56f17cc66baf.png) - Redirect on remove user from channel by user profile tab ([#21951](https://github.com/RocketChat/Rocket.Chat/pull/21951)) @@ -2299,8 +2290,8 @@ - Removed fields from User Info for which the user doesn't have permissions. ([#20923](https://github.com/RocketChat/Rocket.Chat/pull/20923) by [@Darshilp326](https://github.com/Darshilp326)) - Removed LastLogin, CreatedAt and Roles for users who don't have permission. - + Removed LastLogin, CreatedAt and Roles for users who don't have permission. + https://user-images.githubusercontent.com/55157259/109381351-f2c62e80-78ff-11eb-9289-e11072bf62f8.mp4 - Replace `query` param by `name`, `username` and `status` on the `teams.members` endpoint ([#21539](https://github.com/RocketChat/Rocket.Chat/pull/21539)) @@ -2313,39 +2304,39 @@ - Unable to edit a 'direct' room setting in the admin due to the room name ([#21636](https://github.com/RocketChat/Rocket.Chat/pull/21636) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - When you are in the admin and want to change a room 'd' setting, it doesn't work because it takes into account the name that is set automatically and therefore tries to save that name. Since the name is not valid and should not be registered, we cannot change the setting for the 'd' room. - I made sure that when you want to change a setting in a 'd' room, that you don't take the name into account - - - https://user-images.githubusercontent.com/45966964/115150919-cd85af00-a06a-11eb-9667-ef3dcfc5adb6.mp4 - - + When you are in the admin and want to change a room 'd' setting, it doesn't work because it takes into account the name that is set automatically and therefore tries to save that name. Since the name is not valid and should not be registered, we cannot change the setting for the 'd' room. + I made sure that when you want to change a setting in a 'd' room, that you don't take the name into account + + + https://user-images.githubusercontent.com/45966964/115150919-cd85af00-a06a-11eb-9667-ef3dcfc5adb6.mp4 + + Behind the scene, the name is not saved - Unable to edit a user who does not have an email via the admin or via the user's profile ([#21626](https://github.com/RocketChat/Rocket.Chat/pull/21626) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - If a user does not have an email address, they cannot change it via their profile or via the admin. I fixed this issue. I have created several profiles and there was one that didn't have an email, I don't know how I did it, I am working on it. I had not modified the db to delete his email, hence the fix - - in admin - - https://user-images.githubusercontent.com/45966964/115112617-9b9b1c80-9f86-11eb-8e3a-950c3c1a1746.mp4 - - - - in the user profile - + If a user does not have an email address, they cannot change it via their profile or via the admin. I fixed this issue. I have created several profiles and there was one that didn't have an email, I don't know how I did it, I am working on it. I had not modified the db to delete his email, hence the fix + + in admin + + https://user-images.githubusercontent.com/45966964/115112617-9b9b1c80-9f86-11eb-8e3a-950c3c1a1746.mp4 + + + + in the user profile + https://user-images.githubusercontent.com/45966964/115112620-a0f86700-9f86-11eb-97b1-56eaba42216b.mp4 - Unable to get channels, sort by most recent message ([#21701](https://github.com/RocketChat/Rocket.Chat/pull/21701) by [@sumukhah](https://github.com/sumukhah)) - Unable to update app manually ([#21215](https://github.com/RocketChat/Rocket.Chat/pull/21215)) - It allows for update of apps using a zip file. - - When installing apps using the zip file, either by url or the file form, if the app was already installed, an error would be thrown stating the condition and forbidding the installation. Now, when sending a zip file of an app that is already installed, the user is presented with the following modal: - - ![2021-04-30-113936_627x235_scrot](https://user-images.githubusercontent.com/733282/116711383-2cbbbb80-a9a9-11eb-8c77-22d6802cb9f5.png) - + It allows for update of apps using a zip file. + + When installing apps using the zip file, either by url or the file form, if the app was already installed, an error would be thrown stating the condition and forbidding the installation. Now, when sending a zip file of an app that is already installed, the user is presented with the following modal: + + ![2021-04-30-113936_627x235_scrot](https://user-images.githubusercontent.com/733282/116711383-2cbbbb80-a9a9-11eb-8c77-22d6802cb9f5.png) + If the app also requires permissions to be reviewed, the modal that handles permission reviews will be shown after this one is accepted. - Unpin message reactivity ([#22029](https://github.com/RocketChat/Rocket.Chat/pull/22029)) @@ -2356,20 +2347,20 @@ - User Impersonation through sendMessage API ([#20391](https://github.com/RocketChat/Rocket.Chat/pull/20391) by [@lucassartor](https://github.com/lucassartor)) - Create a new permission: `message-impersonate`. For new installs only bot role will have the permission and for updating installs the permission will also be given to user role, so it won't break running deployments. - - If a message is being sent with `avatar` or `alias` properties, it validates if the sender has the `message-impersonate` permission, if not, an error is throwed: - ```json - { - "success": false, - "error": "Not enough permission", - "stack": "Error: Not enough permission\n ..." - } + Create a new permission: `message-impersonate`. For new installs only bot role will have the permission and for updating installs the permission will also be given to user role, so it won't break running deployments. + + If a message is being sent with `avatar` or `alias` properties, it validates if the sender has the `message-impersonate` permission, if not, an error is throwed: + ```json + { + "success": false, + "error": "Not enough permission", + "stack": "Error: Not enough permission\n ..." + } ``` - Visibility of burger menu on certain width ([#20736](https://github.com/RocketChat/Rocket.Chat/pull/20736) by [@yash-rajpal](https://github.com/yash-rajpal)) - Burger was not visible on a certain width, specifically between 600 to 780. if width is more than 780px sidebar is shown, if less than 600 then burger icon was shown. But it wasn't shown between 600px to 780 px. + Burger was not visible on a certain width, specifically between 600 to 780. if width is more than 780px sidebar is shown, if less than 600 then burger icon was shown. But it wasn't shown between 600px to 780 px. It was because for showing burger icon we were only checking for `isMobile` which is lenght only less than 600. So i added one more check for condition if length is less than 780 px. - When closing chats a comment is always required ([#21947](https://github.com/RocketChat/Rocket.Chat/pull/21947)) @@ -2384,8 +2375,8 @@ - Wrong icon on "Move to team" option in the channel info actions ([#21944](https://github.com/RocketChat/Rocket.Chat/pull/21944)) - ![image](https://user-images.githubusercontent.com/40830821/117061659-d9bf6c80-acf8-11eb-8e29-be47e702dedd.png) - + ![image](https://user-images.githubusercontent.com/40830821/117061659-d9bf6c80-acf8-11eb-8e29-be47e702dedd.png) + Depends on https://github.com/RocketChat/Rocket.Chat.Fuselage/pull/444
@@ -2402,10 +2393,8 @@ - Add two more test cases to the slash-command test suite ([#21317](https://github.com/RocketChat/Rocket.Chat/pull/21317) by [@EduardoPicolo](https://github.com/EduardoPicolo)) - Added two more test cases to the slash-command test suite: - - - 'should return an error when the command does not exist''; - + Added two more test cases to the slash-command test suite: + - 'should return an error when the command does not exist''; - 'should return an error when no command is provided'; - Bump actions/stale from v3.0.8 to v3.0.18 ([#21877](https://github.com/RocketChat/Rocket.Chat/pull/21877) by [@dependabot[bot]](https://github.com/dependabot[bot])) @@ -2440,9 +2429,9 @@ - i18n: Add missing translation string in account preference ([#21448](https://github.com/RocketChat/Rocket.Chat/pull/21448) by [@sumukhah](https://github.com/sumukhah)) - "Test Desktop Notifications" was missing in translation, Added to the file. - Screenshot 2021-04-05 at 3 58 01 PM - + "Test Desktop Notifications" was missing in translation, Added to the file. + Screenshot 2021-04-05 at 3 58 01 PM + Screenshot 2021-04-05 at 3 58 32 PM - i18n: Correct a typo in German ([#21711](https://github.com/RocketChat/Rocket.Chat/pull/21711) by [@Jeanstaquet](https://github.com/Jeanstaquet)) @@ -2469,10 +2458,10 @@ - Regression: discussions display on sidebar ([#22157](https://github.com/RocketChat/Rocket.Chat/pull/22157)) - ### group by type active - ![image](https://user-images.githubusercontent.com/27704687/119741996-37a92500-be5d-11eb-8b36-4067a7a229f1.png) - - ### group by type inactive + ### group by type active + ![image](https://user-images.githubusercontent.com/27704687/119741996-37a92500-be5d-11eb-8b36-4067a7a229f1.png) + + ### group by type inactive ![image](https://user-images.githubusercontent.com/27704687/119742054-56a7b700-be5d-11eb-8810-e31d4216f573.png) - regression: fix departments with empty ancestors not being returned ([#22068](https://github.com/RocketChat/Rocket.Chat/pull/22068)) @@ -2483,8 +2472,8 @@ - regression: Fix Users list in the Administration ([#22034](https://github.com/RocketChat/Rocket.Chat/pull/22034) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - The app crashed if no custom fields for user profiles have been created by the admin. I fixed this issue. This bug was introduced by a recent commit. - + The app crashed if no custom fields for user profiles have been created by the admin. I fixed this issue. This bug was introduced by a recent commit. + https://user-images.githubusercontent.com/45966964/118210838-5b3a9b80-b46b-11eb-9fe5-5b813848190c.mp4 - Regression: Improve migration 225 ([#22099](https://github.com/RocketChat/Rocket.Chat/pull/22099)) @@ -2501,7 +2490,7 @@ - Regression: not allowed to edit roles due to a new verification ([#22159](https://github.com/RocketChat/Rocket.Chat/pull/22159)) - introduced by https://github.com/RocketChat/Rocket.Chat/pull/21905 + introduced by https://github.com/RocketChat/Rocket.Chat/pull/21905 ![Peek 2021-05-26 22-21](https://user-images.githubusercontent.com/27704687/119750970-b9567e00-be70-11eb-9d52-04c8595950df.gif) - regression: Select Team Modal margin ([#22030](https://github.com/RocketChat/Rocket.Chat/pull/22030)) @@ -2512,10 +2501,10 @@ - Regression: Visual issue on sort list item ([#22158](https://github.com/RocketChat/Rocket.Chat/pull/22158)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/119743703-d84d1400-be60-11eb-97cc-c8256b2c8b07.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/119743703-d84d1400-be60-11eb-97cc-c8256b2c8b07.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/119743638-b18edd80-be60-11eb-828d-22cc5e1b2f5b.png) - Release 3.14.2 ([#22135](https://github.com/RocketChat/Rocket.Chat/pull/22135)) @@ -2701,12 +2690,12 @@ - Paginated and Filtered selects on new/edit unit ([#22052](https://github.com/RocketChat/Rocket.Chat/pull/22052) by [@rafaelblink](https://github.com/rafaelblink)) - REQUIRES https://github.com/RocketChat/Rocket.Chat.Fuselage/pull/447 - - Adds infinite scrolling selects to the units edit/create with the ability to be filtered by text as well - - ![Screen Shot 2021-05-17 at 9 24 19 AM](https://user-images.githubusercontent.com/20868078/118487999-abc32a80-b6f1-11eb-8d58-d031111ea0fb.png) - + REQUIRES https://github.com/RocketChat/Rocket.Chat.Fuselage/pull/447 + + Adds infinite scrolling selects to the units edit/create with the ability to be filtered by text as well + + ![Screen Shot 2021-05-17 at 9 24 19 AM](https://user-images.githubusercontent.com/20868078/118487999-abc32a80-b6f1-11eb-8d58-d031111ea0fb.png) + This Affects the monitors and departments inputs ### 🚀 Improvements @@ -2782,24 +2771,18 @@ - New set of rules for client code ([#21318](https://github.com/RocketChat/Rocket.Chat/pull/21318)) - This _small_ PR does the following: - - - - Now **React** is the web client's first-class citizen, being **loaded before Blaze**. Thus, `BlazeLayout` calls render templates inside of a React component (`BlazeLayoutWrapper`); - - - Main client startup code, including polyfills, is written in **TypeScript**; - - - At the moment, routes are treated as regular startup code; it's expected that `FlowRouter` will be deprecated in favor of a new routing library; - - - **React** was updated to major version **17**, deprecating the usage of `React` as namespace (e.g. use `memo()` instead of `React.memo()`); - - - The `client/` and `ee/client/` directory are linted with a **custom ESLint configuration** that includes: - - **Prettier**; - - `react-hooks/*` rules for TypeScript files; - - `react/no-multi-comp`, enforcing the rule of **one single React component per module**; - - `react/display-name`, which enforces that **React components must have a name for debugging**; - - `import/named`, avoiding broken named imports. - + This _small_ PR does the following: + + - Now **React** is the web client's first-class citizen, being **loaded before Blaze**. Thus, `BlazeLayout` calls render templates inside of a React component (`BlazeLayoutWrapper`); + - Main client startup code, including polyfills, is written in **TypeScript**; + - At the moment, routes are treated as regular startup code; it's expected that `FlowRouter` will be deprecated in favor of a new routing library; + - **React** was updated to major version **17**, deprecating the usage of `React` as namespace (e.g. use `memo()` instead of `React.memo()`); + - The `client/` and `ee/client/` directory are linted with a **custom ESLint configuration** that includes: + - **Prettier**; + - `react-hooks/*` rules for TypeScript files; + - `react/no-multi-comp`, enforcing the rule of **one single React component per module**; + - `react/display-name`, which enforces that **React components must have a name for debugging**; + - `import/named`, avoiding broken named imports. - A bunch of components were refactored to match the new ESLint rules. - On Hold system messages ([#21360](https://github.com/RocketChat/Rocket.Chat/pull/21360) by [@rafaelblink](https://github.com/rafaelblink)) @@ -2808,15 +2791,12 @@ - Password history ([#21607](https://github.com/RocketChat/Rocket.Chat/pull/21607)) - - Store each user's previously used passwords in a `passwordHistory` field (in the `users` record); - - - Users' previously used passwords are stored in their `passwordHistory` even when the setting is disabled; - - - Add "Password History" setting -- when enabled, it blocks users from reusing their most recent passwords; - - - Convert `comparePassword` file to TypeScript. - - ![Password_Change](https://user-images.githubusercontent.com/36537004/115035168-ac726200-9ea2-11eb-93c6-fc8182ba5f3f.png) + - Store each user's previously used passwords in a `passwordHistory` field (in the `users` record); + - Users' previously used passwords are stored in their `passwordHistory` even when the setting is disabled; + - Add "Password History" setting -- when enabled, it blocks users from reusing their most recent passwords; + - Convert `comparePassword` file to TypeScript. + + ![Password_Change](https://user-images.githubusercontent.com/36537004/115035168-ac726200-9ea2-11eb-93c6-fc8182ba5f3f.png) ![Password_History](https://user-images.githubusercontent.com/36537004/115035175-ad0af880-9ea2-11eb-9f40-94c6327a9854.png) - REST endpoint `teams.update` ([#21134](https://github.com/RocketChat/Rocket.Chat/pull/21134) by [@g-thome](https://github.com/g-thome)) @@ -2834,18 +2814,14 @@ - Add error messages to the creation of channels or usernames containing reserved words ([#21016](https://github.com/RocketChat/Rocket.Chat/pull/21016)) - Display error messages when the user attempts to create or edit users' or channels' names with any of the following words (**case-insensitive**): - - - admin; - - - administrator; - - - system; - - - user. - ![create-channel](https://user-images.githubusercontent.com/36537004/110132223-b421ef80-7da9-11eb-82bc-f0d4e1df967f.png) - ![register-username](https://user-images.githubusercontent.com/36537004/110132234-b71ce000-7da9-11eb-904e-580233625951.png) - ![change-channel](https://user-images.githubusercontent.com/36537004/110143057-96f31e00-7db5-11eb-994a-39ae9e63392e.png) + Display error messages when the user attempts to create or edit users' or channels' names with any of the following words (**case-insensitive**): + - admin; + - administrator; + - system; + - user. + ![create-channel](https://user-images.githubusercontent.com/36537004/110132223-b421ef80-7da9-11eb-82bc-f0d4e1df967f.png) + ![register-username](https://user-images.githubusercontent.com/36537004/110132234-b71ce000-7da9-11eb-904e-580233625951.png) + ![change-channel](https://user-images.githubusercontent.com/36537004/110143057-96f31e00-7db5-11eb-994a-39ae9e63392e.png) ![change-username](https://user-images.githubusercontent.com/36537004/110143065-98244b00-7db5-11eb-9d13-afc5dc9866de.png) - add permission check when adding a channel to a team ([#21689](https://github.com/RocketChat/Rocket.Chat/pull/21689) by [@g-thome](https://github.com/g-thome)) @@ -2870,8 +2846,7 @@ - Resize custom emojis on upload instead of saving at max res ([#21593](https://github.com/RocketChat/Rocket.Chat/pull/21593)) - - Create new MediaService (ideally, should be in charge of all media-related operations) - + - Create new MediaService (ideally, should be in charge of all media-related operations) - Resize emojis to 128x128 ### 🐛 Bug fixes @@ -2891,25 +2866,25 @@ - Allows more than 25 discussions/files to be loaded in the contextualbar ([#21511](https://github.com/RocketChat/Rocket.Chat/pull/21511) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - In some places, you could not load more than 25 threads/discussions/files on the screen when searching the lists in the contextualbar. - Threads & list are numbered for a better view of the solution - - + In some places, you could not load more than 25 threads/discussions/files on the screen when searching the lists in the contextualbar. + Threads & list are numbered for a better view of the solution + + https://user-images.githubusercontent.com/45966964/114222225-93335800-996e-11eb-833f-568e83129aae.mp4 - Allows more than 25 threads to be loaded, fixes #21507 ([#21508](https://github.com/RocketChat/Rocket.Chat/pull/21508) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - Allows to display more than 25 users maximum in the users list ([#21518](https://github.com/RocketChat/Rocket.Chat/pull/21518) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - Now when you scroll to the bottom of the users list, it shows more users. Before the fix, the limit for the query for loadMore was calculated so that no additional users could be loaded. - - Before - - https://user-images.githubusercontent.com/45966964/114249739-baece500-999b-11eb-9bb0-3a5bcee18ad8.mp4 - - After - - + Now when you scroll to the bottom of the users list, it shows more users. Before the fix, the limit for the query for loadMore was calculated so that no additional users could be loaded. + + Before + + https://user-images.githubusercontent.com/45966964/114249739-baece500-999b-11eb-9bb0-3a5bcee18ad8.mp4 + + After + + https://user-images.githubusercontent.com/45966964/114249895-364e9680-999c-11eb-985c-47aedc763488.mp4 - App installation from marketplace not correctly displaying the permissions ([#21470](https://github.com/RocketChat/Rocket.Chat/pull/21470)) @@ -2976,19 +2951,19 @@ - Margins on contextual bar information ([#21457](https://github.com/RocketChat/Rocket.Chat/pull/21457)) - ### Room - **Before** - ![image](https://user-images.githubusercontent.com/27704687/115080812-ba8fa500-9ed9-11eb-9078-3625603bf92b.png) - - **After** - ![image](https://user-images.githubusercontent.com/27704687/115080966-e9a61680-9ed9-11eb-929f-6516c1563e99.png) - - ### Livechat + ### Room + **Before** + ![image](https://user-images.githubusercontent.com/27704687/115080812-ba8fa500-9ed9-11eb-9078-3625603bf92b.png) + + **After** + ![image](https://user-images.githubusercontent.com/27704687/115080966-e9a61680-9ed9-11eb-929f-6516c1563e99.png) + + ### Livechat ![image](https://user-images.githubusercontent.com/27704687/113640101-1859fc80-9651-11eb-88f8-09a899953988.png) - Message Block ordering ([#21464](https://github.com/RocketChat/Rocket.Chat/pull/21464)) - Reactions should come before reply button. + Reactions should come before reply button. ![image](https://user-images.githubusercontent.com/40830821/113748926-6f0e1780-96df-11eb-93a5-ddcfa891413e.png) - Message link null corrupts message rendering ([#21579](https://github.com/RocketChat/Rocket.Chat/pull/21579) by [@g-thome](https://github.com/g-thome)) @@ -3041,19 +3016,15 @@ - Typos/missing elements in the French translation ([#21525](https://github.com/RocketChat/Rocket.Chat/pull/21525) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - - I have corrected some typos in the translation - - - I added a translation for missing words - - - I took the opportunity to correct a mistranslated word - - - Test_Desktop_Notifications was missing in the EN and FR file + - I have corrected some typos in the translation + - I added a translation for missing words + - I took the opportunity to correct a mistranslated word + - Test_Desktop_Notifications was missing in the EN and FR file ![image](https://user-images.githubusercontent.com/45966964/114290186-e7792d80-9a7d-11eb-8164-3b5e72e93703.png) - Updating a message causing URLs to be parsed even within markdown code ([#21489](https://github.com/RocketChat/Rocket.Chat/pull/21489)) - - Fix `updateMessage` to avoid parsing URLs inside markdown - + - Fix `updateMessage` to avoid parsing URLs inside markdown - Honor `parseUrls` property when updating messages - Use async await in TeamChannels delete channel action ([#21534](https://github.com/RocketChat/Rocket.Chat/pull/21534)) @@ -3066,8 +3037,8 @@ - Wrong user in user info ([#21451](https://github.com/RocketChat/Rocket.Chat/pull/21451)) - Fixed some race conditions in admin. - + Fixed some race conditions in admin. + Self DMs used to be created with the userId duplicated. Sometimes rooms can have 2 equal uids, but it's a self DM. Fixed a getter so this isn't a problem anymore.
@@ -3076,30 +3047,22 @@ - Doc: Corrected links to documentation of rocket.chat README.md ([#20478](https://github.com/RocketChat/Rocket.Chat/pull/20478) by [@joshi008](https://github.com/joshi008)) - The link for documentation in the readme was previously https://rocket.chat/docs/ while that was not working and according to the website it was https://docs.rocket.chat/ - The link for deployment methods in readme was corrected from https://rocket.chat/docs/installation/paas-deployments/ to https://docs.rocket.chat/installation/paas-deployments + The link for documentation in the readme was previously https://rocket.chat/docs/ while that was not working and according to the website it was https://docs.rocket.chat/ + The link for deployment methods in readme was corrected from https://rocket.chat/docs/installation/paas-deployments/ to https://docs.rocket.chat/installation/paas-deployments Some more links to the documentations were giving 404 error which hence updated. - [Improve] Remove useless tabbar options from Omnichannel rooms ([#21561](https://github.com/RocketChat/Rocket.Chat/pull/21561) by [@rafaelblink](https://github.com/rafaelblink)) - A React-based replacement for BlazeLayout ([#21527](https://github.com/RocketChat/Rocket.Chat/pull/21527)) - - The Meteor package **`kadira:blaze-layout` was removed**; - - - A **global subscription** for the current application layout (**`appLayout`**) replaces `BlazeLayout` entirely; - - - The **`#react-root` element** is rendered on server-side instead of dynamically injected into the DOM tree; - - - The **"page loading" throbber** is now rendered on the React tree; - - - The **`renderRouteComponent` helper was removed**; - - - Some code run without any criteria on **`main` template** module was moved into **client startup modules**; - - - React portals used to embed Blaze templates have their own subscription (**`blazePortals`**); - - - Some **route components were refactored** to remove a URL path trap originally disabled by `renderRouteComponent`; - + - The Meteor package **`kadira:blaze-layout` was removed**; + - A **global subscription** for the current application layout (**`appLayout`**) replaces `BlazeLayout` entirely; + - The **`#react-root` element** is rendered on server-side instead of dynamically injected into the DOM tree; + - The **"page loading" throbber** is now rendered on the React tree; + - The **`renderRouteComponent` helper was removed**; + - Some code run without any criteria on **`main` template** module was moved into **client startup modules**; + - React portals used to embed Blaze templates have their own subscription (**`blazePortals`**); + - Some **route components were refactored** to remove a URL path trap originally disabled by `renderRouteComponent`; - A new component to embed the DOM nodes generated by **`RoomManager`** was created. - Add ')' after Date and Time in DB migration ([#21519](https://github.com/RocketChat/Rocket.Chat/pull/21519) by [@im-adithya](https://github.com/im-adithya)) @@ -3122,8 +3085,8 @@ - Chore: Meteor update to 2.1.1 ([#21494](https://github.com/RocketChat/Rocket.Chat/pull/21494)) - Basically Node update to version 12.22.1 - + Basically Node update to version 12.22.1 + Meteor change log https://github.com/meteor/meteor/blob/devel/History.md#v211-2021-04-06 - Chore: Remove control character from room model operation ([#21493](https://github.com/RocketChat/Rocket.Chat/pull/21493)) @@ -3132,8 +3095,7 @@ - Fix: Missing module `eventemitter3` for micro services ([#21611](https://github.com/RocketChat/Rocket.Chat/pull/21611)) - - Fix error when running micro services after version 3.12 - + - Fix error when running micro services after version 3.12 - Fix build of docker image version latest for micro services - Language update from LingoHub 🤖 on 2021-04-05Z ([#21446](https://github.com/RocketChat/Rocket.Chat/pull/21446)) @@ -3146,12 +3108,9 @@ - QoL improvements to add channel to team flow ([#21778](https://github.com/RocketChat/Rocket.Chat/pull/21778)) - - Fixed canAccessRoom validation - - - Added e2e tests - - - Removed channels that user cannot add to the team from autocomplete suggestions - + - Fixed canAccessRoom validation + - Added e2e tests + - Removed channels that user cannot add to the team from autocomplete suggestions - Improved error messages - Regression: Bold, italic and strike render (Original markdown) ([#21747](https://github.com/RocketChat/Rocket.Chat/pull/21747)) @@ -3174,10 +3133,10 @@ - Regression: Legacy Banner Position ([#21598](https://github.com/RocketChat/Rocket.Chat/pull/21598)) - ### Before: - ![image](https://user-images.githubusercontent.com/27704687/114961773-dc3c4e00-9e3f-11eb-9a32-e882db3fbfbc.png) - - ### After + ### Before: + ![image](https://user-images.githubusercontent.com/27704687/114961773-dc3c4e00-9e3f-11eb-9a32-e882db3fbfbc.png) + + ### After ![image](https://user-images.githubusercontent.com/27704687/114961673-a6976500-9e3f-11eb-9238-a12870d7db8f.png) - regression: Markdown broken on safari ([#21780](https://github.com/RocketChat/Rocket.Chat/pull/21780)) @@ -3387,62 +3346,56 @@ - **APPS:** New event interfaces for pre/post user leaving a room ([#20917](https://github.com/RocketChat/Rocket.Chat/pull/20917) by [@lucassartor](https://github.com/lucassartor)) - Added events and errors that trigger when a user leaves a room. + Added events and errors that trigger when a user leaves a room. That way it can communicate with the Apps-Engine by the `IPreRoomUserLeave` and `IPostRoomUserLeave` event interfaces. - **Enterprise:** Omnichannel On-Hold Queue ([#20945](https://github.com/RocketChat/Rocket.Chat/pull/20945)) - ### About this feature - This feature has been introduced to deal with Inactive chats. A chat is considered Inactive if an Omnichannel End User (aka Visitor) has not replied back to an agent in some time. These types of inactive chats become very important when an organisation has a limit set for `Max Simultaneous Chats per agent` which is defined by the following setting :point_down: , as more number of Inactive chats would directly affect an agent's productivity. - ![image](https://user-images.githubusercontent.com/34130764/111533003-4d7ad980-878c-11eb-8c1c-2796678a07db.png) - - Before this feature, we only had one option to deal with such Inactive/Abandoned chats - which was to auto close abandoned chats via this setting :point_down: - ![image](https://user-images.githubusercontent.com/34130764/111534353-e65e2480-878d-11eb-82a5-71368064ef45.png) - - however closing a chat isn't a best option for some cases. Let me take an example to explain a scenario - - > An agent is assisting a customer for installing a very huge software which is likely to take more than 20-30 minutes to download. In such scenarios closing a chat isn't the best approach since even after the lengthy download the customer might still need some assist from the agent. - > So basically this chat is going to block the agent's queue until the customer is able to finish his time-consuming download task in which he/she doesn't require any agent's assistance. Due to the `Max Simultaneous Chats per agent` limit, the agent is also not able to use this extra time to help other customer thus affecting his overall productivity. - - **So how does the On-Hold feature solve this problem?** - With the On-Hold feature, an agent is now able to place a chat on-hold. On-Hold chats **don’t count towards the maximum number of concurrent chats** an agent can have. So in our above example, the agent can simply now place the customer on-hold for 20-30 minutes until the customer downloads the software and within this time, the agent can serve other customers - hence increasing the productivity of an agent. - - ---------------------------------------- - ### Working of the new On-Hold feature - - #### How can you place a chat on Hold ? - - A chat can be placed on-hold via 2 means - - 1. Automatically place Abandoned chats On-hold - ![image](https://user-images.githubusercontent.com/34130764/111537074-06431780-8791-11eb-8d23-99f5d9f8ec45.png) - Via this :top: option you can define a timer which will get started when a customer sends a message. If we don't receive any message from the customer within this timer, the timer will get expired and the chat will be considered as Abandoned. - ![image](https://user-images.githubusercontent.com/34130764/111537346-53bf8480-8791-11eb-8dc7-260633b4e98f.png) - The via this :top: setting you can choose to automatically place this abandoned chat On Hold - - 2. Manually place a chat On Hold - As an admin, you can allow an agent to manually place a chat on-hold. To do so, you'll need to turn on this :point_down: setting - ![image](https://user-images.githubusercontent.com/34130764/111537545-97b28980-8791-11eb-86fd-db45b87e9cc1.png) - Now an agent will be able to see a new `On Hold` button within their `Visitor Info Panel` like this :point_down: , provided the agent has sent the last message - ![image](https://user-images.githubusercontent.com/34130764/111537853-f24be580-8791-11eb-9561-d77ba430c625.png) - - #### How can you resume a On Hold chat ? - An On Hold chat can be resumed via 2 means - - - 1. If the Customer sends a message - If the Customer / Omnichannel End User sends a message to the On Hold chat, the On Hold chat will get automatically resumed. - - 2. Manually by agent - An Agent can manually resume the On Hold chat via clicking the `Resume` button in the bottom of a chat room. - ![image](https://user-images.githubusercontent.com/34130764/111538666-f88e9180-8792-11eb-8d14-01453b8e3db0.png) - - #### What would happen if the agent already reached maximum chats, and a On-Hold chat gets resumed ? - Based on how the chat was resumed, there are multiple cases are each case is dealt differently - - - - If an agent manually tries to resume the On Hold chat, he/she will get an error saying `Maximum Simultaneous chat limit reached` - + ### About this feature + This feature has been introduced to deal with Inactive chats. A chat is considered Inactive if an Omnichannel End User (aka Visitor) has not replied back to an agent in some time. These types of inactive chats become very important when an organisation has a limit set for `Max Simultaneous Chats per agent` which is defined by the following setting :point_down: , as more number of Inactive chats would directly affect an agent's productivity. + ![image](https://user-images.githubusercontent.com/34130764/111533003-4d7ad980-878c-11eb-8c1c-2796678a07db.png) + + Before this feature, we only had one option to deal with such Inactive/Abandoned chats - which was to auto close abandoned chats via this setting :point_down: + ![image](https://user-images.githubusercontent.com/34130764/111534353-e65e2480-878d-11eb-82a5-71368064ef45.png) + + however closing a chat isn't a best option for some cases. Let me take an example to explain a scenario + + > An agent is assisting a customer for installing a very huge software which is likely to take more than 20-30 minutes to download. In such scenarios closing a chat isn't the best approach since even after the lengthy download the customer might still need some assist from the agent. + > So basically this chat is going to block the agent's queue until the customer is able to finish his time-consuming download task in which he/she doesn't require any agent's assistance. Due to the `Max Simultaneous Chats per agent` limit, the agent is also not able to use this extra time to help other customer thus affecting his overall productivity. + + **So how does the On-Hold feature solve this problem?** + With the On-Hold feature, an agent is now able to place a chat on-hold. On-Hold chats **don’t count towards the maximum number of concurrent chats** an agent can have. So in our above example, the agent can simply now place the customer on-hold for 20-30 minutes until the customer downloads the software and within this time, the agent can serve other customers - hence increasing the productivity of an agent. + + ---------------------------------------- + ### Working of the new On-Hold feature + + #### How can you place a chat on Hold ? + + A chat can be placed on-hold via 2 means + 1. Automatically place Abandoned chats On-hold + ![image](https://user-images.githubusercontent.com/34130764/111537074-06431780-8791-11eb-8d23-99f5d9f8ec45.png) + Via this :top: option you can define a timer which will get started when a customer sends a message. If we don't receive any message from the customer within this timer, the timer will get expired and the chat will be considered as Abandoned. + ![image](https://user-images.githubusercontent.com/34130764/111537346-53bf8480-8791-11eb-8dc7-260633b4e98f.png) + The via this :top: setting you can choose to automatically place this abandoned chat On Hold + 2. Manually place a chat On Hold + As an admin, you can allow an agent to manually place a chat on-hold. To do so, you'll need to turn on this :point_down: setting + ![image](https://user-images.githubusercontent.com/34130764/111537545-97b28980-8791-11eb-86fd-db45b87e9cc1.png) + Now an agent will be able to see a new `On Hold` button within their `Visitor Info Panel` like this :point_down: , provided the agent has sent the last message + ![image](https://user-images.githubusercontent.com/34130764/111537853-f24be580-8791-11eb-9561-d77ba430c625.png) + + #### How can you resume a On Hold chat ? + An On Hold chat can be resumed via 2 means + + 1. If the Customer sends a message + If the Customer / Omnichannel End User sends a message to the On Hold chat, the On Hold chat will get automatically resumed. + 2. Manually by agent + An Agent can manually resume the On Hold chat via clicking the `Resume` button in the bottom of a chat room. + ![image](https://user-images.githubusercontent.com/34130764/111538666-f88e9180-8792-11eb-8d14-01453b8e3db0.png) + + #### What would happen if the agent already reached maximum chats, and a On-Hold chat gets resumed ? + Based on how the chat was resumed, there are multiple cases are each case is dealt differently + + - If an agent manually tries to resume the On Hold chat, he/she will get an error saying `Maximum Simultaneous chat limit reached` - If a customer replies back on an On Hold chat and the last serving agent has reached maximum capacity, then this customer will be placed on the queue again from where based on the Routing Algorithm selected, the chat will get transferred to any available agent - Ability to hide 'Room topic changed' system messages ([#21062](https://github.com/RocketChat/Rocket.Chat/pull/21062) by [@Tirieru](https://github.com/Tirieru)) @@ -3453,39 +3406,33 @@ - Teams ([#20966](https://github.com/RocketChat/Rocket.Chat/pull/20966) by [@g-thome](https://github.com/g-thome)) - ## Teams - - - - You can easily group your users as Teams on Rocket.Chat. The feature takes the hassle out of managing multiple users one by one and allows you to handle them at the same time efficiently. - - - - - Teams can be public or private and each team can have its own channels, which also can be public or private. - - - It's possible to add existing channels to a Team or create new ones inside a Team. - - - It's possible to invite people outside a Team to join Team's channels. - - - It's possible to convert channels to Teams - - - It's possible to add all team members to a channel at once - - - Team members have roles - - - ![image](https://user-images.githubusercontent.com/70927132/113421955-4f56b680-93a2-11eb-80dc-9b70a3f09b3e.png) - - - - **Quickly onboard new users with Autojoin channels** - - Teams can have Auto-join channels – channels to which the team members are automatically added, so you don’t need to go through the manual process of adding users repetitively - - ![image](https://user-images.githubusercontent.com/70927132/113419284-81194e80-939d-11eb-9fff-aeb05cbc8089.png) - - **Instantly mention multiple members at once** (available in EE) - + ## Teams + + + + You can easily group your users as Teams on Rocket.Chat. The feature takes the hassle out of managing multiple users one by one and allows you to handle them at the same time efficiently. + + + - Teams can be public or private and each team can have its own channels, which also can be public or private. + - It's possible to add existing channels to a Team or create new ones inside a Team. + - It's possible to invite people outside a Team to join Team's channels. + - It's possible to convert channels to Teams + - It's possible to add all team members to a channel at once + - Team members have roles + + + ![image](https://user-images.githubusercontent.com/70927132/113421955-4f56b680-93a2-11eb-80dc-9b70a3f09b3e.png) + + + + **Quickly onboard new users with Autojoin channels** + + Teams can have Auto-join channels – channels to which the team members are automatically added, so you don’t need to go through the manual process of adding users repetitively + + ![image](https://user-images.githubusercontent.com/70927132/113419284-81194e80-939d-11eb-9fff-aeb05cbc8089.png) + + **Instantly mention multiple members at once** (available in EE) + With Teams, you don’t need to remember everyone’s name to communicate with a team quickly. Just mention a Team — @engineers, for instance — and all members will be instantly notified. ### 🚀 Improvements @@ -3495,22 +3442,22 @@ - Added modal-box for preview after recording audio. ([#20370](https://github.com/RocketChat/Rocket.Chat/pull/20370) by [@Darshilp326](https://github.com/Darshilp326)) - A modal box will be displayed so that users can change the filename and add description. - - **Before** - - https://user-images.githubusercontent.com/55157259/105687301-4e2a8880-5f1e-11eb-873d-dc8a880a2fc8.mp4 - - **After** - + A modal box will be displayed so that users can change the filename and add description. + + **Before** + + https://user-images.githubusercontent.com/55157259/105687301-4e2a8880-5f1e-11eb-873d-dc8a880a2fc8.mp4 + + **After** + https://user-images.githubusercontent.com/55157259/105687342-597db400-5f1e-11eb-8b61-8f9d9ebad0c4.mp4 - Adds toast after follow/unfollow messages and following icon for followed messages without threads. ([#20025](https://github.com/RocketChat/Rocket.Chat/pull/20025) by [@RonLek](https://github.com/RonLek)) - There was no alert on following/unfollowing a message previously. Also, it was impossible to make out a followed message with no threads from an unfollowed one. - - This PR would show an alert on following/unfollowing a message and also display a small bell icon (similar to the ones for starred and pinned messages) when a message with no thread is followed. - + There was no alert on following/unfollowing a message previously. Also, it was impossible to make out a followed message with no threads from an unfollowed one. + + This PR would show an alert on following/unfollowing a message and also display a small bell icon (similar to the ones for starred and pinned messages) when a message with no thread is followed. + https://user-images.githubusercontent.com/28918901/103813540-43e73e00-5086-11eb-8592-2877eb650f3e.mp4 - Back to threads list button on threads contextual bar ([#20882](https://github.com/RocketChat/Rocket.Chat/pull/20882)) @@ -3523,12 +3470,12 @@ - Improve Apps permission modal ([#21193](https://github.com/RocketChat/Rocket.Chat/pull/21193) by [@lucassartor](https://github.com/lucassartor)) - Improve the UI of the Apps permission modal when installing an App that requires permissions. - - **New UI:** - ![after](https://user-images.githubusercontent.com/49413772/111685622-e817fe80-8806-11eb-998d-b56623560e74.PNG) - - **Old UI:** + Improve the UI of the Apps permission modal when installing an App that requires permissions. + + **New UI:** + ![after](https://user-images.githubusercontent.com/49413772/111685622-e817fe80-8806-11eb-998d-b56623560e74.PNG) + + **Old UI:** ![before](https://user-images.githubusercontent.com/49413772/111685897-375e2f00-8807-11eb-814e-cb8060dc1830.PNG) - Make debug logs of Apps configurable via Log_Level setting in the Admin panel ([#21000](https://github.com/RocketChat/Rocket.Chat/pull/21000) by [@cuonghuunguyen](https://github.com/cuonghuunguyen)) @@ -3539,15 +3486,15 @@ - Sort Users List In Case Insensitive Manner ([#20790](https://github.com/RocketChat/Rocket.Chat/pull/20790) by [@aditya-mitra](https://github.com/aditya-mitra)) - The users listed in the admin panel were sorted in a case-sensitive manner , where the capitals came first and then the small letters (like - *A B C a b c*). This Change fixes this by sorting the names in a caseinsensitive manner (now - *A a B b C c*). - - ### Before - - ![before](https://user-images.githubusercontent.com/55396651/108189880-3fa74980-7137-11eb-99da-6498707b4bf8.png) - - - ### With This Change - + The users listed in the admin panel were sorted in a case-sensitive manner , where the capitals came first and then the small letters (like - *A B C a b c*). This Change fixes this by sorting the names in a caseinsensitive manner (now - *A a B b C c*). + + ### Before + + ![before](https://user-images.githubusercontent.com/55396651/108189880-3fa74980-7137-11eb-99da-6498707b4bf8.png) + + + ### With This Change + ![after](https://user-images.githubusercontent.com/55396651/108190177-9dd42c80-7137-11eb-8b4e-b7cef4ba512f.png) ### 🐛 Bug fixes @@ -3561,12 +3508,12 @@ - **APPS:** Warn message while installing app in air-gapped environment ([#20992](https://github.com/RocketChat/Rocket.Chat/pull/20992) by [@lucassartor](https://github.com/lucassartor)) - Change **error** message to a **warn** message when uploading a `.zip` file app into a air-gapped environment. - - The **error** message was giving the impression for the user that the app wasn't properly being installed , which it wasn't the case: - ![error](https://user-images.githubusercontent.com/49413772/109855273-d3e4d680-7c36-11eb-824b-ad455d24710c.PNG) - - A more detailed **warn** message can fix that impression for the user: + Change **error** message to a **warn** message when uploading a `.zip` file app into a air-gapped environment. + + The **error** message was giving the impression for the user that the app wasn't properly being installed , which it wasn't the case: + ![error](https://user-images.githubusercontent.com/49413772/109855273-d3e4d680-7c36-11eb-824b-ad455d24710c.PNG) + + A more detailed **warn** message can fix that impression for the user: ![warn](https://user-images.githubusercontent.com/49413772/109855383-f2e36880-7c36-11eb-8d61-c442980bd8fd.PNG) - Add missing `unreads` field to `users.info` REST endpoint ([#20905](https://github.com/RocketChat/Rocket.Chat/pull/20905)) @@ -3581,10 +3528,10 @@ - Correct direction for admin mapview text ([#20897](https://github.com/RocketChat/Rocket.Chat/pull/20897) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) - ![Screenshot from 2021-02-25 02-49-21](https://user-images.githubusercontent.com/38764067/109068512-f8602080-7715-11eb-8e22-d610f9d046d8.png) - ![Screenshot from 2021-02-25 02-49-46](https://user-images.githubusercontent.com/38764067/109068516-fa29e400-7715-11eb-9119-1c79abce278f.png) - ![Screenshot from 2021-02-25 02-49-57](https://user-images.githubusercontent.com/38764067/109068519-fbf3a780-7715-11eb-8b3d-0dc32f898725.png) - + ![Screenshot from 2021-02-25 02-49-21](https://user-images.githubusercontent.com/38764067/109068512-f8602080-7715-11eb-8e22-d610f9d046d8.png) + ![Screenshot from 2021-02-25 02-49-46](https://user-images.githubusercontent.com/38764067/109068516-fa29e400-7715-11eb-9119-1c79abce278f.png) + ![Screenshot from 2021-02-25 02-49-57](https://user-images.githubusercontent.com/38764067/109068519-fbf3a780-7715-11eb-8b3d-0dc32f898725.png) + The text says the share button will be on the left of the messagebox once enabled. However, it actually is on the right. - Correct ignored message CSS ([#20928](https://github.com/RocketChat/Rocket.Chat/pull/20928) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) @@ -3601,13 +3548,13 @@ - Custom emojis to override default ([#20359](https://github.com/RocketChat/Rocket.Chat/pull/20359) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) - Due to the sequence of the imports and how the emojiRenderer prioritizes lists, the custom emojis could not override the emojione emojis. Making two small changes fixed the issue. - - With the custom emoji for `:facepalm:` added, you can check out the result below: - ### Before - ![Screenshot from 2021-01-25 02-20-04](https://user-images.githubusercontent.com/38764067/105643088-dfb0e080-5eb3-11eb-8a00-582c53fbe9a4.png) - - ### After + Due to the sequence of the imports and how the emojiRenderer prioritizes lists, the custom emojis could not override the emojione emojis. Making two small changes fixed the issue. + + With the custom emoji for `:facepalm:` added, you can check out the result below: + ### Before + ![Screenshot from 2021-01-25 02-20-04](https://user-images.githubusercontent.com/38764067/105643088-dfb0e080-5eb3-11eb-8a00-582c53fbe9a4.png) + + ### After ![Screenshot from 2021-01-25 02-18-58](https://user-images.githubusercontent.com/38764067/105643076-cdcf3d80-5eb3-11eb-84b8-5dbc4f1135df.png) - Empty URL in user avatar doesn't show error and enables save ([#20440](https://github.com/RocketChat/Rocket.Chat/pull/20440) by [@im-adithya](https://github.com/im-adithya)) @@ -3620,12 +3567,12 @@ - Fix the search list showing the last channel ([#21160](https://github.com/RocketChat/Rocket.Chat/pull/21160) by [@shrinish123](https://github.com/shrinish123)) - The search list now also properly shows the last channel - Before : - - ![searchlist](https://user-images.githubusercontent.com/56491104/111471487-f3a7ee80-874e-11eb-9c6e-19bbf0731d60.png) - - After : + The search list now also properly shows the last channel + Before : + + ![searchlist](https://user-images.githubusercontent.com/56491104/111471487-f3a7ee80-874e-11eb-9c6e-19bbf0731d60.png) + + After : ![search_final](https://user-images.githubusercontent.com/56491104/111471521-fe628380-874e-11eb-8fa3-d1edb57587e1.png) - Follow thread action on threads list ([#20881](https://github.com/RocketChat/Rocket.Chat/pull/20881)) @@ -3650,13 +3597,13 @@ - Multi Select isn't working in Export Messages ([#21236](https://github.com/RocketChat/Rocket.Chat/pull/21236) by [@PriyaBihani](https://github.com/PriyaBihani)) - While exporting messages, we were not able to select multiple Users like this: - - https://user-images.githubusercontent.com/69837339/111953057-169a2000-8b0c-11eb-94a4-0e1657683f96.mp4 - - Now we can select multiple users: - - + While exporting messages, we were not able to select multiple Users like this: + + https://user-images.githubusercontent.com/69837339/111953057-169a2000-8b0c-11eb-94a4-0e1657683f96.mp4 + + Now we can select multiple users: + + https://user-images.githubusercontent.com/69837339/111953097-274a9600-8b0c-11eb-9177-bec388b042bd.mp4 - New Channel popover not closing ([#21080](https://github.com/RocketChat/Rocket.Chat/pull/21080)) @@ -3665,31 +3612,31 @@ - OEmbedURLWidget - Show Full Embedded Text Description ([#20569](https://github.com/RocketChat/Rocket.Chat/pull/20569) by [@aditya-mitra](https://github.com/aditya-mitra)) - Embeds were cutoff when either _urls had a long description_. - This was handled by removing `overflow:hidden;text-overflow:ellipsis;` from the inline styles in [`oembedUrlWidget.html`](https://github.com/RocketChat/Rocket.Chat/blob/develop/app/oembed/client/oembedUrlWidget.html#L28). - - ### Earlier - - ![earlier](https://user-images.githubusercontent.com/55396651/107110825-00dcde00-6871-11eb-866e-13cabc5b0d05.png) - - ### Now - + Embeds were cutoff when either _urls had a long description_. + This was handled by removing `overflow:hidden;text-overflow:ellipsis;` from the inline styles in [`oembedUrlWidget.html`](https://github.com/RocketChat/Rocket.Chat/blob/develop/app/oembed/client/oembedUrlWidget.html#L28). + + ### Earlier + + ![earlier](https://user-images.githubusercontent.com/55396651/107110825-00dcde00-6871-11eb-866e-13cabc5b0d05.png) + + ### Now + ![now](https://user-images.githubusercontent.com/55396651/107110794-ca06c800-6870-11eb-9b3b-168679936612.png) - Reactions list showing users in reactions option of message action. ([#20753](https://github.com/RocketChat/Rocket.Chat/pull/20753) by [@Darshilp326](https://github.com/Darshilp326)) - Reactions list shows emojis with respected users who have reacted with that emoji. - + Reactions list shows emojis with respected users who have reacted with that emoji. + https://user-images.githubusercontent.com/55157259/107857609-5870e000-6e55-11eb-8137-494a9f71b171.mp4 - Removing truncation from profile ([#20352](https://github.com/RocketChat/Rocket.Chat/pull/20352) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) - Truncating text in profile view was making some information completely inaccessible. Removed it from the user status and the custom fields where if the information is longer, the user would actually want to see all of it. - - ### Before - ![Screenshot from 2021-01-24 20-54-44](https://user-images.githubusercontent.com/38764067/105634935-7e264d00-5e86-11eb-8a6c-9f2a363e0f6c.png) - - ### After + Truncating text in profile view was making some information completely inaccessible. Removed it from the user status and the custom fields where if the information is longer, the user would actually want to see all of it. + + ### Before + ![Screenshot from 2021-01-24 20-54-44](https://user-images.githubusercontent.com/38764067/105634935-7e264d00-5e86-11eb-8a6c-9f2a363e0f6c.png) + + ### After ![Screenshot from 2021-01-24 20-54-06](https://user-images.githubusercontent.com/38764067/105634940-82eb0100-5e86-11eb-8b90-e97a43c5e938.png) - Replace wrong field description on Room Information panel ([#21395](https://github.com/RocketChat/Rocket.Chat/pull/21395) by [@rafaelblink](https://github.com/rafaelblink)) @@ -3700,8 +3647,8 @@ - Set establishing to false if OTR timeouts ([#21183](https://github.com/RocketChat/Rocket.Chat/pull/21183) by [@Darshilp326](https://github.com/Darshilp326)) - Set establishing false if OTR timeouts. - + Set establishing false if OTR timeouts. + https://user-images.githubusercontent.com/55157259/111617086-b30cab80-8808-11eb-8740-3b4ffacfc322.mp4 - Sidebar scroll missing full height ([#21071](https://github.com/RocketChat/Rocket.Chat/pull/21071)) @@ -3740,33 +3687,20 @@ - Chore: Add tests for Meteor methods ([#20901](https://github.com/RocketChat/Rocket.Chat/pull/20901)) - Add end-to-end tests for the following meteor methods - - - - [x] public-settings:get - - - [x] rooms:get - - - [x] subscriptions:get - - - [x] permissions:get - - - [x] loadMissedMessages - - - [x] loadHistory - - - [x] listCustomUserStatus - - - [x] getUserRoles - - - [x] getRoomRoles (called by the API, already covered) - - - [x] getMessages - - - [x] getUsersOfRoom - - - [x] loadNextMessages - + Add end-to-end tests for the following meteor methods + + - [x] public-settings:get + - [x] rooms:get + - [x] subscriptions:get + - [x] permissions:get + - [x] loadMissedMessages + - [x] loadHistory + - [x] listCustomUserStatus + - [x] getUserRoles + - [x] getRoomRoles (called by the API, already covered) + - [x] getMessages + - [x] getUsersOfRoom + - [x] loadNextMessages - [x] getThreadMessages - Chore: Meteor update 2.1 ([#21061](https://github.com/RocketChat/Rocket.Chat/pull/21061)) @@ -3779,10 +3713,8 @@ - Improve: Increase testing coverage ([#21015](https://github.com/RocketChat/Rocket.Chat/pull/21015)) - Add test for - - - settings/raw - + Add test for + - settings/raw - minimongo/comparisons - Improve: NPS survey fetch ([#21263](https://github.com/RocketChat/Rocket.Chat/pull/21263)) @@ -3801,19 +3733,17 @@ - Regression: Add scope to permission checks in Team's endpoints ([#21369](https://github.com/RocketChat/Rocket.Chat/pull/21369)) - - Include scope (team's main room ID) in the permission checks; + - Include scope (team's main room ID) in the permission checks; - Remove the `teamName` parameter from the `members`, `addMembers`, `updateMember` and `removeMembers` methods (since `teamId` will always be defined). - Regression: Add support to filter on `teams.listRooms` endpoint ([#21327](https://github.com/RocketChat/Rocket.Chat/pull/21327)) - - Add support for queries (within the `query` parameter); - + - Add support for queries (within the `query` parameter); - Add support to pagination (`offset` and `count`) when an user doesn't have the permission to get all rooms. - Regression: Add teams support to directory ([#21351](https://github.com/RocketChat/Rocket.Chat/pull/21351)) - - Change `directory.js` to reduce function complexity - + - Change `directory.js` to reduce function complexity - Add `teams` type of item. Directory will return all public teams & private teams the user is part of. - Regression: add view room action on Teams Channels ([#21295](https://github.com/RocketChat/Rocket.Chat/pull/21295)) @@ -3866,19 +3796,18 @@ - Regression: Quick action button missing for Omnichannel On-Hold queue ([#21285](https://github.com/RocketChat/Rocket.Chat/pull/21285)) - - Move the Manual On Hold button to the new Omnichannel Header - ![image](https://user-images.githubusercontent.com/34130764/112291749-6ae10380-8cb6-11eb-94cd-e05efc14b1bf.png) - ![image](https://user-images.githubusercontent.com/34130764/112304146-27d95d00-8cc3-11eb-85db-dde04a110dd1.png) - - + - Move the Manual On Hold button to the new Omnichannel Header + ![image](https://user-images.githubusercontent.com/34130764/112291749-6ae10380-8cb6-11eb-94cd-e05efc14b1bf.png) + ![image](https://user-images.githubusercontent.com/34130764/112304146-27d95d00-8cc3-11eb-85db-dde04a110dd1.png) + - Minor fixes - regression: Remove Breadcrumbs and update Tag component ([#21399](https://github.com/RocketChat/Rocket.Chat/pull/21399)) - Regression: Remove channel action on add channel's modal don't work ([#21356](https://github.com/RocketChat/Rocket.Chat/pull/21356)) - ![removechannel-on-add-existing-modal](https://user-images.githubusercontent.com/27704687/112911017-eda8fa80-90ca-11eb-9c24-47a70be0c314.gif) - + ![removechannel-on-add-existing-modal](https://user-images.githubusercontent.com/27704687/112911017-eda8fa80-90ca-11eb-9c24-47a70be0c314.gif) + ![image](https://user-images.githubusercontent.com/27704687/112911052-02858e00-90cb-11eb-85a2-0ef1f5f9ffd9.png) - Regression: Remove primary color from button in TeamChannels component ([#21293](https://github.com/RocketChat/Rocket.Chat/pull/21293)) @@ -3907,10 +3836,10 @@ - Regression: Unify Contact information displayed on the Room header and Room Info ([#21312](https://github.com/RocketChat/Rocket.Chat/pull/21312) by [@rafaelblink](https://github.com/rafaelblink)) - ![image](https://user-images.githubusercontent.com/34130764/112586659-35592900-8e22-11eb-94be-32bdff7ca883.png) - - ![image](https://user-images.githubusercontent.com/2493803/112913130-788bf400-90cf-11eb-84c6-782b203e100a.png) - + ![image](https://user-images.githubusercontent.com/34130764/112586659-35592900-8e22-11eb-94be-32bdff7ca883.png) + + ![image](https://user-images.githubusercontent.com/2493803/112913130-788bf400-90cf-11eb-84c6-782b203e100a.png) + ![image](https://user-images.githubusercontent.com/2493803/112913146-817cc580-90cf-11eb-87ad-ef79766be2b3.png) - Regression: Unify team actions to add a room to a team ([#21386](https://github.com/RocketChat/Rocket.Chat/pull/21386)) @@ -3919,10 +3848,8 @@ - Regression: Update .invite endpoints to support multiple users at once ([#21328](https://github.com/RocketChat/Rocket.Chat/pull/21328)) - - channels.invite now supports passing an array as a param (either with usernames or userIds) via `usernames` or `userIds` properties. - - - You can still use the endpoint to invite only one user via the old params `userId`, `username` or `user`. - + - channels.invite now supports passing an array as a param (either with usernames or userIds) via `usernames` or `userIds` properties. + - You can still use the endpoint to invite only one user via the old params `userId`, `username` or `user`. - Same changes apply to groups.invite - Regression: user actions in admin ([#21307](https://github.com/RocketChat/Rocket.Chat/pull/21307)) @@ -4057,7 +3984,7 @@ - Close Call contextual bar after starting jitsi call. ([#21004](https://github.com/RocketChat/Rocket.Chat/pull/21004) by [@yash-rajpal](https://github.com/yash-rajpal)) - After jitsi call is started, if the call is started in a new window then we should close contextual tab bar. + After jitsi call is started, if the call is started in a new window then we should close contextual tab bar. So, when 'YES' is pressed on modal, we call handleClose function if openNewWindow is true, as call doesn't starts on tab bar, it starts on new window. ### 🐛 Bug fixes @@ -4067,7 +3994,7 @@ - Stopping Jitsi reload ([#20973](https://github.com/RocketChat/Rocket.Chat/pull/20973) by [@yash-rajpal](https://github.com/yash-rajpal)) - The Function where Jitsi call is started gets called many times due to `room.usernames` dep of useMemo, this dep triggers reloading of this function many times. + The Function where Jitsi call is started gets called many times due to `room.usernames` dep of useMemo, this dep triggers reloading of this function many times. So removing this dep from useMemo dependencies ### 👩‍💻👨‍💻 Contributors 😍 @@ -4095,10 +4022,10 @@ - Cloud Workspace bridge ([#20838](https://github.com/RocketChat/Rocket.Chat/pull/20838)) - Adds the new CloudWorkspace functionality. - - It allows apps to request the access token for the workspace it's installed on, so it can perform actions with other Rocket.Chat services, such as the Omni Gateway. - + Adds the new CloudWorkspace functionality. + + It allows apps to request the access token for the workspace it's installed on, so it can perform actions with other Rocket.Chat services, such as the Omni Gateway. + https://github.com/RocketChat/Rocket.Chat.Apps-engine/pull/382 - Header with Breadcrumbs ([#20609](https://github.com/RocketChat/Rocket.Chat/pull/20609)) @@ -4116,10 +4043,10 @@ - Add symbol to indicate apps' required settings in the UI ([#20447](https://github.com/RocketChat/Rocket.Chat/pull/20447)) - - Apps are able to define **required** settings. These settings should not be left blank by the user and an error will be thrown and shown in the interface if an user attempts to save changes in the app details page leaving any required fields blank; - ![prt_screen_required_app_settings_warning](https://user-images.githubusercontent.com/36537004/106032964-e73cd900-60af-11eb-8eab-c11fd651b593.png) - - - A sign (*) is added to the label of app settings' fields that are required so as to highlight the fields which must not be left blank. + - Apps are able to define **required** settings. These settings should not be left blank by the user and an error will be thrown and shown in the interface if an user attempts to save changes in the app details page leaving any required fields blank; + ![prt_screen_required_app_settings_warning](https://user-images.githubusercontent.com/36537004/106032964-e73cd900-60af-11eb-8eab-c11fd651b593.png) + + - A sign (*) is added to the label of app settings' fields that are required so as to highlight the fields which must not be left blank. ![prt_screen_required_app_settings](https://user-images.githubusercontent.com/36537004/106014879-ae473900-609c-11eb-9b9e-95de7bbf20a5.png) - Add visual validation on users admin forms ([#20308](https://github.com/RocketChat/Rocket.Chat/pull/20308)) @@ -4140,20 +4067,20 @@ - Adds tooltip for sidebar header icons ([#19934](https://github.com/RocketChat/Rocket.Chat/pull/19934) by [@RonLek](https://github.com/RonLek)) - Previously the header icons in the sidebar didn't show a tooltip when hovered over. This PR fixes that. - + Previously the header icons in the sidebar didn't show a tooltip when hovered over. This PR fixes that. + ![Screenshot from 2020-12-22 15-17-41](https://user-images.githubusercontent.com/28918901/102874804-f2756700-4468-11eb-8324-b7f3194e62fe.png) - Better Presentation of Blockquotes ([#20750](https://github.com/RocketChat/Rocket.Chat/pull/20750) by [@aditya-mitra](https://github.com/aditya-mitra)) - Changed the values of `margin-top` and `margin-bottom` for *first* and *last* childs in blockquotes to increase readability. - - ### Before - - ![before](https://user-images.githubusercontent.com/55396651/107858662-3e3a0080-6e5b-11eb-8274-9bd956807235.png) - - ### Now - + Changed the values of `margin-top` and `margin-bottom` for *first* and *last* childs in blockquotes to increase readability. + + ### Before + + ![before](https://user-images.githubusercontent.com/55396651/107858662-3e3a0080-6e5b-11eb-8274-9bd956807235.png) + + ### Now + ![now](https://user-images.githubusercontent.com/55396651/107858471-480f3400-6e5a-11eb-9ccb-3f1be2fed0a4.png) - Change header based on room type ([#20612](https://github.com/RocketChat/Rocket.Chat/pull/20612)) @@ -4174,18 +4101,13 @@ - Replace react-window for react-virtuoso package ([#20392](https://github.com/RocketChat/Rocket.Chat/pull/20392)) - Remove: - - - react-window - - - react-window-infinite-loader - - - simplebar-react - - Include: - - - react-virtuoso - + Remove: + - react-window + - react-window-infinite-loader + - simplebar-react + + Include: + - react-virtuoso - rc-scrollbars - Rewrite Call as React component ([#19778](https://github.com/RocketChat/Rocket.Chat/pull/19778)) @@ -4201,13 +4123,13 @@ - Add debouncing to add users search field. ([#20297](https://github.com/RocketChat/Rocket.Chat/pull/20297) by [@Darshilp326](https://github.com/Darshilp326)) - BEFORE - - https://user-images.githubusercontent.com/55157259/105350722-98a3c080-5c11-11eb-82f3-d9a62a4fa50b.mp4 - - - AFTER - + BEFORE + + https://user-images.githubusercontent.com/55157259/105350722-98a3c080-5c11-11eb-82f3-d9a62a4fa50b.mp4 + + + AFTER + https://user-images.githubusercontent.com/55157259/105350757-a2c5bf00-5c11-11eb-91db-25c0b9e01a28.mp4 - Add tooltips to Thread header buttons ([#20456](https://github.com/RocketChat/Rocket.Chat/pull/20456) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) @@ -4220,8 +4142,8 @@ - Added check for view admin permission page ([#20403](https://github.com/RocketChat/Rocket.Chat/pull/20403) by [@yash-rajpal](https://github.com/yash-rajpal)) - Admin Permission page was visible to all, if you add admin/permissions after the base url. This should not be visible to all user, only people with certain permissions should be able to see this page. - I am also able to see permissions page for open workspace of Rocket chat. + Admin Permission page was visible to all, if you add admin/permissions after the base url. This should not be visible to all user, only people with certain permissions should be able to see this page. + I am also able to see permissions page for open workspace of Rocket chat. ![image](https://user-images.githubusercontent.com/58601732/105829728-bfd00880-5fea-11eb-9121-6c53a752f140.png) - Adding the accidentally deleted tag template, used by other templates ([#20772](https://github.com/RocketChat/Rocket.Chat/pull/20772) by [@yash-rajpal](https://github.com/yash-rajpal)) @@ -4230,8 +4152,8 @@ - Admin cannot clear user details like bio or nickname ([#20785](https://github.com/RocketChat/Rocket.Chat/pull/20785) by [@yash-rajpal](https://github.com/yash-rajpal)) - When the API users.update is called to update user data, it passes data to saveUser function. Here before saving data like bio or nickname we are checking if they are available or not. If data is available then we are saving it, but we are not doing anything when data isn't available. - + When the API users.update is called to update user data, it passes data to saveUser function. Here before saving data like bio or nickname we are checking if they are available or not. If data is available then we are saving it, but we are not doing anything when data isn't available. + So unsetting data if data isn't available to save. Will also fix bio and other fields. :) - Admin Panel pages not visible in Safari ([#20912](https://github.com/RocketChat/Rocket.Chat/pull/20912)) @@ -4248,24 +4170,24 @@ - Blank Personal Access Token Bug ([#20193](https://github.com/RocketChat/Rocket.Chat/pull/20193) by [@RonLek](https://github.com/RonLek)) - Adds error when personal access token is blank thereby disallowing the creation of one. - + Adds error when personal access token is blank thereby disallowing the creation of one. + https://user-images.githubusercontent.com/28918901/104483631-5adde100-55ee-11eb-9938-64146bce127e.mp4 - CAS login failing due to TOTP requirement ([#20840](https://github.com/RocketChat/Rocket.Chat/pull/20840)) - Changed password input field for password access in edit room info. ([#20356](https://github.com/RocketChat/Rocket.Chat/pull/20356) by [@Darshilp326](https://github.com/Darshilp326)) - Password field would be secured with asterisks in edit room info - - https://user-images.githubusercontent.com/55157259/105641758-cad04f00-5eab-11eb-90de-0c91263edd55.mp4 - + Password field would be secured with asterisks in edit room info + + https://user-images.githubusercontent.com/55157259/105641758-cad04f00-5eab-11eb-90de-0c91263edd55.mp4 + . - Channel mentions showing user subscribed channels twice ([#20484](https://github.com/RocketChat/Rocket.Chat/pull/20484) by [@Darshilp326](https://github.com/Darshilp326)) - Channel mention shows user subscribed channels twice. - + Channel mention shows user subscribed channels twice. + https://user-images.githubusercontent.com/55157259/106183033-b353d780-61c5-11eb-8aab-1dbb62b02ff8.mp4 - CORS config not accepting multiple origins ([#20696](https://github.com/RocketChat/Rocket.Chat/pull/20696) by [@g-thome](https://github.com/g-thome)) @@ -4276,26 +4198,26 @@ - Default Attachments - Remove Extra Margin in Field Attachments ([#20618](https://github.com/RocketChat/Rocket.Chat/pull/20618) by [@aditya-mitra](https://github.com/aditya-mitra)) - A large amount of unnecessary margin which existed in the **Field Attachments inside the `DefaultAttachments`** has been fixed. - - ### Earlier - - ![earlier](https://user-images.githubusercontent.com/55396651/107056792-ba4b9d00-67f8-11eb-9153-05281416cddb.png) - - ### Now - + A large amount of unnecessary margin which existed in the **Field Attachments inside the `DefaultAttachments`** has been fixed. + + ### Earlier + + ![earlier](https://user-images.githubusercontent.com/55396651/107056792-ba4b9d00-67f8-11eb-9153-05281416cddb.png) + + ### Now + ![now](https://user-images.githubusercontent.com/55396651/107057196-3219c780-67f9-11eb-84db-e4a0addfc168.png) - Default Attachments - Show Full Attachment.Text with Markdown ([#20606](https://github.com/RocketChat/Rocket.Chat/pull/20606) by [@aditya-mitra](https://github.com/aditya-mitra)) - Removed truncating of text in `Attachment.Text`. - Added `Attachment.Text` to be parsed to markdown by default. - - ### Earlier - ![earlier](https://user-images.githubusercontent.com/55396651/106910781-92d8cf80-6727-11eb-82ec-818df7544ff0.png) - - ### Now - + Removed truncating of text in `Attachment.Text`. + Added `Attachment.Text` to be parsed to markdown by default. + + ### Earlier + ![earlier](https://user-images.githubusercontent.com/55396651/106910781-92d8cf80-6727-11eb-82ec-818df7544ff0.png) + + ### Now + ![now](https://user-images.githubusercontent.com/55396651/106910840-a126eb80-6727-11eb-8bd6-d86383dd9181.png) - Don't ask again not rendering ([#20745](https://github.com/RocketChat/Rocket.Chat/pull/20745)) @@ -4316,21 +4238,21 @@ - Feedback on bulk invite ([#20339](https://github.com/RocketChat/Rocket.Chat/pull/20339) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) - Resolved structure where no response was being received. Changed from callback to async/await. - Added error in case of empty submission, or if no valid emails were found. - + Resolved structure where no response was being received. Changed from callback to async/await. + Added error in case of empty submission, or if no valid emails were found. + https://user-images.githubusercontent.com/38764067/105613964-dfe5a900-5deb-11eb-80f2-21fc8dee57c0.mp4 - Filters are not being applied correctly in Omnichannel Current Chats list ([#20320](https://github.com/RocketChat/Rocket.Chat/pull/20320) by [@rafaelblink](https://github.com/rafaelblink)) - ### Before - ![image](https://user-images.githubusercontent.com/2493803/105537672-082cb500-5cd1-11eb-8f1b-1726ba60420a.png) - - ### After - ![image](https://user-images.githubusercontent.com/2493803/105537773-2d212800-5cd1-11eb-8746-048deb9502d9.png) - - ![image](https://user-images.githubusercontent.com/2493803/106494728-88090b00-6499-11eb-922e-5386107e2389.png) - + ### Before + ![image](https://user-images.githubusercontent.com/2493803/105537672-082cb500-5cd1-11eb-8f1b-1726ba60420a.png) + + ### After + ![image](https://user-images.githubusercontent.com/2493803/105537773-2d212800-5cd1-11eb-8746-048deb9502d9.png) + + ![image](https://user-images.githubusercontent.com/2493803/106494728-88090b00-6499-11eb-922e-5386107e2389.png) + ![image](https://user-images.githubusercontent.com/2493803/106494751-90f9dc80-6499-11eb-901b-5e4dbdc678ba.png) - Fix Empty highlighted words field ([#20329](https://github.com/RocketChat/Rocket.Chat/pull/20329) by [@yash-rajpal](https://github.com/yash-rajpal)) @@ -4359,11 +4281,11 @@ - List of Omnichannel triggers is not listing data ([#20624](https://github.com/RocketChat/Rocket.Chat/pull/20624) by [@rafaelblink](https://github.com/rafaelblink)) - ### Before - ![image](https://user-images.githubusercontent.com/2493803/107095379-7308e080-67e7-11eb-8251-7e7ff891087a.png) - - - ### After + ### Before + ![image](https://user-images.githubusercontent.com/2493803/107095379-7308e080-67e7-11eb-8251-7e7ff891087a.png) + + + ### After ![image](https://user-images.githubusercontent.com/2493803/107095261-3b019d80-67e7-11eb-8425-8612b03ac50a.png) - Livechat bridge permission checkers ([#20653](https://github.com/RocketChat/Rocket.Chat/pull/20653) by [@lolimay](https://github.com/lolimay)) @@ -4386,8 +4308,7 @@ - Missing setting to control when to send the ReplyTo field in email notifications ([#20744](https://github.com/RocketChat/Rocket.Chat/pull/20744)) - - Add a new setting ("Add Reply-To header") in the Email settings' page to control when the Reply-To header is used in e-mail notifications; - + - Add a new setting ("Add Reply-To header") in the Email settings' page to control when the Reply-To header is used in e-mail notifications; - The new setting is turned off (`false` value) by default. - New Integration page was not being displayed ([#20670](https://github.com/RocketChat/Rocket.Chat/pull/20670) by [@yash-rajpal](https://github.com/yash-rajpal)) @@ -4420,15 +4341,15 @@ - Remove duplicate getCommonRoomEvents() event binding for starredMessages ([#20185](https://github.com/RocketChat/Rocket.Chat/pull/20185) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) - The getCommonRoomEvents() returned functions were bound to the starredMessages template twice. This was causing some bugs, as detailed in the Issue mentioned below. + The getCommonRoomEvents() returned functions were bound to the starredMessages template twice. This was causing some bugs, as detailed in the Issue mentioned below. I removed the top events call that only bound the getCommonRoomEvents(). Therefore, only one call for the same is left, which is at the end of the file. Having the events bound just once removes the bugs mentioned. - Remove warning problems from console ([#20800](https://github.com/RocketChat/Rocket.Chat/pull/20800)) - Removed tooltip in kebab menu options. ([#20498](https://github.com/RocketChat/Rocket.Chat/pull/20498) by [@Darshilp326](https://github.com/Darshilp326)) - Removed tooltip as it was not needed. - + Removed tooltip as it was not needed. + https://user-images.githubusercontent.com/55157259/106246146-a53ca000-6233-11eb-9874-cbd1b4331bc0.mp4 - Retry icon comes out of the div ([#20390](https://github.com/RocketChat/Rocket.Chat/pull/20390) by [@im-adithya](https://github.com/im-adithya)) @@ -4443,8 +4364,8 @@ - Room's last message's update date format on IE ([#20680](https://github.com/RocketChat/Rocket.Chat/pull/20680)) - The proposed change fixes a bug when updates the cached records on Internet Explorer and it breaks the sidebar as shown on the screenshot below: - + The proposed change fixes a bug when updates the cached records on Internet Explorer and it breaks the sidebar as shown on the screenshot below: + ![image](https://user-images.githubusercontent.com/27704687/107578007-f2285b00-6bd1-11eb-9250-1e76ae67f9c9.png) - Save user password and email from My Account ([#20737](https://github.com/RocketChat/Rocket.Chat/pull/20737)) @@ -4453,8 +4374,8 @@ - Selected hide system messages would now be viewed in vertical bar. ([#20358](https://github.com/RocketChat/Rocket.Chat/pull/20358) by [@Darshilp326](https://github.com/Darshilp326)) - All selected hide system messages are now in vertical Bar. - + All selected hide system messages are now in vertical Bar. + https://user-images.githubusercontent.com/55157259/105642624-d5411780-5eb0-11eb-8848-93e4b02629cb.mp4 - Selected messages don't get unselected ([#20408](https://github.com/RocketChat/Rocket.Chat/pull/20408) by [@im-adithya](https://github.com/im-adithya)) @@ -4469,22 +4390,14 @@ - Several Slack Importer issues ([#20216](https://github.com/RocketChat/Rocket.Chat/pull/20216)) - - Fix: Slack Importer crashes when importing a large users.json file - - - Fix: Slack importer crashes when messages have invalid mentions - - - Skip listing all users on the preparation screen when the user count is too large. - - - Split avatar download into a separate process. - - - Update room's last message when the import is complete. - - - Prevent invalid or duplicated channel names - - - Improve message error handling. - - - Reduce max allowed BSON size to avoid possible issues in some servers. - + - Fix: Slack Importer crashes when importing a large users.json file + - Fix: Slack importer crashes when messages have invalid mentions + - Skip listing all users on the preparation screen when the user count is too large. + - Split avatar download into a separate process. + - Update room's last message when the import is complete. + - Prevent invalid or duplicated channel names + - Improve message error handling. + - Reduce max allowed BSON size to avoid possible issues in some servers. - Improve handling of very large channel files. - star icon was visible after unstarring a message ([#19645](https://github.com/RocketChat/Rocket.Chat/pull/19645) by [@bhavayAnand9](https://github.com/bhavayAnand9)) @@ -4503,15 +4416,15 @@ - User statuses in admin user info panel ([#20341](https://github.com/RocketChat/Rocket.Chat/pull/20341) by [@RonLek](https://github.com/RonLek)) - Modifies user statuses in admin info panel based on their actual status instead of their `statusConnection`. This enables correct and consistent change in user statuses. - Also, bot users having status as online were classified as offline, with this change they are now correctly classified based on their corresponding statuses. - + Modifies user statuses in admin info panel based on their actual status instead of their `statusConnection`. This enables correct and consistent change in user statuses. + Also, bot users having status as online were classified as offline, with this change they are now correctly classified based on their corresponding statuses. + https://user-images.githubusercontent.com/28918901/105624438-b8bcc500-5e47-11eb-8d1e-3a4180da1304.mp4 - Users autocomplete showing duplicated results ([#20481](https://github.com/RocketChat/Rocket.Chat/pull/20481) by [@Darshilp326](https://github.com/Darshilp326)) - Added new query for outside room users so that room members are not shown twice. - + Added new query for outside room users so that room members are not shown twice. + https://user-images.githubusercontent.com/55157259/106174582-33c10b00-61bb-11eb-9716-377ef7bba34e.mp4
@@ -4532,7 +4445,7 @@ - Chore: Disable Sessions Aggregates tests locally ([#20607](https://github.com/RocketChat/Rocket.Chat/pull/20607)) - Disable Session aggregates tests in local environments + Disable Session aggregates tests in local environments For context, refer to: #20161 - Chore: Improve performance of messages’ watcher ([#20519](https://github.com/RocketChat/Rocket.Chat/pull/20519)) @@ -4741,20 +4654,18 @@ - **ENTERPRISE:** Omnichannel Contact Manager as preferred agent for routing ([#20244](https://github.com/RocketChat/Rocket.Chat/pull/20244)) - If the `Contact-Manager` is assigned to a Visitor, the chat will automatically get transferred to the respective Contact-Manager, provided the Contact-Manager is online. In-case the Contact-Manager is offline, the chat will be transferred to any other online agent. - We have provided a setting to control this auto-assignment feature - ![image](https://user-images.githubusercontent.com/34130764/104880961-8104d780-5986-11eb-9d87-82b99814b028.png) - - Behavior based-on Routing method - - - 1. Auto-selection, Load-Balancing, or External Service (`autoAssignAgent = true`) - This is straightforward, - - if the Contact-manager is online, the chat will be transferred to the Contact-Manger only - - if the Contact-manager is offline, the chat will be transferred to any other online-agent based on the Routing system - - 2. Manual-selection (`autoAssignAgent = false`) - - If the Contact-Manager is online, the chat will appear in the Queue of Contact-Manager **ONLY** + If the `Contact-Manager` is assigned to a Visitor, the chat will automatically get transferred to the respective Contact-Manager, provided the Contact-Manager is online. In-case the Contact-Manager is offline, the chat will be transferred to any other online agent. + We have provided a setting to control this auto-assignment feature + ![image](https://user-images.githubusercontent.com/34130764/104880961-8104d780-5986-11eb-9d87-82b99814b028.png) + + Behavior based-on Routing method + + 1. Auto-selection, Load-Balancing, or External Service (`autoAssignAgent = true`) + This is straightforward, + - if the Contact-manager is online, the chat will be transferred to the Contact-Manger only + - if the Contact-manager is offline, the chat will be transferred to any other online-agent based on the Routing system + 2. Manual-selection (`autoAssignAgent = false`) + - If the Contact-Manager is online, the chat will appear in the Queue of Contact-Manager **ONLY** - If the Contact-Manager is offline, the chat will appear in the Queue of all related Agents/Manager ( like it's done right now ) - Banner system and NPS ([#20221](https://github.com/RocketChat/Rocket.Chat/pull/20221)) @@ -4763,34 +4674,34 @@ - Email Inboxes for Omnichannel ([#20101](https://github.com/RocketChat/Rocket.Chat/pull/20101) by [@rafaelblink](https://github.com/rafaelblink)) - With this new feature, email accounts will receive email messages(threads) which will be transformed into Omnichannel chats. It'll be possible to set up multiple email accounts, test the connection with email server(email provider) and define the behaviour of each account. - - https://user-images.githubusercontent.com/2493803/105430398-242d4980-5c32-11eb-835a-450c94837d23.mp4 - - ### New item on admin menu - - ![image](https://user-images.githubusercontent.com/2493803/105428723-bc293400-5c2e-11eb-8c02-e8d36ea82726.png) - - - ### Send test email tooltip - - ![image](https://user-images.githubusercontent.com/2493803/104366986-eaa16380-54f8-11eb-9ba7-831cfde2319c.png) - - - ### Inbox Info - - ![image](https://user-images.githubusercontent.com/2493803/104366796-ab731280-54f8-11eb-9941-a3cc8eb610e1.png) - - ### SMTP Info - - ![image](https://user-images.githubusercontent.com/2493803/104366868-c47bc380-54f8-11eb-969e-ccc29070957c.png) - - ### IMAP Info - - ![image](https://user-images.githubusercontent.com/2493803/104366897-cd6c9500-54f8-11eb-80c4-97d5b0c002d5.png) - - ### Messages - + With this new feature, email accounts will receive email messages(threads) which will be transformed into Omnichannel chats. It'll be possible to set up multiple email accounts, test the connection with email server(email provider) and define the behaviour of each account. + + https://user-images.githubusercontent.com/2493803/105430398-242d4980-5c32-11eb-835a-450c94837d23.mp4 + + ### New item on admin menu + + ![image](https://user-images.githubusercontent.com/2493803/105428723-bc293400-5c2e-11eb-8c02-e8d36ea82726.png) + + + ### Send test email tooltip + + ![image](https://user-images.githubusercontent.com/2493803/104366986-eaa16380-54f8-11eb-9ba7-831cfde2319c.png) + + + ### Inbox Info + + ![image](https://user-images.githubusercontent.com/2493803/104366796-ab731280-54f8-11eb-9941-a3cc8eb610e1.png) + + ### SMTP Info + + ![image](https://user-images.githubusercontent.com/2493803/104366868-c47bc380-54f8-11eb-969e-ccc29070957c.png) + + ### IMAP Info + + ![image](https://user-images.githubusercontent.com/2493803/104366897-cd6c9500-54f8-11eb-80c4-97d5b0c002d5.png) + + ### Messages + ![image](https://user-images.githubusercontent.com/2493803/105428971-45d90180-5c2f-11eb-992a-022a3df94471.png) - Encrypted Discussions and new Encryption Permissions ([#20201](https://github.com/RocketChat/Rocket.Chat/pull/20201)) @@ -4802,7 +4713,7 @@ - Add extra SAML settings to update room subs and add private room subs. ([#19489](https://github.com/RocketChat/Rocket.Chat/pull/19489) by [@tlskinneriv](https://github.com/tlskinneriv)) - Added a SAML setting to support updating room subscriptions each time a user logs in via SAML. + Added a SAML setting to support updating room subscriptions each time a user logs in via SAML. Added a SAML setting to support including private rooms in SAML updated subscriptions (whether initial or on each logon). - Autofocus on directory ([#20509](https://github.com/RocketChat/Rocket.Chat/pull/20509)) @@ -4829,7 +4740,7 @@ - Tooltip added for Kebab menu on chat header ([#20116](https://github.com/RocketChat/Rocket.Chat/pull/20116) by [@yash-rajpal](https://github.com/yash-rajpal)) - Added the missing Tooltip for kebab menu on chat header. + Added the missing Tooltip for kebab menu on chat header. ![tooltip after](https://user-images.githubusercontent.com/58601732/104031406-b07f4b80-51f2-11eb-87a4-1e8da78a254f.gif) ### 🐛 Bug fixes @@ -4851,7 +4762,7 @@ - Added context check for closing active tabbar for member-list ([#20228](https://github.com/RocketChat/Rocket.Chat/pull/20228) by [@yash-rajpal](https://github.com/yash-rajpal)) - When we click on a username and then click on see user's full profile, a tab gets active and shows us the user's profile, the problem occurs when the tab is still active and we try to see another user's profile. In this case, tabbar gets closed. + When we click on a username and then click on see user's full profile, a tab gets active and shows us the user's profile, the problem occurs when the tab is still active and we try to see another user's profile. In this case, tabbar gets closed. To resolve this, added context check for closing action of active tabbar. - Added Margin between status bullet and status label ([#20199](https://github.com/RocketChat/Rocket.Chat/pull/20199) by [@yash-rajpal](https://github.com/yash-rajpal)) @@ -4860,8 +4771,8 @@ - Added success message on saving notification preference. ([#20220](https://github.com/RocketChat/Rocket.Chat/pull/20220) by [@Darshilp326](https://github.com/Darshilp326)) - Added success message after saving notification preferences. - + Added success message after saving notification preferences. + https://user-images.githubusercontent.com/55157259/104774617-03ca3e80-579d-11eb-8fa4-990b108dd8d9.mp4 - Admin User Info email verified status ([#20110](https://github.com/RocketChat/Rocket.Chat/pull/20110) by [@bdelwood](https://github.com/bdelwood)) @@ -4870,10 +4781,10 @@ - Change header's favorite icon to filled star ([#20174](https://github.com/RocketChat/Rocket.Chat/pull/20174)) - ### Before: - ![image](https://user-images.githubusercontent.com/27704687/104351819-a60bcd00-54e4-11eb-8b43-7d281a6e5dcb.png) - - ### After: + ### Before: + ![image](https://user-images.githubusercontent.com/27704687/104351819-a60bcd00-54e4-11eb-8b43-7d281a6e5dcb.png) + + ### After: ![image](https://user-images.githubusercontent.com/27704687/104351632-67761280-54e4-11eb-87ba-25b940494bb5.png) - Changed success message for adding custom sound. ([#20272](https://github.com/RocketChat/Rocket.Chat/pull/20272) by [@Darshilp326](https://github.com/Darshilp326)) @@ -4882,24 +4793,24 @@ - Changed success message for ignoring member. ([#19996](https://github.com/RocketChat/Rocket.Chat/pull/19996) by [@Darshilp326](https://github.com/Darshilp326)) - Different messages for ignoring/unignoring will be displayed. - + Different messages for ignoring/unignoring will be displayed. + https://user-images.githubusercontent.com/55157259/103310307-4241c880-4a3d-11eb-8c6c-4c9b99d023db.mp4 - Creation of Omnichannel rooms not working correctly through the Apps when the agent parameter is set ([#19997](https://github.com/RocketChat/Rocket.Chat/pull/19997)) - Engagement dashboard graphs labels superposing each other ([#20267](https://github.com/RocketChat/Rocket.Chat/pull/20267)) - Now after a certain breakpoint, the graphs should stack vertically, and overlapping text rotated. - + Now after a certain breakpoint, the graphs should stack vertically, and overlapping text rotated. + ![image](https://user-images.githubusercontent.com/40830821/105098926-93b40500-5a89-11eb-9a56-2fc3b1552914.png) - Fields overflowing page ([#20287](https://github.com/RocketChat/Rocket.Chat/pull/20287)) - ### Before - ![image](https://user-images.githubusercontent.com/40830821/105246952-c1b14c00-5b52-11eb-8671-cff88edf242d.png) - - ### After + ### Before + ![image](https://user-images.githubusercontent.com/40830821/105246952-c1b14c00-5b52-11eb-8671-cff88edf242d.png) + + ### After ![image](https://user-images.githubusercontent.com/40830821/105247125-0a690500-5b53-11eb-9f3c-d6a68108e336.png) - Fix error that occurs on changing archive status of room ([#20098](https://github.com/RocketChat/Rocket.Chat/pull/20098) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) @@ -4916,7 +4827,7 @@ - Livechat.RegisterGuest method removing unset fields ([#20124](https://github.com/RocketChat/Rocket.Chat/pull/20124) by [@rafaelblink](https://github.com/rafaelblink)) - After changes made on https://github.com/RocketChat/Rocket.Chat/pull/19931, the `Livechat.RegisterGuest` method started removing properties from the visitor inappropriately. The properties that did not receive value were removed from the object. + After changes made on https://github.com/RocketChat/Rocket.Chat/pull/19931, the `Livechat.RegisterGuest` method started removing properties from the visitor inappropriately. The properties that did not receive value were removed from the object. Those changes were made to support the new Contact Form, but now the form has its own method to deal with Contact data so those changes are no longer necessary. - Markdown added for Header Room topic ([#20021](https://github.com/RocketChat/Rocket.Chat/pull/20021) by [@yash-rajpal](https://github.com/yash-rajpal)) @@ -4937,18 +4848,18 @@ - Omnichannel - Contact Center form is not validating custom fields properly ([#20196](https://github.com/RocketChat/Rocket.Chat/pull/20196) by [@rafaelblink](https://github.com/rafaelblink)) - The contact form is accepting undefined values in required custom fields when creating or editing contacts, and, the errror message isn't following Rocket.chat design system. - - ### Before - ![image](https://user-images.githubusercontent.com/2493803/104522668-31688980-55dd-11eb-92c5-83f96073edc4.png) - - ### After - - #### New - ![image](https://user-images.githubusercontent.com/2493803/104770494-68f74300-574f-11eb-94a3-c8fd73365308.png) - - - #### Edit + The contact form is accepting undefined values in required custom fields when creating or editing contacts, and, the errror message isn't following Rocket.chat design system. + + ### Before + ![image](https://user-images.githubusercontent.com/2493803/104522668-31688980-55dd-11eb-92c5-83f96073edc4.png) + + ### After + + #### New + ![image](https://user-images.githubusercontent.com/2493803/104770494-68f74300-574f-11eb-94a3-c8fd73365308.png) + + + #### Edit ![image](https://user-images.githubusercontent.com/2493803/104770538-7b717c80-574f-11eb-829f-1ae304103369.png) - Omnichannel Agents unable to take new chats in the queue ([#20022](https://github.com/RocketChat/Rocket.Chat/pull/20022) by [@rafaelblink](https://github.com/rafaelblink)) @@ -4969,15 +4880,15 @@ - Room special name in prompts ([#20277](https://github.com/RocketChat/Rocket.Chat/pull/20277) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) - The "Hide room" and "Leave Room" confirmation prompts use the "name" key from the room info. When the setting " - Allow Special Characters in Room Names" is enabled, the prompts show the normalized names instead of those that contain the special characters. - - Changed the value being used from name to fname, which always has the user-set name. - - Previous: - ![Screenshot from 2021-01-20 15-52-29](https://user-images.githubusercontent.com/38764067/105161642-9b31e780-5b37-11eb-8b0c-ec4b1414c948.png) - - Updated: + The "Hide room" and "Leave Room" confirmation prompts use the "name" key from the room info. When the setting " + Allow Special Characters in Room Names" is enabled, the prompts show the normalized names instead of those that contain the special characters. + + Changed the value being used from name to fname, which always has the user-set name. + + Previous: + ![Screenshot from 2021-01-20 15-52-29](https://user-images.githubusercontent.com/38764067/105161642-9b31e780-5b37-11eb-8b0c-ec4b1414c948.png) + + Updated: ![Screenshot from 2021-01-20 15-50-19](https://user-images.githubusercontent.com/38764067/105161627-966d3380-5b37-11eb-9812-3dd9352b4f95.png) - Room's list showing all rooms with same name ([#20176](https://github.com/RocketChat/Rocket.Chat/pull/20176)) @@ -4988,9 +4899,9 @@ - Saving with blank email in edit user ([#20259](https://github.com/RocketChat/Rocket.Chat/pull/20259) by [@RonLek](https://github.com/RonLek)) - Disallows showing a success popup when email field is made blank in Edit User and instead shows the relevant error popup. - - + Disallows showing a success popup when email field is made blank in Edit User and instead shows the relevant error popup. + + https://user-images.githubusercontent.com/28918901/104960749-dbd81680-59fa-11eb-9c7b-2b257936f894.mp4 - Search list filter ([#19937](https://github.com/RocketChat/Rocket.Chat/pull/19937)) @@ -5037,7 +4948,7 @@ - Add translation of Edit Status in all languages ([#19916](https://github.com/RocketChat/Rocket.Chat/pull/19916) by [@sushant52](https://github.com/sushant52)) - Closes [#19915](https://github.com/RocketChat/Rocket.Chat/issues/19915) + Closes [#19915](https://github.com/RocketChat/Rocket.Chat/issues/19915) The profile options menu is well translated in many languages. However, Edit Status is the only button which is not well translated. With this change, the whole profile options will be properly translated in a lot of languages. - Bump axios from 0.18.0 to 0.18.1 ([#20055](https://github.com/RocketChat/Rocket.Chat/pull/20055) by [@dependabot[bot]](https://github.com/dependabot[bot])) @@ -5072,10 +4983,10 @@ - Regression: Announcement bar not showing properly Markdown content ([#20290](https://github.com/RocketChat/Rocket.Chat/pull/20290)) - **Before**: - ![image](https://user-images.githubusercontent.com/27704687/105273746-a4907380-5b7a-11eb-8121-aff665251c44.png) - - **After**: + **Before**: + ![image](https://user-images.githubusercontent.com/27704687/105273746-a4907380-5b7a-11eb-8121-aff665251c44.png) + + **After**: ![image](https://user-images.githubusercontent.com/27704687/105274050-2e404100-5b7b-11eb-93b2-b6282a7bed95.png) - regression: Announcement link open in new tab ([#20435](https://github.com/RocketChat/Rocket.Chat/pull/20435)) @@ -5090,23 +5001,23 @@ - Regression: Change sort icon ([#20177](https://github.com/RocketChat/Rocket.Chat/pull/20177)) - ### Before - ![image](https://user-images.githubusercontent.com/40830821/104366414-1bcd6400-54f8-11eb-9fc7-c6f13f07a61e.png) - - ### After + ### Before + ![image](https://user-images.githubusercontent.com/40830821/104366414-1bcd6400-54f8-11eb-9fc7-c6f13f07a61e.png) + + ### After ![image](https://user-images.githubusercontent.com/40830821/104366542-4cad9900-54f8-11eb-83ca-acb99899515a.png) - Regression: Custom field labels are not displayed properly on Omnichannel Contact Profile form ([#20393](https://github.com/RocketChat/Rocket.Chat/pull/20393) by [@rafaelblink](https://github.com/rafaelblink)) - ### Before - ![image](https://user-images.githubusercontent.com/2493803/105780399-20116c80-5f4f-11eb-9620-0901472e453b.png) - - ![image](https://user-images.githubusercontent.com/2493803/105780420-2e5f8880-5f4f-11eb-8e93-8115ebc685be.png) - - ### After - - ![image](https://user-images.githubusercontent.com/2493803/105780832-1ccab080-5f50-11eb-8042-188dd0c41904.png) - + ### Before + ![image](https://user-images.githubusercontent.com/2493803/105780399-20116c80-5f4f-11eb-9620-0901472e453b.png) + + ![image](https://user-images.githubusercontent.com/2493803/105780420-2e5f8880-5f4f-11eb-8e93-8115ebc685be.png) + + ### After + + ![image](https://user-images.githubusercontent.com/2493803/105780832-1ccab080-5f50-11eb-8042-188dd0c41904.png) + ![image](https://user-images.githubusercontent.com/2493803/105780911-500d3f80-5f50-11eb-96e0-7df3f179dbd5.png) - Regression: ESLint Warning - explicit-function-return-type ([#20434](https://github.com/RocketChat/Rocket.Chat/pull/20434) by [@aditya-mitra](https://github.com/aditya-mitra)) @@ -5123,8 +5034,8 @@ - Regression: Fixed update room avatar issue. ([#20433](https://github.com/RocketChat/Rocket.Chat/pull/20433) by [@Darshilp326](https://github.com/Darshilp326)) - Users can now update their room avatar without any error. - + Users can now update their room avatar without any error. + https://user-images.githubusercontent.com/55157259/105951602-560d3880-6096-11eb-97a5-b5eb9a28b58d.mp4 - Regression: Info Page Icon style and usage graph breaking ([#20180](https://github.com/RocketChat/Rocket.Chat/pull/20180)) @@ -5141,11 +5052,11 @@ - Regression: Unread superposing announcement. ([#20306](https://github.com/RocketChat/Rocket.Chat/pull/20306)) - ### Before - ![image](https://user-images.githubusercontent.com/40830821/105412619-c2f67d80-5c13-11eb-8204-5932ea880c8a.png) - - - ### After + ### Before + ![image](https://user-images.githubusercontent.com/40830821/105412619-c2f67d80-5c13-11eb-8204-5932ea880c8a.png) + + + ### After ![image](https://user-images.githubusercontent.com/40830821/105411176-d1439a00-5c11-11eb-8d1b-ea27c8485214.png) - Regression: User Dropdown margin ([#20222](https://github.com/RocketChat/Rocket.Chat/pull/20222)) @@ -5433,8 +5344,8 @@ - Hightlights validation on Account Preferences page ([#19902](https://github.com/RocketChat/Rocket.Chat/pull/19902) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) - This PR fixes two issues in the account settings "preferences" panel. - Once set, the "Highlighted Words" setting cannot be reset to an empty string. This was fixed by changing the string validation from checking the length to checking the type of variable. + This PR fixes two issues in the account settings "preferences" panel. + Once set, the "Highlighted Words" setting cannot be reset to an empty string. This was fixed by changing the string validation from checking the length to checking the type of variable. Secondly, it tracks the changes to correctly identify if changes after the last "save changes" action have been made, using an "updates" state variable, instead of just comparing against the initialValue that does not change on clicking "save changes". - Image preview for image URLs on messages ([#19734](https://github.com/RocketChat/Rocket.Chat/pull/19734) by [@g-thome](https://github.com/g-thome)) @@ -5493,14 +5404,10 @@ - Chore: Update Pull Request template ([#19768](https://github.com/RocketChat/Rocket.Chat/pull/19768)) - Improve the template of Pull Requests in order to make it clear reducing duplicated information and removing the visible checklists that were generating noise and misunderstanding with the PR progress. - - - Moved the checklists to inside comments - - - Merge the changelog and proposed changes sections to have a single source of description that goes to the changelog - - - Remove the screenshot section, they can be added inside the description - + Improve the template of Pull Requests in order to make it clear reducing duplicated information and removing the visible checklists that were generating noise and misunderstanding with the PR progress. + - Moved the checklists to inside comments + - Merge the changelog and proposed changes sections to have a single source of description that goes to the changelog + - Remove the screenshot section, they can be added inside the description - Changed the proposed changes title to incentivizing the usage of images and videos - Frontend folder structure ([#19631](https://github.com/RocketChat/Rocket.Chat/pull/19631)) @@ -5535,11 +5442,11 @@ - Regression: Double Scrollbars on tables ([#19980](https://github.com/RocketChat/Rocket.Chat/pull/19980)) - Before: - ![image](https://user-images.githubusercontent.com/40830821/103242719-0ec84680-4936-11eb-87a7-68b6eea8de7b.png) - - - After: + Before: + ![image](https://user-images.githubusercontent.com/40830821/103242719-0ec84680-4936-11eb-87a7-68b6eea8de7b.png) + + + After: ![image](https://user-images.githubusercontent.com/40830821/103242680-ee988780-4935-11eb-99e2-a95de99f78f1.png) - Regression: Failed autolinker and markdown rendering ([#19831](https://github.com/RocketChat/Rocket.Chat/pull/19831)) @@ -5558,7 +5465,7 @@ - Regression: Omnichannel Custom Fields Form no longer working after refactoring ([#19948](https://github.com/RocketChat/Rocket.Chat/pull/19948)) - The Omnichannel `Custom Fields` form is not working anymore after some refactorings on client-side. + The Omnichannel `Custom Fields` form is not working anymore after some refactorings on client-side. When the user clicks on `Custom Field` in the Omnichannel menu, a blank page appears. - Regression: polishing licenses endpoints ([#19981](https://github.com/RocketChat/Rocket.Chat/pull/19981) by [@g-thome](https://github.com/g-thome)) @@ -5757,8 +5664,8 @@ - Bundle Size Client ([#19533](https://github.com/RocketChat/Rocket.Chat/pull/19533)) - temporarily removes some codeblock languages - Moved some libraries to dynamic imports + temporarily removes some codeblock languages + Moved some libraries to dynamic imports Removed some shared code not used on the client side - Forward Omnichannel room to agent in another department ([#19576](https://github.com/RocketChat/Rocket.Chat/pull/19576) by [@mrfigueiredo](https://github.com/mrfigueiredo)) @@ -6839,10 +6746,8 @@ - **2FA:** Password enforcement setting and 2FA protection when saving settings or resetting E2E encryption ([#18640](https://github.com/RocketChat/Rocket.Chat/pull/18640)) - - Increase the 2FA remembering time from 5min to 30min - - - Add new setting to enforce 2FA password fallback (enabled only for new installations) - + - Increase the 2FA remembering time from 5min to 30min + - Add new setting to enforce 2FA password fallback (enabled only for new installations) - Require 2FA to save settings and reset E2E Encryption keys - **Omnichannel:** Allow set other agent status via method `livechat:changeLivechatStatus ` ([#18571](https://github.com/RocketChat/Rocket.Chat/pull/18571)) @@ -6860,7 +6765,7 @@ - 2FA by Email setting showing for the user even when disabled by the admin ([#18473](https://github.com/RocketChat/Rocket.Chat/pull/18473)) - The option to disable/enable the **Two-factor authentication via Email** at `Account > Security > Two Factor Authentication + The option to disable/enable the **Two-factor authentication via Email** at `Account > Security > Two Factor Authentication ` was visible even when the setting **Enable Two Factor Authentication via Email** at `Admin > Accounts > Two Factor Authentication` was disabled leading to misbehavior since the functionality was disabled. - Agents enabledDepartment attribute not set on collection ([#18614](https://github.com/RocketChat/Rocket.Chat/pull/18614) by [@paulobernardoaf](https://github.com/paulobernardoaf)) @@ -7210,16 +7115,13 @@ - Mention autocomplete UI and performance improvements ([#18309](https://github.com/RocketChat/Rocket.Chat/pull/18309)) - * New setting to configure the number of suggestions `Admin > Layout > User Interface > Number of users' autocomplete suggestions` (default 5) - - * The UI shows whenever the user is not a member of the room - - * The UI shows when the suggestion came from the last messages for quick selection/reply - - * The suggestions follow this order: - * The user with the exact username and member of the room - * The user with the exact username but not a member of the room (if allowed to list non-members) - * The users containing the text in username, name or nickname and member of the room + * New setting to configure the number of suggestions `Admin > Layout > User Interface > Number of users' autocomplete suggestions` (default 5) + * The UI shows whenever the user is not a member of the room + * The UI shows when the suggestion came from the last messages for quick selection/reply + * The suggestions follow this order: + * The user with the exact username and member of the room + * The user with the exact username but not a member of the room (if allowed to list non-members) + * The users containing the text in username, name or nickname and member of the room * The users containing the text in username, name or nickname and not a member of the room (if allowed to list non-members) - Message action styles ([#18190](https://github.com/RocketChat/Rocket.Chat/pull/18190)) @@ -7561,10 +7463,10 @@ - Split NOTIFICATIONS_SCHEDULE_DELAY into three separate variables ([#17669](https://github.com/RocketChat/Rocket.Chat/pull/17669) by [@jazztickets](https://github.com/jazztickets)) - Email notification delay can now be customized with the following environment variables: - NOTIFICATIONS_SCHEDULE_DELAY_ONLINE - NOTIFICATIONS_SCHEDULE_DELAY_AWAY - NOTIFICATIONS_SCHEDULE_DELAY_OFFLINE + Email notification delay can now be customized with the following environment variables: + NOTIFICATIONS_SCHEDULE_DELAY_ONLINE + NOTIFICATIONS_SCHEDULE_DELAY_AWAY + NOTIFICATIONS_SCHEDULE_DELAY_OFFLINE Setting the value to -1 disable notifications for that type. - Threads ([#17416](https://github.com/RocketChat/Rocket.Chat/pull/17416)) @@ -7964,11 +7866,11 @@ - **ENTERPRISE:** Omnichannel Last-Chatted Agent Preferred option ([#17666](https://github.com/RocketChat/Rocket.Chat/pull/17666)) - If activated, this feature will store the last agent that assisted each Omnichannel visitor when a conversation is taken. So, when a visitor returns(it works with any entry point, Livechat, Facebook, REST API, and so on) and starts a new chat, the routing system checks: - - 1 - The visitor object for any stored agent that the visitor has previously talked to; - 2 - If a previous agent is not found, the system will try to find a previous conversation of the same visitor. If a room is found, the system will get the previous agent from the room; - + If activated, this feature will store the last agent that assisted each Omnichannel visitor when a conversation is taken. So, when a visitor returns(it works with any entry point, Livechat, Facebook, REST API, and so on) and starts a new chat, the routing system checks: + + 1 - The visitor object for any stored agent that the visitor has previously talked to; + 2 - If a previous agent is not found, the system will try to find a previous conversation of the same visitor. If a room is found, the system will get the previous agent from the room; + After this process, if an agent has been found, the system will check the agent's availability to assist the new chat. If it's not available, then the routing system will get the next available agent in the queue. - **ENTERPRISE:** Support for custom Livechat registration form fields ([#17581](https://github.com/RocketChat/Rocket.Chat/pull/17581)) @@ -8073,12 +7975,9 @@ - Notification sounds ([#17616](https://github.com/RocketChat/Rocket.Chat/pull/17616)) - * Global CDN config was ignored when loading the sound files - - * Upload of custom sounds wasn't getting the file extension correctly - - * Some translations were missing - + * Global CDN config was ignored when loading the sound files + * Upload of custom sounds wasn't getting the file extension correctly + * Some translations were missing * Edit and delete of custom sounds were not working correctly - Omnichannel departments are not saved when the offline channel name is not defined ([#17553](https://github.com/RocketChat/Rocket.Chat/pull/17553)) @@ -8366,19 +8265,14 @@ - Better Push and Email Notification logic ([#17357](https://github.com/RocketChat/Rocket.Chat/pull/17357)) - We are still using the same logic to define which notifications every new message will generate, it takes some servers' settings, users's preferences and subscriptions' settings in consideration to determine who will receive each notification type (desktop, audio, email and mobile push), but now it doesn't check the user's status (online, away, offline) for email and mobile push notifications but send those notifications to a new queue with the following rules: - - - - When the user is online the notification is scheduled to be sent in 120 seconds - - - When the user is away the notification is scheduled to be sent in 120 seconds minus the amount of time he is away - - - When the user is offline the notification is scheduled to be sent right away - - - When the user reads a channel all the notifications for that user are removed (clear queue) - - - When a notification is processed to be sent to a user and there are other scheduled notifications: - - All the scheduled notifications for that user are rescheduled to now + We are still using the same logic to define which notifications every new message will generate, it takes some servers' settings, users's preferences and subscriptions' settings in consideration to determine who will receive each notification type (desktop, audio, email and mobile push), but now it doesn't check the user's status (online, away, offline) for email and mobile push notifications but send those notifications to a new queue with the following rules: + + - When the user is online the notification is scheduled to be sent in 120 seconds + - When the user is away the notification is scheduled to be sent in 120 seconds minus the amount of time he is away + - When the user is offline the notification is scheduled to be sent right away + - When the user reads a channel all the notifications for that user are removed (clear queue) + - When a notification is processed to be sent to a user and there are other scheduled notifications: + - All the scheduled notifications for that user are rescheduled to now - The current notification goes back to the queue to be processed ordered by creation date - Buttons to check/uncheck all users and channels on import ([#17207](https://github.com/RocketChat/Rocket.Chat/pull/17207)) @@ -8741,7 +8635,7 @@ - Translation via MS translate ([#16363](https://github.com/RocketChat/Rocket.Chat/pull/16363) by [@mrsimpson](https://github.com/mrsimpson)) - Adds Microsoft's translation service (https://translator.microsoft.com/) as a provider for translation of messages. + Adds Microsoft's translation service (https://translator.microsoft.com/) as a provider for translation of messages. In addition to implementing the interface (similar to google and DeepL), a small change has been done in order to display the translation provider on the UI. - Two Factor authentication via email ([#15949](https://github.com/RocketChat/Rocket.Chat/pull/15949)) @@ -20280,4 +20174,4 @@ - [@graywolf336](https://github.com/graywolf336) - [@marceloschmidt](https://github.com/marceloschmidt) - [@rodrigok](https://github.com/rodrigok) -- [@sampaiodiego](https://github.com/sampaiodiego) +- [@sampaiodiego](https://github.com/sampaiodiego) \ No newline at end of file diff --git a/app/utils/rocketchat.info b/app/utils/rocketchat.info index 9832fdaa3dc8b..9e93688da8f5a 100644 --- a/app/utils/rocketchat.info +++ b/app/utils/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "4.0.1" + "version": "4.0.2" } diff --git a/package-lock.json b/package-lock.json index 3a22f5a3beefa..52ac3b5b440f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "Rocket.Chat", - "version": "4.0.1", + "version": "4.0.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index a6512913bb1eb..e4b24f8db2a5b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Rocket.Chat", "description": "The Ultimate Open Source WebChat Platform", - "version": "4.0.1", + "version": "4.0.2", "author": { "name": "Rocket.Chat", "url": "https://rocket.chat/" From 536ba339d6d294d49c05ebd9d81664e8280d1aed Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 18 Oct 2021 11:57:23 -0600 Subject: [PATCH 015/137] [FIX] Server crashing when Routing method is not available at start (#23473) * Prevent server crash when routing method is not valid * Change way of starting/stopping queue * Friday... * Fix PR comments * Update app/livechat/server/lib/RoutingManager.js Co-authored-by: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> Co-authored-by: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> --- app/livechat/server/index.js | 2 +- app/livechat/server/lib/RoutingManager.js | 26 +++++++++++++------ app/livechat/server/startup.js | 5 ++++ .../server/lib/LivechatEnterprise.js | 16 +++++++++--- server/main.js | 2 +- 5 files changed, 37 insertions(+), 14 deletions(-) diff --git a/app/livechat/server/index.js b/app/livechat/server/index.js index 4e9dc73677dce..ec823937e7cd0 100644 --- a/app/livechat/server/index.js +++ b/app/livechat/server/index.js @@ -1,9 +1,9 @@ import './livechat'; +import './config'; import './startup'; import './visitorStatus'; import './agentStatus'; import '../lib/messageTypes'; -import './config'; import './roomType'; import './hooks/beforeCloseRoom'; import './hooks/beforeDelegateAgent'; diff --git a/app/livechat/server/lib/RoutingManager.js b/app/livechat/server/lib/RoutingManager.js index c167cd99648ba..8e1fa48927721 100644 --- a/app/livechat/server/lib/RoutingManager.js +++ b/app/livechat/server/lib/RoutingManager.js @@ -1,7 +1,6 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; -import { settings } from '../../../settings/server'; import { createLivechatSubscription, dispatchAgentDelegated, @@ -13,7 +12,7 @@ import { allowAgentSkipQueue, } from './Helper'; import { callbacks } from '../../../callbacks/server'; -import { Logger } from '../../../logger'; +import { Logger } from '../../../../server/lib/logger/Logger'; import { LivechatRooms, Rooms, Messages, Users, LivechatInquiry, Subscriptions } from '../../../models/server'; import { Apps, AppEvents } from '../../../apps/server'; @@ -23,9 +22,24 @@ export const RoutingManager = { methodName: null, methods: {}, - setMethodName(name) { + startQueue() { + // queue shouldn't start on CE + }, + + isMethodSet() { + return !!this.methodName; + }, + + setMethodNameAndStartQueue(name) { logger.debug(`Changing default routing method from ${ this.methodName } to ${ name }`); - this.methodName = name; + if (!this.methods[name]) { + logger.warn(`Cannot change routing method to ${ name }. Selected Routing method does not exists. Defaulting to Manual_Selection`); + this.methodName = 'Manual_Selection'; + } else { + this.methodName = name; + } + + this.startQueue(); }, registerMethod(name, Method) { @@ -217,7 +231,3 @@ export const RoutingManager = { }); }, }; - -settings.get('Livechat_Routing_Method', function(key, value) { - RoutingManager.setMethodName(value); -}); diff --git a/app/livechat/server/startup.js b/app/livechat/server/startup.js index 8cb5261ffd7ff..edbbb6fecf361 100644 --- a/app/livechat/server/startup.js +++ b/app/livechat/server/startup.js @@ -11,6 +11,7 @@ import { businessHourManager } from './business-hour'; import { createDefaultBusinessHourIfNotExists } from './business-hour/Helper'; import { hasPermission } from '../../authorization/server'; import { Livechat } from './lib/Livechat'; +import { RoutingManager } from './lib/RoutingManager'; import './roomAccessValidator.internalService'; @@ -57,5 +58,9 @@ Meteor.startup(async () => { return businessHourManager.stopManager(); }); + settings.get('Livechat_Routing_Method', function(key, value) { + RoutingManager.setMethodNameAndStartQueue(value); + }); + Accounts.onLogout(({ user }) => user?.roles?.includes('livechat-agent') && !user?.roles?.includes('bot') && Livechat.setUserStatusLivechat(user._id, 'not-available')); }); diff --git a/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.js b/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.js index bca34b743ed4d..270a31218c8b5 100644 --- a/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.js +++ b/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.js @@ -259,7 +259,14 @@ const queueWorker = { }, }; +let omnichannelIsEnabled = false; + function shouldQueueStart() { + if (!omnichannelIsEnabled) { + queueWorker.stop(); + return; + } + const routingSupportsAutoAssign = RoutingManager.getConfig().autoAssignAgent; queueLogger.debug(`Routing method ${ RoutingManager.methodName } supports auto assignment: ${ routingSupportsAutoAssign }. ${ routingSupportsAutoAssign @@ -267,11 +274,12 @@ function shouldQueueStart() { : 'Stopping' } queue`); - routingSupportsAutoAssign && settings.get('Livechat_enabled') ? queueWorker.start() : queueWorker.stop(); + routingSupportsAutoAssign ? queueWorker.start() : queueWorker.stop(); } +RoutingManager.startQueue = shouldQueueStart; + settings.get('Livechat_enabled', (_, value) => { - value && settings.get('Livechat_Routing_Method') ? shouldQueueStart() : queueWorker.stop(); + omnichannelIsEnabled = value; + omnichannelIsEnabled && RoutingManager.isMethodSet() ? shouldQueueStart() : queueWorker.stop(); }); - -settings.onload('Livechat_Routing_Method', shouldQueueStart); diff --git a/server/main.js b/server/main.js index b12aae86ad962..cc4bf7b42da70 100644 --- a/server/main.js +++ b/server/main.js @@ -1,11 +1,11 @@ import '../ee/server/broker'; +import './lib/logger/startup'; import './importPackages'; import '../imports/startup/server'; import './services/startup'; import '../ee/server'; -import './lib/logger/startup'; import './lib/pushConfig'; import './startup/migrations'; import './startup/appcache'; From 9631bd136cfe16c33b54970996143a810b90765d Mon Sep 17 00:00:00 2001 From: Thassio Victor Date: Mon, 18 Oct 2021 12:21:57 -0300 Subject: [PATCH 016/137] [FIX][APPS] Communication problem when updating and uninstalling apps in cluster (#23418) * Fix communication problem when updating apps in cluster * Fix app being removed in multi-instances env problem --- app/apps/server/communication/websockets.js | 4 +++- app/apps/server/storage/AppGridFSSourceStorage.ts | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/apps/server/communication/websockets.js b/app/apps/server/communication/websockets.js index f5eadf281d97d..8be3edc38e0e0 100644 --- a/app/apps/server/communication/websockets.js +++ b/app/apps/server/communication/websockets.js @@ -69,7 +69,9 @@ export class AppServerListener { const storageItem = await this.orch.getStorage().retrieveOne(appId); - await this.orch.getManager().update(Buffer.from(storageItem.zip, 'base64')); + const appPackage = await this.orch.getAppSourceStorage().fetch(storageItem); + + await this.orch.getManager().update(appPackage); this.clientStreamer.emitWithoutBroadcast(AppEvents.APP_UPDATED, appId); } diff --git a/app/apps/server/storage/AppGridFSSourceStorage.ts b/app/apps/server/storage/AppGridFSSourceStorage.ts index 9a69b67644043..30d5980f21533 100644 --- a/app/apps/server/storage/AppGridFSSourceStorage.ts +++ b/app/apps/server/storage/AppGridFSSourceStorage.ts @@ -59,6 +59,11 @@ export class AppGridFSSourceStorage extends AppSourceStorage { return new Promise((resolve, reject) => { this.bucket.delete(this.itemToObjectId(item), (error) => { if (error) { + if (error.message.includes('FileNotFound: no file with id')) { + console.warn(`This instance could not remove the ${ item.info.name } app package. If you are running Rocket.Chat in a cluster with multiple instances, possibly other instance removed the package. If this is not the case, it is possible that the file in the database got renamed or removed manually.`); + return resolve(); + } + return reject(error); } From 1249183f9b4fc3aef7c08b9f6510055d8d7e6dd2 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Mon, 18 Oct 2021 18:07:12 -0300 Subject: [PATCH 017/137] Bump version to 4.0.3 --- .docker/Dockerfile.rhel | 2 +- .github/history.json | 34 ++++++++++++++++++++++++++ .snapcraft/resources/prepareRocketChat | 2 +- .snapcraft/snap/snapcraft.yaml | 2 +- HISTORY.md | 24 ++++++++++++++++++ app/utils/rocketchat.info | 2 +- package-lock.json | 2 +- package.json | 2 +- 8 files changed, 64 insertions(+), 6 deletions(-) diff --git a/.docker/Dockerfile.rhel b/.docker/Dockerfile.rhel index e43e3b2b9e03f..123349efe1a05 100644 --- a/.docker/Dockerfile.rhel +++ b/.docker/Dockerfile.rhel @@ -1,6 +1,6 @@ FROM registry.access.redhat.com/ubi8/nodejs-12 -ENV RC_VERSION 4.0.2 +ENV RC_VERSION 4.0.3 MAINTAINER buildmaster@rocket.chat diff --git a/.github/history.json b/.github/history.json index bf48c2f617f92..8c882ffc2cd8a 100644 --- a/.github/history.json +++ b/.github/history.json @@ -66186,6 +66186,40 @@ ] } ] + }, + "4.0.3": { + "node_version": "12.22.1", + "npm_version": "6.14.1", + "apps_engine_version": "1.28.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "23418", + "title": "[FIX][APPS] Communication problem when updating and uninstalling apps in cluster", + "userLogin": "thassiov", + "description": "- Make the hook responsible for receiving app update events inside a cluster fetch the app's package (zip file) in the correct place.\r\n- Also shows a warning message on uninstalls inside a cluster. As there are many servers writing to the same place, some race conditions may occur. This prevents problems related to terminating the process in the middle due to errors being thrown and leaving the server in a faulty state.", + "milestone": "4.0.3", + "contributors": [ + "thassiov" + ] + }, + { + "pr": "23473", + "title": "[FIX] Server crashing when Routing method is not available at start", + "userLogin": "KevLehman", + "milestone": "4.0.3", + "contributors": [ + "KevLehman", + "web-flow" + ] + } + ] } } } \ No newline at end of file diff --git a/.snapcraft/resources/prepareRocketChat b/.snapcraft/resources/prepareRocketChat index 921e8e6e8ecb3..afe64c8a04f75 100755 --- a/.snapcraft/resources/prepareRocketChat +++ b/.snapcraft/resources/prepareRocketChat @@ -1,6 +1,6 @@ #!/bin/bash -curl -SLf "https://releases.rocket.chat/4.0.2/download/" -o rocket.chat.tgz +curl -SLf "https://releases.rocket.chat/4.0.3/download/" -o rocket.chat.tgz tar xf rocket.chat.tgz --strip 1 diff --git a/.snapcraft/snap/snapcraft.yaml b/.snapcraft/snap/snapcraft.yaml index a1a7d2cf2b034..fe717d823b828 100644 --- a/.snapcraft/snap/snapcraft.yaml +++ b/.snapcraft/snap/snapcraft.yaml @@ -7,7 +7,7 @@ # 5. `snapcraft snap` name: rocketchat-server -version: 4.0.2 +version: 4.0.3 summary: Rocket.Chat server description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ confinement: strict diff --git a/HISTORY.md b/HISTORY.md index 77808f2288c73..cf3ad29eab917 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,4 +1,28 @@ +# 4.0.3 +`2021-10-18 · 2 🐛 · 2 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.22.1` +- NPM: `6.14.1` +- MongoDB: `3.6, 4.0, 4.2, 4.4, 5.0` +- Apps-Engine: `1.28.0` + +### 🐛 Bug fixes + + +- **APPS:** Communication problem when updating and uninstalling apps in cluster ([#23418](https://github.com/RocketChat/Rocket.Chat/pull/23418)) + + - Make the hook responsible for receiving app update events inside a cluster fetch the app's package (zip file) in the correct place. + - Also shows a warning message on uninstalls inside a cluster. As there are many servers writing to the same place, some race conditions may occur. This prevents problems related to terminating the process in the middle due to errors being thrown and leaving the server in a faulty state. + +- Server crashing when Routing method is not available at start ([#23473](https://github.com/RocketChat/Rocket.Chat/pull/23473)) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@KevLehman](https://github.com/KevLehman) +- [@thassiov](https://github.com/thassiov) + # 4.0.2 `2021-10-14 · 4 🐛 · 2 👩‍💻👨‍💻` diff --git a/app/utils/rocketchat.info b/app/utils/rocketchat.info index 9e93688da8f5a..692f5a791ffed 100644 --- a/app/utils/rocketchat.info +++ b/app/utils/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "4.0.2" + "version": "4.0.3" } diff --git a/package-lock.json b/package-lock.json index 52ac3b5b440f1..1b0b706860bc6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "Rocket.Chat", - "version": "4.0.2", + "version": "4.0.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index e4b24f8db2a5b..c8a96c5fd3e69 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Rocket.Chat", "description": "The Ultimate Open Source WebChat Platform", - "version": "4.0.2", + "version": "4.0.3", "author": { "name": "Rocket.Chat", "url": "https://rocket.chat/" From adf0d37adad7ba817c1dd9d7c5a5f0551b1426af Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 20 Oct 2021 23:42:32 -0300 Subject: [PATCH 018/137] Bump version to 4.1.0-rc.0 --- .docker/Dockerfile.rhel | 2 +- .github/history.json | 557 +++++++++++++++++++++++++ .snapcraft/resources/prepareRocketChat | 2 +- .snapcraft/snap/snapcraft.yaml | 2 +- HISTORY.md | 242 ++++++++++- app/utils/rocketchat.info | 2 +- package-lock.json | 2 +- package.json | 2 +- 8 files changed, 803 insertions(+), 8 deletions(-) diff --git a/.docker/Dockerfile.rhel b/.docker/Dockerfile.rhel index 468f509afbe80..f6b321bdef533 100644 --- a/.docker/Dockerfile.rhel +++ b/.docker/Dockerfile.rhel @@ -1,6 +1,6 @@ FROM registry.access.redhat.com/ubi8/nodejs-12 -ENV RC_VERSION 4.1.0-develop +ENV RC_VERSION 4.1.0-rc.0 MAINTAINER buildmaster@rocket.chat diff --git a/.github/history.json b/.github/history.json index 92513a4960e69..838ace9ca1d44 100644 --- a/.github/history.json +++ b/.github/history.json @@ -66042,6 +66042,563 @@ "5.0" ], "pull_requests": [] + }, + "4.1.0-rc.0": { + "node_version": "12.22.1", + "npm_version": "6.14.1", + "apps_engine_version": "1.28.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "23524", + "title": "Chore: Fix some TS warnings", + "userLogin": "KevLehman", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "23521", + "title": "[FIX] Delay start of email inbox", + "userLogin": "KevLehman", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "23495", + "title": "Chore: Make omnichannel settings dependent on omnichannel being enabled", + "userLogin": "KevLehman", + "contributors": [ + "KevLehman", + "web-flow", + "sampaiodiego" + ] + }, + { + "pr": "23523", + "title": "Chore: Update Livechat Package", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "23411", + "title": "[FIX] SAML Users' roles being reset to default on login", + "userLogin": "matheusbsilva137", + "description": "- Remove `roles` field update on `insertOrUpdateSAMLUser` function;\r\n- Add SAML `syncRoles` event;", + "milestone": "4.0.4", + "contributors": [ + "matheusbsilva137", + "pierre-lehnen-rc" + ] + }, + { + "pr": "23522", + "title": "[FIX] Queue error handling and unlocking behavior", + "userLogin": "KevLehman", + "milestone": "4.0.4", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "23314", + "title": "[FIX] MONGO_OPTIONS being ignored for oplog connection", + "userLogin": "cuonghuunguyen", + "contributors": [ + "cuonghuunguyen", + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "23392", + "title": "[IMPROVE] Allow Omnichannel to handle huge queues ", + "userLogin": "KevLehman", + "contributors": [ + "KevLehman", + "web-flow" + ] + }, + { + "pr": "23515", + "title": "[IMPROVE] Make Livechat Instructions setting multi-line", + "userLogin": "murtaza98", + "description": "Since now we're supporting markdown text on this field (via this PR - https://github.com/RocketChat/Rocket.Chat.Livechat/pull/648), it would be nice to make this setting multiline so users can have more space to edit the text\r\n![image](https://user-images.githubusercontent.com/34130764/138146712-13e4968b-5312-4d53-b44c-b5699c5e49c1.png)", + "contributors": [ + "murtaza98" + ] + }, + { + "pr": "23505", + "title": "Chore: Improve watch OAuth settings logic", + "userLogin": "ggazzo", + "description": "Just prevent to perform 200 deletions for registers that not even exist", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "23519", + "title": "Regression: Fix enterprise setting validation", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "23514", + "title": "Chore: Ensure all permissions are created up to this point", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "23469", + "title": "[FIX] useEndpointAction replace by useEndpointActionExperimental", + "userLogin": "tiagoevanp", + "contributors": [ + "tiagoevanp" + ] + }, + { + "pr": "23394", + "title": "[FIX] Omni-Webhook's retry mechanism going in infinite loop", + "userLogin": "murtaza98", + "contributors": [ + "murtaza98" + ] + }, + { + "pr": "23511", + "title": "Regression: Fix user typings style", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "23510", + "title": "Chore: Update pino and pino-pretty", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "23506", + "title": "Regression: Prevent Settings Unit Test Error ", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "23486", + "title": "i18n: Language update from LingoHub 🤖 on 2021-10-18Z", + "userLogin": "lingohub[bot]", + "contributors": [ + null, + "KevLehman" + ] + }, + { + "pr": "23376", + "title": "Bump url-parse from 1.4.7 to 1.5.3", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "23172", + "title": "[FIX] Rewrite missing webRTC feature", + "userLogin": "dougfabris", + "contributors": [ + "dougfabris", + "tassoevan" + ] + }, + { + "pr": "23488", + "title": "Chore: Replace `promises` helper", + "userLogin": "tassoevan", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "23210", + "title": "Chore: Startup Time", + "userLogin": "ggazzo", + "description": "The settings logic has been improved as a whole.\r\n\r\nAll the logic to get the data from the env var was confusing.\r\n\r\nSetting default values was tricky to understand.\r\n\r\nEvery time the server booted, all settings were updated and callbacks were called 2x or more (horrible for environments with multiple instances and generating a turbulent startup).\r\n\r\n`Settings.get(......, callback);` was deprecated. We now have better methods for each case.", + "milestone": "4.1.0", + "contributors": [ + "ggazzo", + "sampaiodiego" + ] + }, + { + "pr": "23491", + "title": "Chore: Move `isJSON` helper", + "userLogin": "tassoevan", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "23497", + "title": "Update the community open call link in README", + "userLogin": "Sing-Li", + "contributors": [ + "Sing-Li", + "web-flow", + "geekgonecrazy" + ] + }, + { + "pr": "23490", + "title": "Chore: Move `addMinutesToADate` helper", + "userLogin": "tassoevan", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "23489", + "title": "Chore: Move `isEmail` helper", + "userLogin": "tassoevan", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "23228", + "title": "[FIX] Admins can't update or reset user avatars when the \"Allow User Avatar Change\" setting is off", + "userLogin": "matheusbsilva137", + "description": "- Allow admins (or any other user with the `edit-other-user-avatar` permission) to update or reset user avatars even when the \"Allow User Avatar Change\" setting is off.", + "contributors": [ + "matheusbsilva137", + "web-flow", + "tassoevan" + ] + }, + { + "pr": "23473", + "title": "[FIX] Server crashing when Routing method is not available at start", + "userLogin": "KevLehman", + "milestone": "4.0.3", + "contributors": [ + "KevLehman", + "web-flow" + ] + }, + { + "pr": "22949", + "title": "[FIX] Avoid last admin deactivate itself", + "userLogin": "ostjen", + "description": "Co-authored-by: @Kartik18g", + "contributors": [ + "ostjen", + "web-flow", + null + ] + }, + { + "pr": "23418", + "title": "[FIX][APPS] Communication problem when updating and uninstalling apps in cluster", + "userLogin": "thassiov", + "description": "- Make the hook responsible for receiving app update events inside a cluster fetch the app's package (zip file) in the correct place.\r\n- Also shows a warning message on uninstalls inside a cluster. As there are many servers writing to the same place, some race conditions may occur. This prevents problems related to terminating the process in the middle due to errors being thrown and leaving the server in a faulty state.", + "milestone": "4.0.3", + "contributors": [ + "thassiov" + ] + }, + { + "pr": "23462", + "title": "[FIX] Markdown quote message style", + "userLogin": "tiagoevanp", + "description": "Before:\r\n![image](https://user-images.githubusercontent.com/17487063/137496669-3abecab4-cf90-45cb-8b1b-d9411a5682dd.png)\r\n\r\nAfter:\r\n![image](https://user-images.githubusercontent.com/17487063/137496905-fd727f90-f707-4ec6-8139-ba2eb1a2146e.png)", + "contributors": [ + "tiagoevanp" + ] + }, + { + "pr": "22950", + "title": "[NEW] Stream to get individual presence updates", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego", + "web-flow", + "ggazzo" + ] + }, + { + "pr": "23396", + "title": "[FIX] Prevent starting Omni-Queue if Omnichannel is disabled", + "userLogin": "murtaza98", + "description": "Whenever the Routing system setting changes, and omnichannel is disabled, then we shouldn't start the queue.", + "milestone": "4.0.2", + "contributors": [ + "murtaza98" + ] + }, + { + "pr": "23404", + "title": "[FIX][ENTERPRISE] Omnichannel agent is not leaving the room when a forwarded chat is queued", + "userLogin": "murtaza98", + "milestone": "4.0.2", + "contributors": [ + "murtaza98", + "web-flow" + ] + }, + { + "pr": "23419", + "title": "Chore: Partially migrate 2FA client code to TypeScript", + "userLogin": "tassoevan", + "description": "Additionally, hides `toastr` behind an module to handle UI's toast notifications.", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "23342", + "title": "Chore: clean README", + "userLogin": "AbhJ", + "contributors": [ + "AbhJ", + "web-flow" + ] + }, + { + "pr": "23355", + "title": "Chore: Fixed a Typo in 11-admin.js test", + "userLogin": "badbart", + "contributors": [ + "badbart", + "web-flow" + ] + }, + { + "pr": "23405", + "title": "Chore: Document REST API endpoints (DNS)", + "userLogin": "tassoevan", + "description": "Describes endpoints for DNS on REST API using a JSDoc annotation compatible with OpenAPI spec.", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "23430", + "title": "Chore: Document REST API endpoints (E2E)", + "userLogin": "tassoevan", + "description": "Describes endpoints for end-to-end encryption on REST API using a JSDoc annotation compatible with OpenAPI spec.", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "23428", + "title": "Chore: Document REST API endpoints (Misc)", + "userLogin": "tassoevan", + "description": "Describes miscellaneous endpoints on REST API using a JSDoc annotation compatible with OpenAPI spec.", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "20947", + "title": "[IMPROVE] Add markdown to custom fields in user Info", + "userLogin": "yash-rajpal", + "description": "Added markdown to custom fields to render links", + "contributors": [ + "yash-rajpal", + "dougfabris" + ] + }, + { + "pr": "23393", + "title": "[FIX] user/agent upload not working via Apps Engine after 3.16.0", + "userLogin": "murtaza98", + "description": "Fixes #22974", + "milestone": "4.0.2", + "contributors": [ + "murtaza98" + ] + }, + { + "pr": "23377", + "title": "[FIX] Attachment buttons overlap in mobile view", + "userLogin": "Aman-Maheshwari", + "milestone": "4.0.2", + "contributors": [ + "Aman-Maheshwari" + ] + }, + { + "pr": "23378", + "title": "[FIX] Users' `roles` and `type` being reset to default on LDAP DataSync", + "userLogin": "matheusbsilva137", + "description": "- Update `roles` and `type` fields only if they are specified in the data imported from LDAP (otherwise, no changes are applied).", + "milestone": "4.0.1", + "contributors": [ + "matheusbsilva137", + "sampaiodiego" + ] + }, + { + "pr": "23382", + "title": "[FIX] LDAP not stoping after wrong password", + "userLogin": "rodrigok", + "milestone": "4.0.1", + "contributors": [ + "rodrigok" + ] + }, + { + "pr": "23381", + "title": "[FIX] MongoDB deprecation link", + "userLogin": "sampaiodiego", + "milestone": "4.0.1", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "23385", + "title": "Chore: Remove dangling README file", + "userLogin": "tassoevan", + "description": "Removes the elderly `server/restapi/README.md`.", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "23379", + "title": "[FIX] resumeToken not working", + "userLogin": "sampaiodiego", + "milestone": "4.0.1", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "23372", + "title": "[FIX] unwanted toastr error message when deleting user", + "userLogin": "ostjen", + "milestone": "4.0.1", + "contributors": [ + "ostjen", + "tassoevan", + "web-flow" + ] + }, + { + "pr": "23370", + "title": "Chore: Migrate some React components/hooks to TypeScript", + "userLogin": "tassoevan", + "description": "Just low-hanging fruits.", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "23366", + "title": "[FIX] BigBlueButton integration error due to missing file import", + "userLogin": "wolbernd", + "description": "Fixes BigBlueButton integration", + "milestone": "4.0.1", + "contributors": [ + "wolbernd", + "web-flow" + ] + }, + { + "pr": "23375", + "title": "Chore: Update Apps-Engine version", + "userLogin": "d-gubert", + "milestone": "4.0.1", + "contributors": [ + "d-gubert" + ] + }, + { + "pr": "23374", + "title": "[FIX] imported migration v240", + "userLogin": "ostjen", + "milestone": "4.0.1", + "contributors": [ + "ostjen" + ] + }, + { + "pr": "22941", + "title": "[IMPROVE] optimized groups.listAll response time", + "userLogin": "ostjen", + "description": "groups.listAll endpoint was having performance issues, specially when the total number of groups was high. This happened because the endpoint was loading all objects in memory then using splice to paginate, instead of paginating beforehand.\r\n\r\nConsidering 70k groups, this was the performance improvement:\r\n\r\nbefore\r\n![image](https://user-images.githubusercontent.com/28611993/129601314-bdf89337-79fa-4446-9f44-95264af4adb3.png)\r\n\r\nafter\r\n![image](https://user-images.githubusercontent.com/28611993/129601358-5872e166-f923-4c1c-b21d-eb9507365ecf.png)", + "contributors": [ + "ostjen", + "tassoevan", + "web-flow" + ] + }, + { + "pr": "23213", + "title": "[FIX] Read only description in team creation", + "userLogin": "dougfabris", + "description": "![image](https://user-images.githubusercontent.com/27704687/133608433-8ca788a3-71a8-4d40-8c40-8156ab03c606.png)\r\n\r\n![image](https://user-images.githubusercontent.com/27704687/133608400-4cdc7a67-95e5-46c6-8c65-29ab107cd314.png)", + "contributors": [ + "dougfabris" + ] + }, + { + "pr": "23364", + "title": "Chore: Upgrade Storybook", + "userLogin": "tassoevan", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "23360", + "title": "Chore: Move components away from /app/", + "userLogin": "tassoevan", + "description": "We currently do NOT recommend placing React components under `/app`.", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "23361", + "title": "Chore: Document REST API endpoints (banners)", + "userLogin": "tassoevan", + "description": "Describes endpoints for banners on REST API using a JSDoc annotation compatible with OpenAPI spec.", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "23362", + "title": "Merge master into develop & Set version to 4.1.0-develop", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego", + "ggazzo", + "web-flow" + ] + } + ] } } } \ No newline at end of file diff --git a/.snapcraft/resources/prepareRocketChat b/.snapcraft/resources/prepareRocketChat index 5162f965d6e1e..b4e84a5a1f9b7 100755 --- a/.snapcraft/resources/prepareRocketChat +++ b/.snapcraft/resources/prepareRocketChat @@ -1,6 +1,6 @@ #!/bin/bash -curl -SLf "https://releases.rocket.chat/4.1.0-develop/download/" -o rocket.chat.tgz +curl -SLf "https://releases.rocket.chat/4.1.0-rc.0/download/" -o rocket.chat.tgz tar xf rocket.chat.tgz --strip 1 diff --git a/.snapcraft/snap/snapcraft.yaml b/.snapcraft/snap/snapcraft.yaml index 2ed13efd925e2..0eb54a527c1e8 100644 --- a/.snapcraft/snap/snapcraft.yaml +++ b/.snapcraft/snap/snapcraft.yaml @@ -7,7 +7,7 @@ # 5. `snapcraft snap` name: rocketchat-server -version: 4.1.0-develop +version: 4.1.0-rc.0 summary: Rocket.Chat server description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ confinement: strict diff --git a/HISTORY.md b/HISTORY.md index 383262cae967b..c381bf438839a 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,4 +1,242 @@ +# 4.1.0 (Under Release Candidate Process) + +## 4.1.0-rc.0 +`2021-10-20 · 1 🎉 · 4 🚀 · 24 🐛 · 30 🔍 · 23 👩‍💻👨‍💻` + +### 🎉 New features + + +- Stream to get individual presence updates ([#22950](https://github.com/RocketChat/Rocket.Chat/pull/22950)) + +### 🚀 Improvements + + +- Add markdown to custom fields in user Info ([#20947](https://github.com/RocketChat/Rocket.Chat/pull/20947) by [@yash-rajpal](https://github.com/yash-rajpal)) + + Added markdown to custom fields to render links + +- Allow Omnichannel to handle huge queues ([#23392](https://github.com/RocketChat/Rocket.Chat/pull/23392)) + +- Make Livechat Instructions setting multi-line ([#23515](https://github.com/RocketChat/Rocket.Chat/pull/23515)) + + Since now we're supporting markdown text on this field (via this PR - https://github.com/RocketChat/Rocket.Chat.Livechat/pull/648), it would be nice to make this setting multiline so users can have more space to edit the text + ![image](https://user-images.githubusercontent.com/34130764/138146712-13e4968b-5312-4d53-b44c-b5699c5e49c1.png) + +- optimized groups.listAll response time ([#22941](https://github.com/RocketChat/Rocket.Chat/pull/22941)) + + groups.listAll endpoint was having performance issues, specially when the total number of groups was high. This happened because the endpoint was loading all objects in memory then using splice to paginate, instead of paginating beforehand. + + Considering 70k groups, this was the performance improvement: + + before + ![image](https://user-images.githubusercontent.com/28611993/129601314-bdf89337-79fa-4446-9f44-95264af4adb3.png) + + after + ![image](https://user-images.githubusercontent.com/28611993/129601358-5872e166-f923-4c1c-b21d-eb9507365ecf.png) + +### 🐛 Bug fixes + + +- **APPS:** Communication problem when updating and uninstalling apps in cluster ([#23418](https://github.com/RocketChat/Rocket.Chat/pull/23418)) + + - Make the hook responsible for receiving app update events inside a cluster fetch the app's package (zip file) in the correct place. + - Also shows a warning message on uninstalls inside a cluster. As there are many servers writing to the same place, some race conditions may occur. This prevents problems related to terminating the process in the middle due to errors being thrown and leaving the server in a faulty state. + +- **ENTERPRISE:** Omnichannel agent is not leaving the room when a forwarded chat is queued ([#23404](https://github.com/RocketChat/Rocket.Chat/pull/23404)) + +- Admins can't update or reset user avatars when the "Allow User Avatar Change" setting is off ([#23228](https://github.com/RocketChat/Rocket.Chat/pull/23228)) + + - Allow admins (or any other user with the `edit-other-user-avatar` permission) to update or reset user avatars even when the "Allow User Avatar Change" setting is off. + +- Attachment buttons overlap in mobile view ([#23377](https://github.com/RocketChat/Rocket.Chat/pull/23377) by [@Aman-Maheshwari](https://github.com/Aman-Maheshwari)) + +- Avoid last admin deactivate itself ([#22949](https://github.com/RocketChat/Rocket.Chat/pull/22949)) + + Co-authored-by: @Kartik18g + +- BigBlueButton integration error due to missing file import ([#23366](https://github.com/RocketChat/Rocket.Chat/pull/23366) by [@wolbernd](https://github.com/wolbernd)) + + Fixes BigBlueButton integration + +- Delay start of email inbox ([#23521](https://github.com/RocketChat/Rocket.Chat/pull/23521)) + +- imported migration v240 ([#23374](https://github.com/RocketChat/Rocket.Chat/pull/23374)) + +- LDAP not stoping after wrong password ([#23382](https://github.com/RocketChat/Rocket.Chat/pull/23382)) + +- Markdown quote message style ([#23462](https://github.com/RocketChat/Rocket.Chat/pull/23462)) + + Before: + ![image](https://user-images.githubusercontent.com/17487063/137496669-3abecab4-cf90-45cb-8b1b-d9411a5682dd.png) + + After: + ![image](https://user-images.githubusercontent.com/17487063/137496905-fd727f90-f707-4ec6-8139-ba2eb1a2146e.png) + +- MONGO_OPTIONS being ignored for oplog connection ([#23314](https://github.com/RocketChat/Rocket.Chat/pull/23314) by [@cuonghuunguyen](https://github.com/cuonghuunguyen)) + +- MongoDB deprecation link ([#23381](https://github.com/RocketChat/Rocket.Chat/pull/23381)) + +- Omni-Webhook's retry mechanism going in infinite loop ([#23394](https://github.com/RocketChat/Rocket.Chat/pull/23394)) + +- Prevent starting Omni-Queue if Omnichannel is disabled ([#23396](https://github.com/RocketChat/Rocket.Chat/pull/23396)) + + Whenever the Routing system setting changes, and omnichannel is disabled, then we shouldn't start the queue. + +- Queue error handling and unlocking behavior ([#23522](https://github.com/RocketChat/Rocket.Chat/pull/23522)) + +- Read only description in team creation ([#23213](https://github.com/RocketChat/Rocket.Chat/pull/23213)) + + ![image](https://user-images.githubusercontent.com/27704687/133608433-8ca788a3-71a8-4d40-8c40-8156ab03c606.png) + + ![image](https://user-images.githubusercontent.com/27704687/133608400-4cdc7a67-95e5-46c6-8c65-29ab107cd314.png) + +- resumeToken not working ([#23379](https://github.com/RocketChat/Rocket.Chat/pull/23379)) + +- Rewrite missing webRTC feature ([#23172](https://github.com/RocketChat/Rocket.Chat/pull/23172)) + +- SAML Users' roles being reset to default on login ([#23411](https://github.com/RocketChat/Rocket.Chat/pull/23411)) + + - Remove `roles` field update on `insertOrUpdateSAMLUser` function; + - Add SAML `syncRoles` event; + +- Server crashing when Routing method is not available at start ([#23473](https://github.com/RocketChat/Rocket.Chat/pull/23473)) + +- unwanted toastr error message when deleting user ([#23372](https://github.com/RocketChat/Rocket.Chat/pull/23372)) + +- useEndpointAction replace by useEndpointActionExperimental ([#23469](https://github.com/RocketChat/Rocket.Chat/pull/23469)) + +- user/agent upload not working via Apps Engine after 3.16.0 ([#23393](https://github.com/RocketChat/Rocket.Chat/pull/23393)) + + Fixes #22974 + +- Users' `roles` and `type` being reset to default on LDAP DataSync ([#23378](https://github.com/RocketChat/Rocket.Chat/pull/23378)) + + - Update `roles` and `type` fields only if they are specified in the data imported from LDAP (otherwise, no changes are applied). + +
+🔍 Minor changes + + +- Bump url-parse from 1.4.7 to 1.5.3 ([#23376](https://github.com/RocketChat/Rocket.Chat/pull/23376) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Chore: clean README ([#23342](https://github.com/RocketChat/Rocket.Chat/pull/23342) by [@AbhJ](https://github.com/AbhJ)) + +- Chore: Document REST API endpoints (banners) ([#23361](https://github.com/RocketChat/Rocket.Chat/pull/23361)) + + Describes endpoints for banners on REST API using a JSDoc annotation compatible with OpenAPI spec. + +- Chore: Document REST API endpoints (DNS) ([#23405](https://github.com/RocketChat/Rocket.Chat/pull/23405)) + + Describes endpoints for DNS on REST API using a JSDoc annotation compatible with OpenAPI spec. + +- Chore: Document REST API endpoints (E2E) ([#23430](https://github.com/RocketChat/Rocket.Chat/pull/23430)) + + Describes endpoints for end-to-end encryption on REST API using a JSDoc annotation compatible with OpenAPI spec. + +- Chore: Document REST API endpoints (Misc) ([#23428](https://github.com/RocketChat/Rocket.Chat/pull/23428)) + + Describes miscellaneous endpoints on REST API using a JSDoc annotation compatible with OpenAPI spec. + +- Chore: Ensure all permissions are created up to this point ([#23514](https://github.com/RocketChat/Rocket.Chat/pull/23514)) + +- Chore: Fix some TS warnings ([#23524](https://github.com/RocketChat/Rocket.Chat/pull/23524)) + +- Chore: Fixed a Typo in 11-admin.js test ([#23355](https://github.com/RocketChat/Rocket.Chat/pull/23355) by [@badbart](https://github.com/badbart)) + +- Chore: Improve watch OAuth settings logic ([#23505](https://github.com/RocketChat/Rocket.Chat/pull/23505)) + + Just prevent to perform 200 deletions for registers that not even exist + +- Chore: Make omnichannel settings dependent on omnichannel being enabled ([#23495](https://github.com/RocketChat/Rocket.Chat/pull/23495)) + +- Chore: Migrate some React components/hooks to TypeScript ([#23370](https://github.com/RocketChat/Rocket.Chat/pull/23370)) + + Just low-hanging fruits. + +- Chore: Move `addMinutesToADate` helper ([#23490](https://github.com/RocketChat/Rocket.Chat/pull/23490)) + +- Chore: Move `isEmail` helper ([#23489](https://github.com/RocketChat/Rocket.Chat/pull/23489)) + +- Chore: Move `isJSON` helper ([#23491](https://github.com/RocketChat/Rocket.Chat/pull/23491)) + +- Chore: Move components away from /app/ ([#23360](https://github.com/RocketChat/Rocket.Chat/pull/23360)) + + We currently do NOT recommend placing React components under `/app`. + +- Chore: Partially migrate 2FA client code to TypeScript ([#23419](https://github.com/RocketChat/Rocket.Chat/pull/23419)) + + Additionally, hides `toastr` behind an module to handle UI's toast notifications. + +- Chore: Remove dangling README file ([#23385](https://github.com/RocketChat/Rocket.Chat/pull/23385)) + + Removes the elderly `server/restapi/README.md`. + +- Chore: Replace `promises` helper ([#23488](https://github.com/RocketChat/Rocket.Chat/pull/23488)) + +- Chore: Startup Time ([#23210](https://github.com/RocketChat/Rocket.Chat/pull/23210)) + + The settings logic has been improved as a whole. + + All the logic to get the data from the env var was confusing. + + Setting default values was tricky to understand. + + Every time the server booted, all settings were updated and callbacks were called 2x or more (horrible for environments with multiple instances and generating a turbulent startup). + + `Settings.get(......, callback);` was deprecated. We now have better methods for each case. + +- Chore: Update Apps-Engine version ([#23375](https://github.com/RocketChat/Rocket.Chat/pull/23375)) + +- Chore: Update Livechat Package ([#23523](https://github.com/RocketChat/Rocket.Chat/pull/23523)) + +- Chore: Update pino and pino-pretty ([#23510](https://github.com/RocketChat/Rocket.Chat/pull/23510)) + +- Chore: Upgrade Storybook ([#23364](https://github.com/RocketChat/Rocket.Chat/pull/23364)) + +- i18n: Language update from LingoHub 🤖 on 2021-10-18Z ([#23486](https://github.com/RocketChat/Rocket.Chat/pull/23486)) + +- Merge master into develop & Set version to 4.1.0-develop ([#23362](https://github.com/RocketChat/Rocket.Chat/pull/23362)) + +- Regression: Fix enterprise setting validation ([#23519](https://github.com/RocketChat/Rocket.Chat/pull/23519)) + +- Regression: Fix user typings style ([#23511](https://github.com/RocketChat/Rocket.Chat/pull/23511)) + +- Regression: Prevent Settings Unit Test Error ([#23506](https://github.com/RocketChat/Rocket.Chat/pull/23506)) + +- Update the community open call link in README ([#23497](https://github.com/RocketChat/Rocket.Chat/pull/23497)) + +
+ +### 👩‍💻👨‍💻 Contributors 😍 + +- [@AbhJ](https://github.com/AbhJ) +- [@Aman-Maheshwari](https://github.com/Aman-Maheshwari) +- [@badbart](https://github.com/badbart) +- [@cuonghuunguyen](https://github.com/cuonghuunguyen) +- [@dependabot[bot]](https://github.com/dependabot[bot]) +- [@wolbernd](https://github.com/wolbernd) +- [@yash-rajpal](https://github.com/yash-rajpal) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@KevLehman](https://github.com/KevLehman) +- [@MartinSchoeler](https://github.com/MartinSchoeler) +- [@Sing-Li](https://github.com/Sing-Li) +- [@d-gubert](https://github.com/d-gubert) +- [@dougfabris](https://github.com/dougfabris) +- [@geekgonecrazy](https://github.com/geekgonecrazy) +- [@ggazzo](https://github.com/ggazzo) +- [@matheusbsilva137](https://github.com/matheusbsilva137) +- [@murtaza98](https://github.com/murtaza98) +- [@ostjen](https://github.com/ostjen) +- [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) +- [@rodrigok](https://github.com/rodrigok) +- [@sampaiodiego](https://github.com/sampaiodiego) +- [@tassoevan](https://github.com/tassoevan) +- [@thassiov](https://github.com/thassiov) +- [@tiagoevanp](https://github.com/tiagoevanp) + # 4.0.0 `2021-10-01 · 15 ️️️⚠️ · 4 🎉 · 11 🚀 · 24 🐛 · 67 🔍 · 26 👩‍💻👨‍💻` @@ -8733,7 +8971,7 @@ - Slash command preview: Wrong item being selected, Horizontal scroll ([#16750](https://github.com/RocketChat/Rocket.Chat/pull/16750)) -- Text formatted to remain within button even on screen resize ([#14136](https://github.com/RocketChat/Rocket.Chat/pull/14136) by [@Rodriq](https://github.com/Rodriq)) +- Text formatted to remain within button even on screen resize ([#14136](https://github.com/RocketChat/Rocket.Chat/pull/14136)) - There is no option to pin a thread message by admin ([#16457](https://github.com/RocketChat/Rocket.Chat/pull/16457) by [@ashwaniYDV](https://github.com/ashwaniYDV)) @@ -8939,7 +9177,6 @@ - [@GOVINDDIXIT](https://github.com/GOVINDDIXIT) - [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@Nikhil713](https://github.com/Nikhil713) -- [@Rodriq](https://github.com/Rodriq) - [@aKn1ghtOut](https://github.com/aKn1ghtOut) - [@antkaz](https://github.com/antkaz) - [@aryamanpuri](https://github.com/aryamanpuri) @@ -8967,6 +9204,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 - [@PrajvalRaval](https://github.com/PrajvalRaval) +- [@Rodriq](https://github.com/Rodriq) - [@Sing-Li](https://github.com/Sing-Li) - [@d-gubert](https://github.com/d-gubert) - [@engelgabriel](https://github.com/engelgabriel) diff --git a/app/utils/rocketchat.info b/app/utils/rocketchat.info index 292ffbb927c70..81122a6b83fde 100644 --- a/app/utils/rocketchat.info +++ b/app/utils/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "4.1.0-develop" + "version": "4.1.0-rc.0" } diff --git a/package-lock.json b/package-lock.json index e435c334b6014..b5f088ee75985 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "Rocket.Chat", - "version": "4.1.0-develop", + "version": "4.1.0-rc.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d4c510bebf55b..47968eeed8b49 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Rocket.Chat", "description": "The Ultimate Open Source WebChat Platform", - "version": "4.1.0-develop", + "version": "4.1.0-rc.0", "author": { "name": "Rocket.Chat", "url": "https://rocket.chat/" From 317a04bc49cc1672783132a78068fbc4f0607390 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 21 Oct 2021 13:45:59 -0300 Subject: [PATCH 019/137] Bump version to 4.1.0-rc.1 --- .docker/Dockerfile.rhel | 2 +- .github/history.json | 250 +++++++++++++++++++++++++ .snapcraft/resources/prepareRocketChat | 2 +- .snapcraft/snap/snapcraft.yaml | 2 +- HISTORY.md | 147 +++++++++++++++ app/utils/rocketchat.info | 2 +- package-lock.json | 2 +- package.json | 2 +- 8 files changed, 403 insertions(+), 6 deletions(-) diff --git a/.docker/Dockerfile.rhel b/.docker/Dockerfile.rhel index f6b321bdef533..bd0da27f32289 100644 --- a/.docker/Dockerfile.rhel +++ b/.docker/Dockerfile.rhel @@ -1,6 +1,6 @@ FROM registry.access.redhat.com/ubi8/nodejs-12 -ENV RC_VERSION 4.1.0-rc.0 +ENV RC_VERSION 4.1.0-rc.1 MAINTAINER buildmaster@rocket.chat diff --git a/.github/history.json b/.github/history.json index 838ace9ca1d44..b5c949cc50e32 100644 --- a/.github/history.json +++ b/.github/history.json @@ -66599,6 +66599,256 @@ ] } ] + }, + "4.0.1": { + "node_version": "12.22.1", + "npm_version": "6.14.1", + "apps_engine_version": "1.28.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "23386", + "title": "Release 4.0.1", + "userLogin": "sampaiodiego", + "contributors": [ + "rodrigok", + "sampaiodiego", + "ostjen", + "wolbernd", + "d-gubert", + "matheusbsilva137" + ] + }, + { + "pr": "23378", + "title": "[FIX] Users' `roles` and `type` being reset to default on LDAP DataSync", + "userLogin": "matheusbsilva137", + "description": "- Update `roles` and `type` fields only if they are specified in the data imported from LDAP (otherwise, no changes are applied).", + "milestone": "4.0.1", + "contributors": [ + "matheusbsilva137", + "sampaiodiego" + ] + }, + { + "pr": "23374", + "title": "[FIX] imported migration v240", + "userLogin": "ostjen", + "milestone": "4.0.1", + "contributors": [ + "ostjen" + ] + }, + { + "pr": "23375", + "title": "Chore: Update Apps-Engine version", + "userLogin": "d-gubert", + "milestone": "4.0.1", + "contributors": [ + "d-gubert" + ] + }, + { + "pr": "23366", + "title": "[FIX] BigBlueButton integration error due to missing file import", + "userLogin": "wolbernd", + "description": "Fixes BigBlueButton integration", + "milestone": "4.0.1", + "contributors": [ + "wolbernd", + "web-flow" + ] + }, + { + "pr": "23372", + "title": "[FIX] unwanted toastr error message when deleting user", + "userLogin": "ostjen", + "milestone": "4.0.1", + "contributors": [ + "ostjen", + "tassoevan", + "web-flow" + ] + }, + { + "pr": "23379", + "title": "[FIX] resumeToken not working", + "userLogin": "sampaiodiego", + "milestone": "4.0.1", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "23381", + "title": "[FIX] MongoDB deprecation link", + "userLogin": "sampaiodiego", + "milestone": "4.0.1", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "23382", + "title": "[FIX] LDAP not stoping after wrong password", + "userLogin": "rodrigok", + "milestone": "4.0.1", + "contributors": [ + "rodrigok" + ] + } + ] + }, + "4.0.2": { + "node_version": "12.22.1", + "npm_version": "6.14.1", + "apps_engine_version": "1.28.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "23460", + "title": "Release 4.0.2", + "userLogin": "sampaiodiego", + "contributors": [ + "murtaza98", + "sampaiodiego", + "Aman-Maheshwari" + ] + }, + { + "pr": "23377", + "title": "[FIX] Attachment buttons overlap in mobile view", + "userLogin": "Aman-Maheshwari", + "milestone": "4.0.2", + "contributors": [ + "Aman-Maheshwari" + ] + }, + { + "pr": "23393", + "title": "[FIX] user/agent upload not working via Apps Engine after 3.16.0", + "userLogin": "murtaza98", + "description": "Fixes #22974", + "milestone": "4.0.2", + "contributors": [ + "murtaza98" + ] + }, + { + "pr": "23404", + "title": "[FIX][ENTERPRISE] Omnichannel agent is not leaving the room when a forwarded chat is queued", + "userLogin": "murtaza98", + "milestone": "4.0.2", + "contributors": [ + "murtaza98", + "web-flow" + ] + }, + { + "pr": "23396", + "title": "[FIX] Prevent starting Omni-Queue if Omnichannel is disabled", + "userLogin": "murtaza98", + "description": "Whenever the Routing system setting changes, and omnichannel is disabled, then we shouldn't start the queue.", + "milestone": "4.0.2", + "contributors": [ + "murtaza98" + ] + } + ] + }, + "4.0.3": { + "node_version": "12.22.1", + "npm_version": "6.14.1", + "apps_engine_version": "1.28.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "23496", + "title": "Release 4.0.3", + "userLogin": "sampaiodiego", + "contributors": [ + "KevLehman", + "sampaiodiego", + "thassiov" + ] + }, + { + "pr": "23418", + "title": "[FIX][APPS] Communication problem when updating and uninstalling apps in cluster", + "userLogin": "thassiov", + "description": "- Make the hook responsible for receiving app update events inside a cluster fetch the app's package (zip file) in the correct place.\r\n- Also shows a warning message on uninstalls inside a cluster. As there are many servers writing to the same place, some race conditions may occur. This prevents problems related to terminating the process in the middle due to errors being thrown and leaving the server in a faulty state.", + "milestone": "4.0.3", + "contributors": [ + "thassiov" + ] + }, + { + "pr": "23473", + "title": "[FIX] Server crashing when Routing method is not available at start", + "userLogin": "KevLehman", + "milestone": "4.0.3", + "contributors": [ + "KevLehman", + "web-flow" + ] + } + ] + }, + "4.1.0-rc.1": { + "node_version": "12.22.1", + "npm_version": "6.14.1", + "apps_engine_version": "1.28.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "23531", + "title": "Regression: Waiting_queue setting not being applied due to missing module key", + "userLogin": "KevLehman", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "23528", + "title": "Regression: Settings order", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "23529", + "title": "Regression: watchByRegex without Fibers", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo" + ] + } + ] } } } \ No newline at end of file diff --git a/.snapcraft/resources/prepareRocketChat b/.snapcraft/resources/prepareRocketChat index b4e84a5a1f9b7..ec80c97638f9d 100755 --- a/.snapcraft/resources/prepareRocketChat +++ b/.snapcraft/resources/prepareRocketChat @@ -1,6 +1,6 @@ #!/bin/bash -curl -SLf "https://releases.rocket.chat/4.1.0-rc.0/download/" -o rocket.chat.tgz +curl -SLf "https://releases.rocket.chat/4.1.0-rc.1/download/" -o rocket.chat.tgz tar xf rocket.chat.tgz --strip 1 diff --git a/.snapcraft/snap/snapcraft.yaml b/.snapcraft/snap/snapcraft.yaml index 0eb54a527c1e8..9d66e78bd1d42 100644 --- a/.snapcraft/snap/snapcraft.yaml +++ b/.snapcraft/snap/snapcraft.yaml @@ -7,7 +7,7 @@ # 5. `snapcraft snap` name: rocketchat-server -version: 4.1.0-rc.0 +version: 4.1.0-rc.1 summary: Rocket.Chat server description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ confinement: strict diff --git a/HISTORY.md b/HISTORY.md index c381bf438839a..daa30a4cdb274 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,26 @@ # 4.1.0 (Under Release Candidate Process) +## 4.1.0-rc.1 +`2021-10-21 · 3 🔍 · 2 👩‍💻👨‍💻` + +
+🔍 Minor changes + + +- Regression: Settings order ([#23528](https://github.com/RocketChat/Rocket.Chat/pull/23528)) + +- Regression: Waiting_queue setting not being applied due to missing module key ([#23531](https://github.com/RocketChat/Rocket.Chat/pull/23531)) + +- Regression: watchByRegex without Fibers ([#23529](https://github.com/RocketChat/Rocket.Chat/pull/23529)) + +
+ +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@KevLehman](https://github.com/KevLehman) +- [@ggazzo](https://github.com/ggazzo) + ## 4.1.0-rc.0 `2021-10-20 · 1 🎉 · 4 🚀 · 24 🐛 · 30 🔍 · 23 👩‍💻👨‍💻` @@ -237,6 +257,133 @@ - [@thassiov](https://github.com/thassiov) - [@tiagoevanp](https://github.com/tiagoevanp) +# 4.0.3 +`2021-10-18 · 2 🐛 · 1 🔍 · 3 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.22.1` +- NPM: `6.14.1` +- MongoDB: `3.6, 4.0, 4.2, 4.4, 5.0` +- Apps-Engine: `1.28.0` + +### 🐛 Bug fixes + + +- **APPS:** Communication problem when updating and uninstalling apps in cluster ([#23418](https://github.com/RocketChat/Rocket.Chat/pull/23418)) + + - Make the hook responsible for receiving app update events inside a cluster fetch the app's package (zip file) in the correct place. + - Also shows a warning message on uninstalls inside a cluster. As there are many servers writing to the same place, some race conditions may occur. This prevents problems related to terminating the process in the middle due to errors being thrown and leaving the server in a faulty state. + +- Server crashing when Routing method is not available at start ([#23473](https://github.com/RocketChat/Rocket.Chat/pull/23473)) + +
+🔍 Minor changes + + +- Release 4.0.3 ([#23496](https://github.com/RocketChat/Rocket.Chat/pull/23496)) + +
+ +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@KevLehman](https://github.com/KevLehman) +- [@sampaiodiego](https://github.com/sampaiodiego) +- [@thassiov](https://github.com/thassiov) + +# 4.0.2 +`2021-10-14 · 4 🐛 · 1 🔍 · 3 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.22.1` +- NPM: `6.14.1` +- MongoDB: `3.6, 4.0, 4.2, 4.4, 5.0` +- Apps-Engine: `1.28.0` + +### 🐛 Bug fixes + + +- **ENTERPRISE:** Omnichannel agent is not leaving the room when a forwarded chat is queued ([#23404](https://github.com/RocketChat/Rocket.Chat/pull/23404)) + +- Attachment buttons overlap in mobile view ([#23377](https://github.com/RocketChat/Rocket.Chat/pull/23377) by [@Aman-Maheshwari](https://github.com/Aman-Maheshwari)) + +- Prevent starting Omni-Queue if Omnichannel is disabled ([#23396](https://github.com/RocketChat/Rocket.Chat/pull/23396)) + + Whenever the Routing system setting changes, and omnichannel is disabled, then we shouldn't start the queue. + +- user/agent upload not working via Apps Engine after 3.16.0 ([#23393](https://github.com/RocketChat/Rocket.Chat/pull/23393)) + + Fixes #22974 + +
+🔍 Minor changes + + +- Release 4.0.2 ([#23460](https://github.com/RocketChat/Rocket.Chat/pull/23460) by [@Aman-Maheshwari](https://github.com/Aman-Maheshwari)) + +
+ +### 👩‍💻👨‍💻 Contributors 😍 + +- [@Aman-Maheshwari](https://github.com/Aman-Maheshwari) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@murtaza98](https://github.com/murtaza98) +- [@sampaiodiego](https://github.com/sampaiodiego) + +# 4.0.1 +`2021-10-06 · 7 🐛 · 2 🔍 · 7 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.22.1` +- NPM: `6.14.1` +- MongoDB: `3.6, 4.0, 4.2, 4.4, 5.0` +- Apps-Engine: `1.28.0` + +### 🐛 Bug fixes + + +- BigBlueButton integration error due to missing file import ([#23366](https://github.com/RocketChat/Rocket.Chat/pull/23366) by [@wolbernd](https://github.com/wolbernd)) + + Fixes BigBlueButton integration + +- imported migration v240 ([#23374](https://github.com/RocketChat/Rocket.Chat/pull/23374)) + +- LDAP not stoping after wrong password ([#23382](https://github.com/RocketChat/Rocket.Chat/pull/23382)) + +- MongoDB deprecation link ([#23381](https://github.com/RocketChat/Rocket.Chat/pull/23381)) + +- resumeToken not working ([#23379](https://github.com/RocketChat/Rocket.Chat/pull/23379)) + +- unwanted toastr error message when deleting user ([#23372](https://github.com/RocketChat/Rocket.Chat/pull/23372)) + +- Users' `roles` and `type` being reset to default on LDAP DataSync ([#23378](https://github.com/RocketChat/Rocket.Chat/pull/23378)) + + - Update `roles` and `type` fields only if they are specified in the data imported from LDAP (otherwise, no changes are applied). + +
+🔍 Minor changes + + +- Chore: Update Apps-Engine version ([#23375](https://github.com/RocketChat/Rocket.Chat/pull/23375)) + +- Release 4.0.1 ([#23386](https://github.com/RocketChat/Rocket.Chat/pull/23386) by [@wolbernd](https://github.com/wolbernd)) + +
+ +### 👩‍💻👨‍💻 Contributors 😍 + +- [@wolbernd](https://github.com/wolbernd) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@d-gubert](https://github.com/d-gubert) +- [@matheusbsilva137](https://github.com/matheusbsilva137) +- [@ostjen](https://github.com/ostjen) +- [@rodrigok](https://github.com/rodrigok) +- [@sampaiodiego](https://github.com/sampaiodiego) +- [@tassoevan](https://github.com/tassoevan) + # 4.0.0 `2021-10-01 · 15 ️️️⚠️ · 4 🎉 · 11 🚀 · 24 🐛 · 67 🔍 · 26 👩‍💻👨‍💻` diff --git a/app/utils/rocketchat.info b/app/utils/rocketchat.info index 81122a6b83fde..44d60f6858fa7 100644 --- a/app/utils/rocketchat.info +++ b/app/utils/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "4.1.0-rc.0" + "version": "4.1.0-rc.1" } diff --git a/package-lock.json b/package-lock.json index b5f088ee75985..2e1aa932c01f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "Rocket.Chat", - "version": "4.1.0-rc.0", + "version": "4.1.0-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 47968eeed8b49..deaa3f77e1718 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Rocket.Chat", "description": "The Ultimate Open Source WebChat Platform", - "version": "4.1.0-rc.0", + "version": "4.1.0-rc.1", "author": { "name": "Rocket.Chat", "url": "https://rocket.chat/" From ed9603094f4320c3e04cb1f697dea02d50ad419c Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Wed, 20 Oct 2021 17:55:04 -0600 Subject: [PATCH 020/137] [FIX] Queue error handling and unlocking behavior (#23522) * Error handling on queue processing * add error handling to queue execution and lock timeout * add error handling to queue execution and lock timeout --- app/models/server/raw/OmnichannelQueue.ts | 15 ++++++++++++- .../server/lib/LivechatEnterprise.js | 21 +++++++++++++------ 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/app/models/server/raw/OmnichannelQueue.ts b/app/models/server/raw/OmnichannelQueue.ts index 74a2e1d9f8029..bca8878d86098 100644 --- a/app/models/server/raw/OmnichannelQueue.ts +++ b/app/models/server/raw/OmnichannelQueue.ts @@ -32,12 +32,22 @@ export class OmnichannelQueueRaw extends BaseRaw { } async lockQueue() { + const date = new Date(); const result = await this.col.findOneAndUpdate({ _id: UNIQUE_QUEUE_ID, - locked: false, + $or: [{ + locked: true, + lockedAt: { + $lte: new Date(date.getTime() - 5000), + }, + }, { + locked: false, + }], }, { $set: { locked: true, + // apply 5 secs lock lifetime + lockedAt: new Date(), }, }, { sort: { @@ -55,6 +65,9 @@ export class OmnichannelQueueRaw extends BaseRaw { $set: { locked: false, }, + $unset: { + lockedAt: 1, + }, }, { sort: { _id: 1, diff --git a/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.js b/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.js index 270a31218c8b5..6f17dc191d983 100644 --- a/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.js +++ b/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.js @@ -249,13 +249,22 @@ const queueWorker = { async checkQueue(queue) { queueLogger.debug(`Processing items for queue ${ queue || 'Public' }`); - if (await OmnichannelQueue.lockQueue()) { - await processWaitingQueue(queue); - queueLogger.debug(`Queue ${ queue || 'Public' } processed. Unlocking`); - await OmnichannelQueue.unlockQueue(); + try { + if (await OmnichannelQueue.lockQueue()) { + await processWaitingQueue(queue); + queueLogger.debug(`Queue ${ queue || 'Public' } processed. Unlocking`); + await OmnichannelQueue.unlockQueue(); + } else { + queueLogger.debug('Queue locked. Waiting'); + } + } catch (e) { + queueLogger.error({ + msg: `Error processing queue ${ queue || 'public' }`, + err: e, + }); + } finally { + this.execute(); } - - this.execute(); }, }; From 761b874a1b5b3741e04912bea5ef40d249c71f5e Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Wed, 20 Oct 2021 21:04:58 -0300 Subject: [PATCH 021/137] [FIX] SAML Users' roles being reset to default on login (#23411) * Remove roles update on insertOrUpdateSAMLUser * Check SAML Role Sync setting before updating users' roles * Add rolesSync variable * Add role sync event * Remove isEnterprise check * New code * Updated unit test for the new behavior Co-authored-by: Pierre Lehnen --- .../server/definition/ISAMLUser.ts | 2 +- app/meteor-accounts-saml/server/lib/SAML.ts | 11 +++++++---- app/meteor-accounts-saml/server/lib/Utils.ts | 3 --- app/meteor-accounts-saml/tests/server.tests.ts | 2 +- ee/server/configuration/saml.ts | 1 - 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/app/meteor-accounts-saml/server/definition/ISAMLUser.ts b/app/meteor-accounts-saml/server/definition/ISAMLUser.ts index 7b4e41019ef80..0dccac76d4252 100644 --- a/app/meteor-accounts-saml/server/definition/ISAMLUser.ts +++ b/app/meteor-accounts-saml/server/definition/ISAMLUser.ts @@ -1,7 +1,7 @@ export interface ISAMLUser { emailList: Array; fullName: string | null; - roles: Array; + roles?: Array; eppn: string | null; username?: string; diff --git a/app/meteor-accounts-saml/server/lib/SAML.ts b/app/meteor-accounts-saml/server/lib/SAML.ts index c2141d74a4a71..5de64393352a5 100644 --- a/app/meteor-accounts-saml/server/lib/SAML.ts +++ b/app/meteor-accounts-saml/server/lib/SAML.ts @@ -72,7 +72,7 @@ export class SAML { } public static insertOrUpdateSAMLUser(userObject: ISAMLUser): {userId: string; token: string} { - const { generateUsername, immutableProperty, nameOverwrite, mailOverwrite, channelsAttributeUpdate } = SAMLUtils.globalSettings; + const { generateUsername, immutableProperty, nameOverwrite, mailOverwrite, channelsAttributeUpdate, defaultUserRole = 'user' } = SAMLUtils.globalSettings; let customIdentifierMatch = false; let customIdentifierAttributeName: string | null = null; @@ -104,12 +104,14 @@ export class SAML { verified: settings.get('Accounts_Verify_Email_For_External_Accounts'), })); - const { roles } = userObject; let { username } = userObject; const active = !settings.get('Accounts_ManuallyApproveNewUsers'); if (!user) { + // If we received any role from the mapping, use them - otherwise use the default role for creation. + const roles = userObject.roles?.length ? userObject.roles : SAMLUtils.ensureArray(defaultUserRole.split(',')); + const newUser: Record = { name: userObject.fullName, active, @@ -180,8 +182,9 @@ export class SAML { updateData.name = userObject.fullName; } - if (roles) { - updateData.roles = roles; + // When updating an user, we only update the roles if we received them from the mapping + if (userObject.roles?.length) { + updateData.roles = userObject.roles; } if (userObject.channels && channelsAttributeUpdate === true) { diff --git a/app/meteor-accounts-saml/server/lib/Utils.ts b/app/meteor-accounts-saml/server/lib/Utils.ts index 13d1fc3291b81..4b978069ea7a2 100644 --- a/app/meteor-accounts-saml/server/lib/Utils.ts +++ b/app/meteor-accounts-saml/server/lib/Utils.ts @@ -410,7 +410,6 @@ export class SAMLUtils { public static mapProfileToUserObject(profile: Record): ISAMLUser { const userDataMap = this.getUserDataMapping(); SAMLUtils.log('parsed userDataMap', userDataMap); - const { defaultUserRole = 'user' } = this.globalSettings; if (userDataMap.identifier.type === 'custom') { if (!userDataMap.identifier.attribute) { @@ -447,7 +446,6 @@ export class SAMLUtils { }, emailList: this.ensureArray(email), fullName: name || profile.displayName || profile.username, - roles: this.ensureArray(defaultUserRole.split(',')), eppn: profile.eppn, attributeList, identifier: userDataMap.identifier, @@ -469,7 +467,6 @@ export class SAMLUtils { } } - this.events.emit('mapUser', { profile, userObject }); return userObject; diff --git a/app/meteor-accounts-saml/tests/server.tests.ts b/app/meteor-accounts-saml/tests/server.tests.ts index 7cee56b748859..d8ca3ba2f7023 100644 --- a/app/meteor-accounts-saml/tests/server.tests.ts +++ b/app/meteor-accounts-saml/tests/server.tests.ts @@ -649,7 +649,7 @@ describe('SAML', () => { expect(userObject).to.have.property('emailList').that.is.an('array').that.includes('testing@server.com'); expect(userObject).to.have.property('fullName').that.is.equal('[AnotherName]'); expect(userObject).to.have.property('username').that.is.equal('[AnotherUserName]'); - expect(userObject).to.have.property('roles').that.is.an('array').with.members(['user']); + expect(userObject).to.not.have.property('roles'); expect(userObject).to.have.property('channels').that.is.an('array').with.members(['pets', 'pics', 'funny', 'random', 'babies']); }); diff --git a/ee/server/configuration/saml.ts b/ee/server/configuration/saml.ts index 116e602202bc8..d9a89dc442d87 100644 --- a/ee/server/configuration/saml.ts +++ b/ee/server/configuration/saml.ts @@ -5,7 +5,6 @@ import { settings } from '../../../app/settings/server'; import { addSettings } from '../settings/saml'; import { Users } from '../../../app/models/server'; - onLicense('saml-enterprise', () => { SAMLUtils.events.on('mapUser', ({ profile, userObject }: { profile: Record; userObject: ISAMLUser}) => { const roleAttributeName = settings.get('SAML_Custom_Default_role_attribute_name') as string; From f2234436b21d7537a4667200b5567d5afb31687e Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 21 Oct 2021 18:04:42 -0300 Subject: [PATCH 022/137] Bump version to 4.0.4 --- .docker/Dockerfile.rhel | 2 +- .github/history.json | 34 ++++++++++++++++++++++++++ .snapcraft/resources/prepareRocketChat | 2 +- .snapcraft/snap/snapcraft.yaml | 2 +- HISTORY.md | 25 +++++++++++++++++++ app/utils/rocketchat.info | 2 +- package-lock.json | 2 +- package.json | 2 +- 8 files changed, 65 insertions(+), 6 deletions(-) diff --git a/.docker/Dockerfile.rhel b/.docker/Dockerfile.rhel index 123349efe1a05..322a9f8cc15c4 100644 --- a/.docker/Dockerfile.rhel +++ b/.docker/Dockerfile.rhel @@ -1,6 +1,6 @@ FROM registry.access.redhat.com/ubi8/nodejs-12 -ENV RC_VERSION 4.0.3 +ENV RC_VERSION 4.0.4 MAINTAINER buildmaster@rocket.chat diff --git a/.github/history.json b/.github/history.json index 8c882ffc2cd8a..abb3f33258531 100644 --- a/.github/history.json +++ b/.github/history.json @@ -66220,6 +66220,40 @@ ] } ] + }, + "4.0.4": { + "node_version": "12.22.1", + "npm_version": "6.14.1", + "apps_engine_version": "1.28.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "23411", + "title": "[FIX] SAML Users' roles being reset to default on login", + "userLogin": "matheusbsilva137", + "description": "- Remove `roles` field update on `insertOrUpdateSAMLUser` function;\r\n- Add SAML `syncRoles` event;", + "milestone": "4.0.4", + "contributors": [ + "matheusbsilva137", + "pierre-lehnen-rc" + ] + }, + { + "pr": "23522", + "title": "[FIX] Queue error handling and unlocking behavior", + "userLogin": "KevLehman", + "milestone": "4.0.4", + "contributors": [ + "KevLehman" + ] + } + ] } } } \ No newline at end of file diff --git a/.snapcraft/resources/prepareRocketChat b/.snapcraft/resources/prepareRocketChat index afe64c8a04f75..0a840157709f4 100755 --- a/.snapcraft/resources/prepareRocketChat +++ b/.snapcraft/resources/prepareRocketChat @@ -1,6 +1,6 @@ #!/bin/bash -curl -SLf "https://releases.rocket.chat/4.0.3/download/" -o rocket.chat.tgz +curl -SLf "https://releases.rocket.chat/4.0.4/download/" -o rocket.chat.tgz tar xf rocket.chat.tgz --strip 1 diff --git a/.snapcraft/snap/snapcraft.yaml b/.snapcraft/snap/snapcraft.yaml index fe717d823b828..5d8099b0f6a32 100644 --- a/.snapcraft/snap/snapcraft.yaml +++ b/.snapcraft/snap/snapcraft.yaml @@ -7,7 +7,7 @@ # 5. `snapcraft snap` name: rocketchat-server -version: 4.0.3 +version: 4.0.4 summary: Rocket.Chat server description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ confinement: strict diff --git a/HISTORY.md b/HISTORY.md index cf3ad29eab917..f8f83f1cb7821 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,4 +1,29 @@ +# 4.0.4 +`2021-10-21 · 2 🐛 · 3 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.22.1` +- NPM: `6.14.1` +- MongoDB: `3.6, 4.0, 4.2, 4.4, 5.0` +- Apps-Engine: `1.28.0` + +### 🐛 Bug fixes + + +- Queue error handling and unlocking behavior ([#23522](https://github.com/RocketChat/Rocket.Chat/pull/23522)) + +- SAML Users' roles being reset to default on login ([#23411](https://github.com/RocketChat/Rocket.Chat/pull/23411)) + + - Remove `roles` field update on `insertOrUpdateSAMLUser` function; + - Add SAML `syncRoles` event; + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@KevLehman](https://github.com/KevLehman) +- [@matheusbsilva137](https://github.com/matheusbsilva137) +- [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) + # 4.0.3 `2021-10-18 · 2 🐛 · 2 👩‍💻👨‍💻` diff --git a/app/utils/rocketchat.info b/app/utils/rocketchat.info index 692f5a791ffed..68923f220d400 100644 --- a/app/utils/rocketchat.info +++ b/app/utils/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "4.0.3" + "version": "4.0.4" } diff --git a/package-lock.json b/package-lock.json index 1b0b706860bc6..6c811a5aacc20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "Rocket.Chat", - "version": "4.0.3", + "version": "4.0.4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index c8a96c5fd3e69..f5152b781571d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Rocket.Chat", "description": "The Ultimate Open Source WebChat Platform", - "version": "4.0.3", + "version": "4.0.4", "author": { "name": "Rocket.Chat", "url": "https://rocket.chat/" From 4c1cadedc8378e54b92c671412c5dc83f3fe64b5 Mon Sep 17 00:00:00 2001 From: pierre-lehnen-rc <55164754+pierre-lehnen-rc@users.noreply.github.com> Date: Mon, 25 Oct 2021 16:20:25 -0300 Subject: [PATCH 023/137] [FIX] OAuth login not working on mobile app (#23541) --- app/google-oauth/server/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/google-oauth/server/index.js b/app/google-oauth/server/index.js index c785c3da47a5b..6d49f091fb203 100644 --- a/app/google-oauth/server/index.js +++ b/app/google-oauth/server/index.js @@ -22,6 +22,7 @@ Meteor.startup(() => { credentialSecret: escape(options.credentialSecret), storagePrefix: escape(OAuth._storageTokenPrefix), redirectUrl: escape(options.redirectUrl), + isCordova: Boolean(options.isCordova), }; let template; @@ -64,12 +65,14 @@ Meteor.startup(() => { } } + const isCordova = OAuth._isCordovaFromQuery(details.query); if (details.error) { res.end(renderEndOfLoginResponse({ loginStyle: details.loginStyle, setCredentialToken: false, redirectUrl, + isCordova, }), 'utf-8'); return; } @@ -83,6 +86,7 @@ Meteor.startup(() => { credentialToken: details.credentials.token, credentialSecret: details.credentials.secret, redirectUrl, + isCordova, }), 'utf-8'); }; }); From 8555634aaa0bb418fd15f57aeb580a58997eea82 Mon Sep 17 00:00:00 2001 From: pierre-lehnen-rc <55164754+pierre-lehnen-rc@users.noreply.github.com> Date: Mon, 25 Oct 2021 16:20:25 -0300 Subject: [PATCH 024/137] [FIX] OAuth login not working on mobile app (#23541) --- app/google-oauth/server/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/google-oauth/server/index.js b/app/google-oauth/server/index.js index c785c3da47a5b..6d49f091fb203 100644 --- a/app/google-oauth/server/index.js +++ b/app/google-oauth/server/index.js @@ -22,6 +22,7 @@ Meteor.startup(() => { credentialSecret: escape(options.credentialSecret), storagePrefix: escape(OAuth._storageTokenPrefix), redirectUrl: escape(options.redirectUrl), + isCordova: Boolean(options.isCordova), }; let template; @@ -64,12 +65,14 @@ Meteor.startup(() => { } } + const isCordova = OAuth._isCordovaFromQuery(details.query); if (details.error) { res.end(renderEndOfLoginResponse({ loginStyle: details.loginStyle, setCredentialToken: false, redirectUrl, + isCordova, }), 'utf-8'); return; } @@ -83,6 +86,7 @@ Meteor.startup(() => { credentialToken: details.credentials.token, credentialSecret: details.credentials.secret, redirectUrl, + isCordova, }), 'utf-8'); }; }); From f1cf805300533912ec29f8d6a33e38aabfb1f7a8 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Mon, 25 Oct 2021 16:22:49 -0300 Subject: [PATCH 025/137] Bump version to 4.0.5 --- .docker/Dockerfile.rhel | 2 +- .github/history.json | 23 +++++++++++++++++++++++ .snapcraft/resources/prepareRocketChat | 2 +- .snapcraft/snap/snapcraft.yaml | 2 +- HISTORY.md | 24 +++++++++++++++++++++--- app/utils/rocketchat.info | 2 +- package-lock.json | 2 +- package.json | 2 +- 8 files changed, 50 insertions(+), 9 deletions(-) diff --git a/.docker/Dockerfile.rhel b/.docker/Dockerfile.rhel index 322a9f8cc15c4..9db24dd452f1f 100644 --- a/.docker/Dockerfile.rhel +++ b/.docker/Dockerfile.rhel @@ -1,6 +1,6 @@ FROM registry.access.redhat.com/ubi8/nodejs-12 -ENV RC_VERSION 4.0.4 +ENV RC_VERSION 4.0.5 MAINTAINER buildmaster@rocket.chat diff --git a/.github/history.json b/.github/history.json index abb3f33258531..934bfd2ab2074 100644 --- a/.github/history.json +++ b/.github/history.json @@ -66254,6 +66254,29 @@ ] } ] + }, + "4.0.5": { + "node_version": "12.22.1", + "npm_version": "6.14.1", + "apps_engine_version": "1.28.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "23541", + "title": "[FIX] OAuth login not working on mobile app", + "userLogin": "pierre-lehnen-rc", + "milestone": "4.0.5", + "contributors": [ + "pierre-lehnen-rc" + ] + } + ] } } } \ No newline at end of file diff --git a/.snapcraft/resources/prepareRocketChat b/.snapcraft/resources/prepareRocketChat index 0a840157709f4..efea9f04e7d7f 100755 --- a/.snapcraft/resources/prepareRocketChat +++ b/.snapcraft/resources/prepareRocketChat @@ -1,6 +1,6 @@ #!/bin/bash -curl -SLf "https://releases.rocket.chat/4.0.4/download/" -o rocket.chat.tgz +curl -SLf "https://releases.rocket.chat/4.0.5/download/" -o rocket.chat.tgz tar xf rocket.chat.tgz --strip 1 diff --git a/.snapcraft/snap/snapcraft.yaml b/.snapcraft/snap/snapcraft.yaml index 5d8099b0f6a32..1077109f26580 100644 --- a/.snapcraft/snap/snapcraft.yaml +++ b/.snapcraft/snap/snapcraft.yaml @@ -7,7 +7,7 @@ # 5. `snapcraft snap` name: rocketchat-server -version: 4.0.4 +version: 4.0.5 summary: Rocket.Chat server description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ confinement: strict diff --git a/HISTORY.md b/HISTORY.md index f8f83f1cb7821..8da727fb41a02 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,4 +1,22 @@ +# 4.0.5 +`2021-10-25 · 1 🐛 · 1 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.22.1` +- NPM: `6.14.1` +- MongoDB: `3.6, 4.0, 4.2, 4.4, 5.0` +- Apps-Engine: `1.28.0` + +### 🐛 Bug fixes + + +- OAuth login not working on mobile app ([#23541](https://github.com/RocketChat/Rocket.Chat/pull/23541)) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) + # 4.0.4 `2021-10-21 · 2 🐛 · 3 👩‍💻👨‍💻` @@ -14857,7 +14875,7 @@ 🔍 Minor changes -- Add reetp to the issues' bot whitelist ([#12227](https://github.com/RocketChat/Rocket.Chat/pull/12227)) +- Add reetp to the issues' bot whitelist ([#12227](https://github.com/RocketChat/Rocket.Chat/pull/12227) by [@theorenck](https://github.com/theorenck)) - Fix: Remove semver satisfies from Apps details that is already done my marketplace ([#12268](https://github.com/RocketChat/Rocket.Chat/pull/12268)) @@ -14865,7 +14883,7 @@ - Regression: fix modal submit ([#12233](https://github.com/RocketChat/Rocket.Chat/pull/12233)) -- Release 0.70.1 ([#12270](https://github.com/RocketChat/Rocket.Chat/pull/12270) by [@Hudell](https://github.com/Hudell) & [@edzluhan](https://github.com/edzluhan)) +- Release 0.70.1 ([#12270](https://github.com/RocketChat/Rocket.Chat/pull/12270) by [@Hudell](https://github.com/Hudell) & [@edzluhan](https://github.com/edzluhan) & [@theorenck](https://github.com/theorenck))
@@ -14875,6 +14893,7 @@ - [@cardoso](https://github.com/cardoso) - [@edzluhan](https://github.com/edzluhan) - [@kaiiiiiiiii](https://github.com/kaiiiiiiiii) +- [@theorenck](https://github.com/theorenck) - [@timkinnane](https://github.com/timkinnane) ### 👩‍💻👨‍💻 Core Team 🤓 @@ -14884,7 +14903,6 @@ - [@rodrigok](https://github.com/rodrigok) - [@sampaiodiego](https://github.com/sampaiodiego) - [@tassoevan](https://github.com/tassoevan) -- [@theorenck](https://github.com/theorenck) # 0.70.0 `2018-09-28 · 2 ️️️⚠️ · 18 🎉 · 3 🚀 · 35 🐛 · 19 🔍 · 32 👩‍💻👨‍💻` diff --git a/app/utils/rocketchat.info b/app/utils/rocketchat.info index 68923f220d400..0d398a0701317 100644 --- a/app/utils/rocketchat.info +++ b/app/utils/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "4.0.4" + "version": "4.0.5" } diff --git a/package-lock.json b/package-lock.json index 6c811a5aacc20..b866d0fd285e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "Rocket.Chat", - "version": "4.0.4", + "version": "4.0.5", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index f5152b781571d..39b401e91fede 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Rocket.Chat", "description": "The Ultimate Open Source WebChat Platform", - "version": "4.0.4", + "version": "4.0.5", "author": { "name": "Rocket.Chat", "url": "https://rocket.chat/" From 0d040496334052f90c7545b0e74ad4cf93ce2b93 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Mon, 25 Oct 2021 18:16:53 -0300 Subject: [PATCH 026/137] Regression: Mail body contains `undefined` text (#23552) Co-authored-by: Diego Sampaio --- .mocharc.js | 1 + app/lib/server/index.js | 1 - app/lib/startup/index.js | 4 - app/mailer/server/api.js | 155 ------------- app/mailer/server/api.ts | 212 ++++++++++++++++++ app/mailer/server/replaceVariables.ts | 5 + app/mailer/server/utils.js | 1 - .../tests/{api.tests.js => api.spec.ts} | 26 ++- 8 files changed, 232 insertions(+), 173 deletions(-) delete mode 100644 app/lib/startup/index.js delete mode 100644 app/mailer/server/api.js create mode 100644 app/mailer/server/api.ts create mode 100644 app/mailer/server/replaceVariables.ts delete mode 100644 app/mailer/server/utils.js rename app/mailer/tests/{api.tests.js => api.spec.ts} (54%) diff --git a/.mocharc.js b/.mocharc.js index bd3bd56e3c0ed..c939a10c0cc5d 100644 --- a/.mocharc.js +++ b/.mocharc.js @@ -11,6 +11,7 @@ module.exports = { spec: [ 'app/**/*.tests.js', 'app/**/*.tests.ts', + 'app/**/*.spec.ts', 'server/**/*.tests.ts', 'client/**/*.spec.ts', ], diff --git a/app/lib/server/index.js b/app/lib/server/index.js index ed1b32afd5a2a..0a0e59bc49b6c 100644 --- a/app/lib/server/index.js +++ b/app/lib/server/index.js @@ -7,7 +7,6 @@ import './startup/settingsOnLoadCdnPrefix'; import './startup/settingsOnLoadDirectReply'; import './startup/settingsOnLoadSMTP'; import '../lib/MessageTypes'; -import '../startup'; import '../startup/defaultRoomTypes'; import './lib/bugsnag'; import './lib/debug'; diff --git a/app/lib/startup/index.js b/app/lib/startup/index.js deleted file mode 100644 index 06021c7418326..0000000000000 --- a/app/lib/startup/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import * as Mailer from '../../mailer'; -import { settings } from '../../settings'; - -Mailer.setSettings(settings); diff --git a/app/mailer/server/api.js b/app/mailer/server/api.js deleted file mode 100644 index 4be41b050884e..0000000000000 --- a/app/mailer/server/api.js +++ /dev/null @@ -1,155 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { Email } from 'meteor/email'; -import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; -import _ from 'underscore'; -import s from 'underscore.string'; -import juice from 'juice'; -import stripHtml from 'string-strip-html'; -import { escapeHTML } from '@rocket.chat/string-helpers'; - -import { settings } from '../../settings/server'; -import { replaceVariables } from './utils.js'; - -let contentHeader; -let contentFooter; - -let body; -let Settings = { - get: () => {}, -}; - -// define server language for email translations -// @TODO: change TAPi18n.__ function to use the server language by default -let lng = 'en'; -settings.watch('Language', (value) => { - lng = value || 'en'; -}); - - -export const replacekey = (str, key, value = '') => str.replace( - new RegExp(`(\\[${ key }\\]|__${ key }__)`, 'igm'), - value, -); - -export const translate = (str) => replaceVariables(str, (match, key) => TAPi18n.__(key, { lng })); -export const replace = function replace(str, data = {}) { - if (!str) { - return ''; - } - const options = { - Site_Name: Settings.get('Site_Name'), - Site_URL: Settings.get('Site_Url'), - Site_URL_Slash: Settings.get('Site_Url')?.replace(/\/?$/, '/'), - ...data.name && { - fname: s.strLeft(data.name, ' '), - lname: s.strRightBack(data.name, ' '), - }, - ...data, - }; - return Object.entries(options).reduce((ret, [key, value]) => replacekey(ret, key, value), translate(str)); -}; - -const nonEscapeKeys = ['room_path']; - -export const replaceEscaped = (str, data = {}) => replace(str, { - Site_Name: escapeHTML(settings.get('Site_Name')), - Site_Url: escapeHTML(settings.get('Site_Url')), - ...Object.entries(data).reduce((ret, [key, value]) => { - ret[key] = nonEscapeKeys.includes(key) ? value : escapeHTML(value); - return ret; - }, {}), -}); - -export const wrap = (html, data = {}) => { - if (settings.get('email_plain_text_only')) { - return replace(html, data); - } - - return replaceEscaped(body.replace('{{body}}', html), data); -}; -export const inlinecss = (html) => { - const css = Settings.get('email_style'); - return css ? juice.inlineContent(html, css) : html; -}; -export const getTemplate = (template, fn, escape = true) => { - let html = ''; - Settings.get(template, (key, value) => { - html = value || ''; - fn(escape ? inlinecss(html) : html); - }); - Settings.get('email_style', () => { - fn(escape ? inlinecss(html) : html); - }); -}; -export const getTemplateWrapped = (template, fn) => { - let html = ''; - const wrapInlineCSS = _.debounce(() => fn(wrap(inlinecss(html))), 100); - - Settings.get('Email_Header', () => html && wrapInlineCSS()); - Settings.get('Email_Footer', () => html && wrapInlineCSS()); - Settings.get('email_style', () => html && wrapInlineCSS()); - Settings.get(template, (key, value) => { - html = value || ''; - return html && wrapInlineCSS(); - }); -}; -export const setSettings = (s) => { - Settings = s; - getTemplate('Email_Header', (value) => { - contentHeader = replace(value || ''); - body = inlinecss(`${ contentHeader } {{body}} ${ contentFooter }`); - }, false); - - getTemplate('Email_Footer', (value) => { - contentFooter = replace(value || ''); - body = inlinecss(`${ contentHeader } {{body}} ${ contentFooter }`); - }, false); - - body = inlinecss(`${ contentHeader } {{body}} ${ contentFooter }`); -}; - -export const rfcMailPatternWithName = /^(?:.*<)?([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)(?:>?)$/; - -export const checkAddressFormat = (from) => rfcMailPatternWithName.test(from); - -export const sendNoWrap = ({ to, from, replyTo, subject, html, text, headers }) => { - if (!checkAddressFormat(to)) { - return; - } - - if (!text) { - text = stripHtml(html).result; - } - - if (settings.get('email_plain_text_only')) { - html = undefined; - } - - Meteor.defer(() => Email.send({ to, from, replyTo, subject, html, text, headers })); -}; - -export const send = ({ to, from, replyTo, subject, html, text, data, headers }) => - sendNoWrap({ - to, - from, - replyTo, - subject: replace(subject, data), - text: text - ? replace(text, data) - : stripHtml(replace(html, data)).result, - html: wrap(html, data), - headers, - }); - -export const checkAddressFormatAndThrow = (from, func) => { - if (checkAddressFormat(from)) { - return true; - } - throw new Meteor.Error('error-invalid-from-address', 'Invalid from address', { - function: func, - }); -}; - -export const getHeader = () => contentHeader; - -export const getFooter = () => contentFooter; diff --git a/app/mailer/server/api.ts b/app/mailer/server/api.ts new file mode 100644 index 0000000000000..fe6c593e18d34 --- /dev/null +++ b/app/mailer/server/api.ts @@ -0,0 +1,212 @@ +import { Meteor } from 'meteor/meteor'; +import { Email } from 'meteor/email'; +import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; +import _ from 'underscore'; +import s from 'underscore.string'; +import juice from 'juice'; +import stripHtml from 'string-strip-html'; +import { escapeHTML } from '@rocket.chat/string-helpers'; + +import { settings } from '../../settings/server'; +import { ISetting } from '../../../definition/ISetting'; +import { replaceVariables } from './replaceVariables'; + +let contentHeader: string | undefined; +let contentFooter: string | undefined; +let body: string | undefined; + +// define server language for email translations +// @TODO: change TAPi18n.__ function to use the server language by default +let lng = 'en'; +settings.watch('Language', (value) => { + lng = value || 'en'; +}); + +export const replacekey = (str: string, key: string, value = ''): string => + str.replace( + new RegExp(`(\\[${ key }\\]|__${ key }__)`, 'igm'), + value, + ); + +export const translate = (str: string): string => + replaceVariables(str, (_match, key) => TAPi18n.__(key, { lng })); + +export const replace = (str: string, data: { [key: string]: unknown } = {}): string => { + if (!str) { + return ''; + } + + const options = { + // eslint-disable-next-line @typescript-eslint/camelcase + Site_Name: settings.get('Site_Name'), + // eslint-disable-next-line @typescript-eslint/camelcase + Site_URL: settings.get('Site_Url'), + // eslint-disable-next-line @typescript-eslint/camelcase + Site_URL_Slash: settings.get('Site_Url')?.replace(/\/?$/, '/'), + ...data.name ? { + fname: s.strLeft(String(data.name), ' '), + lname: s.strRightBack(String(data.name), ' '), + } : {}, + ...data, + }; + + return Object.entries(options) + .reduce((ret, [key, value]) => replacekey(ret, key, value), translate(str)); +}; + +const nonEscapeKeys = ['room_path']; + +export const replaceEscaped = (str: string, data: { [key: string]: unknown } = {}): string => { + const siteName = settings.get('Site_Name'); + const siteUrl = settings.get('Site_Url'); + + return replace(str, { + // eslint-disable-next-line @typescript-eslint/camelcase + Site_Name: siteName ? escapeHTML(siteName) : undefined, + // eslint-disable-next-line @typescript-eslint/camelcase + Site_Url: siteUrl ? escapeHTML(siteUrl) : undefined, + ...Object.entries(data).reduce<{[key: string]: string}>((ret, [key, value]) => { + if (value !== undefined && value !== null) { + ret[key] = nonEscapeKeys.includes(key) ? String(value) : escapeHTML(String(value)); + } + return ret; + }, {}), + }); +}; + +export const wrap = (html: string, data: { [key: string]: unknown } = {}): string => { + if (settings.get('email_plain_text_only')) { + return replace(html, data); + } + + if (!body) { + throw new Error('`body` is not set yet'); + } + + return replaceEscaped(body.replace('{{body}}', html), data); +}; +export const inlinecss = (html: string): string => { + const css = settings.get('email_style'); + return css ? juice.inlineContent(html, css) : html; +}; + +export const getTemplate = (template: ISetting['_id'], fn: (html: string) => void, escape = true): void => { + let html = ''; + + settings.watch(template, (value) => { + html = value || ''; + fn(escape ? inlinecss(html) : html); + }); + + settings.watch('email_style', () => { + fn(escape ? inlinecss(html) : html); + }); +}; + +export const getTemplateWrapped = (template: ISetting['_id'], fn: (html: string) => void): void => { + let html = ''; + const wrapInlineCSS = _.debounce(() => fn(wrap(inlinecss(html))), 100); + + settings.watch('Email_Header', () => html && wrapInlineCSS()); + settings.watch('Email_Footer', () => html && wrapInlineCSS()); + settings.watch('email_style', () => html && wrapInlineCSS()); + settings.watch(template, (value) => { + html = value || ''; + return html && wrapInlineCSS(); + }); +}; + +settings.watchMultiple(['Email_Header', 'Email_Footer'], () => { + getTemplate('Email_Header', (value) => { + contentHeader = replace(value || ''); + body = inlinecss(`${ contentHeader } {{body}} ${ contentFooter }`); + }, false); + + getTemplate('Email_Footer', (value) => { + contentFooter = replace(value || ''); + body = inlinecss(`${ contentHeader } {{body}} ${ contentFooter }`); + }, false); + + body = inlinecss(`${ contentHeader } {{body}} ${ contentFooter }`); +}); + +export const rfcMailPatternWithName = /^(?:.*<)?([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)(?:>?)$/; + +export const checkAddressFormat = (from: string): boolean => rfcMailPatternWithName.test(from); + +export const sendNoWrap = ({ + to, + from, + replyTo, + subject, + html, + text, + headers, +}: { + to: string; + from: string; + replyTo?: string; + subject: string; + html?: string; + text?: string; + headers?: string; +}): void => { + if (!checkAddressFormat(to)) { + return; + } + + if (!text) { + text = html ? stripHtml(html).result : undefined; + } + + if (settings.get('email_plain_text_only')) { + html = undefined; + } + + Meteor.defer(() => Email.send({ to, from, replyTo, subject, html, text, headers })); +}; + +export const send = ({ + to, + from, + replyTo, + subject, + html, + text, + data, + headers, +}: { + to: string; + from: string; + replyTo?: string; + subject: string; + html?: string; + text?: string; + headers?: string; + data?: { [key: string]: unknown }; +}): void => + sendNoWrap({ + to, + from, + replyTo, + subject: replace(subject, data), + text: (text && replace(text, data)) + || (html && stripHtml(replace(html, data)).result) + || undefined, + html: html ? wrap(html, data) : undefined, + headers, + }); + +export const checkAddressFormatAndThrow = (from: string, func: Function): asserts from => { + if (checkAddressFormat(from)) { + return; + } + + throw new Meteor.Error('error-invalid-from-address', 'Invalid from address', { + function: func, + }); +}; + +export const getHeader = (): string | undefined => contentHeader; + +export const getFooter = (): string | undefined => contentFooter; diff --git a/app/mailer/server/replaceVariables.ts b/app/mailer/server/replaceVariables.ts new file mode 100644 index 0000000000000..4681fd1135c3e --- /dev/null +++ b/app/mailer/server/replaceVariables.ts @@ -0,0 +1,5 @@ +export const replaceVariables = ( + str: string, + replacer: (substring: string, key: string) => string, +): string => + str.replace(/\{ *([^\{\} ]+)[^\{\}]*\}/gmi, replacer); diff --git a/app/mailer/server/utils.js b/app/mailer/server/utils.js deleted file mode 100644 index 796215bd89c73..0000000000000 --- a/app/mailer/server/utils.js +++ /dev/null @@ -1 +0,0 @@ -export const replaceVariables = (str, callback) => str.replace(/\{ *([^\{\} ]+)[^\{\}]*\}/gmi, callback); diff --git a/app/mailer/tests/api.tests.js b/app/mailer/tests/api.spec.ts similarity index 54% rename from app/mailer/tests/api.tests.js rename to app/mailer/tests/api.spec.ts index 6ecf3b9f9de17..4c858c1b23e58 100644 --- a/app/mailer/tests/api.tests.js +++ b/app/mailer/tests/api.spec.ts @@ -1,65 +1,67 @@ /* eslint-env mocha */ import assert from 'assert'; -import { replaceVariables } from '../server/utils.js'; +import { replaceVariables } from '../server/replaceVariables'; describe('Mailer-API', function() { - describe('translate', () => { - const i18n = { + describe('replaceVariables', () => { + const i18n: { + [key: string]: string; + } = { key: 'value', }; describe('single key', function functionName() { it(`should be equal to test ${ i18n.key }`, () => { - assert.equal(`test ${ i18n.key }`, replaceVariables('test {key}', (match, key) => i18n[key])); + assert.strictEqual(`test ${ i18n.key }`, replaceVariables('test {key}', (_match, key) => i18n[key])); }); }); describe('multiple keys', function functionName() { it(`should be equal to test ${ i18n.key } and ${ i18n.key }`, () => { - assert.equal(`test ${ i18n.key } and ${ i18n.key }`, replaceVariables('test {key} and {key}', (match, key) => i18n[key])); + assert.strictEqual(`test ${ i18n.key } and ${ i18n.key }`, replaceVariables('test {key} and {key}', (_match, key) => i18n[key])); }); }); describe('key with a trailing space', function functionName() { it(`should be equal to test ${ i18n.key }`, () => { - assert.equal(`test ${ i18n.key }`, replaceVariables('test {key }', (match, key) => i18n[key])); + assert.strictEqual(`test ${ i18n.key }`, replaceVariables('test {key }', (_match, key) => i18n[key])); }); }); describe('key with a leading space', function functionName() { it(`should be equal to test ${ i18n.key }`, () => { - assert.equal(`test ${ i18n.key }`, replaceVariables('test { key}', (match, key) => i18n[key])); + assert.strictEqual(`test ${ i18n.key }`, replaceVariables('test { key}', (_match, key) => i18n[key])); }); }); describe('key with leading and trailing spaces', function functionName() { it(`should be equal to test ${ i18n.key }`, () => { - assert.equal(`test ${ i18n.key }`, replaceVariables('test { key }', (match, key) => i18n[key])); + assert.strictEqual(`test ${ i18n.key }`, replaceVariables('test { key }', (_match, key) => i18n[key])); }); }); describe('key with multiple words', function functionName() { it(`should be equal to test ${ i18n.key }`, () => { - assert.equal(`test ${ i18n.key }`, replaceVariables('test {key ignore}', (match, key) => i18n[key])); + assert.strictEqual(`test ${ i18n.key }`, replaceVariables('test {key ignore}', (_match, key) => i18n[key])); }); }); describe('key with multiple opening brackets', function functionName() { it(`should be equal to test {${ i18n.key }`, () => { - assert.equal(`test {${ i18n.key }`, replaceVariables('test {{key}', (match, key) => i18n[key])); + assert.strictEqual(`test {${ i18n.key }`, replaceVariables('test {{key}', (_match, key) => i18n[key])); }); }); describe('key with multiple closing brackets', function functionName() { it(`should be equal to test ${ i18n.key }}`, () => { - assert.equal(`test ${ i18n.key }}`, replaceVariables('test {key}}', (match, key) => i18n[key])); + assert.strictEqual(`test ${ i18n.key }}`, replaceVariables('test {key}}', (_match, key) => i18n[key])); }); }); describe('key with multiple opening and closing brackets', function functionName() { it(`should be equal to test {${ i18n.key }}`, () => { - assert.equal(`test {${ i18n.key }}`, replaceVariables('test {{key}}', (match, key) => i18n[key])); + assert.strictEqual(`test {${ i18n.key }}`, replaceVariables('test {{key}}', (_match, key) => i18n[key])); }); }); }); From d972826497abfda00b2855955800f7ff52ed0d37 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Mon, 25 Oct 2021 18:19:24 -0300 Subject: [PATCH 027/137] Bump version to 4.1.0-rc.2 --- .docker/Dockerfile.rhel | 2 +- .github/history.json | 109 +++++++++++++++++++++++++ .snapcraft/resources/prepareRocketChat | 2 +- .snapcraft/snap/snapcraft.yaml | 2 +- HISTORY.md | 95 ++++++++++++++++++++- app/utils/rocketchat.info | 2 +- package-lock.json | 2 +- package.json | 2 +- 8 files changed, 207 insertions(+), 9 deletions(-) diff --git a/.docker/Dockerfile.rhel b/.docker/Dockerfile.rhel index bd0da27f32289..7423ccc886e5d 100644 --- a/.docker/Dockerfile.rhel +++ b/.docker/Dockerfile.rhel @@ -1,6 +1,6 @@ FROM registry.access.redhat.com/ubi8/nodejs-12 -ENV RC_VERSION 4.1.0-rc.1 +ENV RC_VERSION 4.1.0-rc.2 MAINTAINER buildmaster@rocket.chat diff --git a/.github/history.json b/.github/history.json index b5c949cc50e32..5da1a7c53b85d 100644 --- a/.github/history.json +++ b/.github/history.json @@ -66849,6 +66849,115 @@ ] } ] + }, + "4.0.4": { + "node_version": "12.22.1", + "npm_version": "6.14.1", + "apps_engine_version": "1.28.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "23532", + "title": "Release 4.0.4", + "userLogin": "sampaiodiego", + "contributors": [ + "KevLehman", + "sampaiodiego", + "matheusbsilva137" + ] + }, + { + "pr": "23411", + "title": "[FIX] SAML Users' roles being reset to default on login", + "userLogin": "matheusbsilva137", + "description": "- Remove `roles` field update on `insertOrUpdateSAMLUser` function;\r\n- Add SAML `syncRoles` event;", + "milestone": "4.0.4", + "contributors": [ + "matheusbsilva137", + "pierre-lehnen-rc" + ] + }, + { + "pr": "23522", + "title": "[FIX] Queue error handling and unlocking behavior", + "userLogin": "KevLehman", + "milestone": "4.0.4", + "contributors": [ + "KevLehman" + ] + } + ] + }, + "4.0.5": { + "node_version": "12.22.1", + "npm_version": "6.14.1", + "apps_engine_version": "1.28.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "23554", + "title": "Release 4.0.5", + "userLogin": "sampaiodiego", + "contributors": [ + "pierre-lehnen-rc", + "sampaiodiego" + ] + }, + { + "pr": "23541", + "title": "[FIX] OAuth login not working on mobile app", + "userLogin": "pierre-lehnen-rc", + "milestone": "4.0.5", + "contributors": [ + "pierre-lehnen-rc" + ] + } + ] + }, + "4.1.0-rc.2": { + "node_version": "12.22.1", + "npm_version": "6.14.1", + "apps_engine_version": "1.28.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "23552", + "title": "Regression: Mail body contains `undefined` text", + "userLogin": "tassoevan", + "description": "### Before\r\n![image](https://user-images.githubusercontent.com/2263066/138733018-10449892-5c2d-46fb-9355-00e98e0d6c9f.png)\r\n\r\n### After\r\n![image](https://user-images.githubusercontent.com/2263066/138733074-a1b88a77-bf64-41c3-a6c3-ac9e1cb63de1.png)", + "contributors": [ + "tassoevan", + "sampaiodiego" + ] + }, + { + "pr": "23541", + "title": "[FIX] OAuth login not working on mobile app", + "userLogin": "pierre-lehnen-rc", + "milestone": "4.0.5", + "contributors": [ + "pierre-lehnen-rc" + ] + } + ] } } } \ No newline at end of file diff --git a/.snapcraft/resources/prepareRocketChat b/.snapcraft/resources/prepareRocketChat index ec80c97638f9d..57d9849e3d4fc 100755 --- a/.snapcraft/resources/prepareRocketChat +++ b/.snapcraft/resources/prepareRocketChat @@ -1,6 +1,6 @@ #!/bin/bash -curl -SLf "https://releases.rocket.chat/4.1.0-rc.1/download/" -o rocket.chat.tgz +curl -SLf "https://releases.rocket.chat/4.1.0-rc.2/download/" -o rocket.chat.tgz tar xf rocket.chat.tgz --strip 1 diff --git a/.snapcraft/snap/snapcraft.yaml b/.snapcraft/snap/snapcraft.yaml index 9d66e78bd1d42..783af012953ae 100644 --- a/.snapcraft/snap/snapcraft.yaml +++ b/.snapcraft/snap/snapcraft.yaml @@ -7,7 +7,7 @@ # 5. `snapcraft snap` name: rocketchat-server -version: 4.1.0-rc.1 +version: 4.1.0-rc.2 summary: Rocket.Chat server description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ confinement: strict diff --git a/HISTORY.md b/HISTORY.md index daa30a4cdb274..b04da2665dd11 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,34 @@ # 4.1.0 (Under Release Candidate Process) +## 4.1.0-rc.2 +`2021-10-25 · 1 🐛 · 1 🔍 · 3 👩‍💻👨‍💻` + +### 🐛 Bug fixes + + +- OAuth login not working on mobile app ([#23541](https://github.com/RocketChat/Rocket.Chat/pull/23541)) + +
+🔍 Minor changes + + +- Regression: Mail body contains `undefined` text ([#23552](https://github.com/RocketChat/Rocket.Chat/pull/23552)) + + ### Before + ![image](https://user-images.githubusercontent.com/2263066/138733018-10449892-5c2d-46fb-9355-00e98e0d6c9f.png) + + ### After + ![image](https://user-images.githubusercontent.com/2263066/138733074-a1b88a77-bf64-41c3-a6c3-ac9e1cb63de1.png) + +
+ +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) +- [@sampaiodiego](https://github.com/sampaiodiego) +- [@tassoevan](https://github.com/tassoevan) + ## 4.1.0-rc.1 `2021-10-21 · 3 🔍 · 2 👩‍💻👨‍💻` @@ -257,6 +285,67 @@ - [@thassiov](https://github.com/thassiov) - [@tiagoevanp](https://github.com/tiagoevanp) +# 4.0.5 +`2021-10-25 · 1 🐛 · 1 🔍 · 2 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.22.1` +- NPM: `6.14.1` +- MongoDB: `3.6, 4.0, 4.2, 4.4, 5.0` +- Apps-Engine: `1.28.0` + +### 🐛 Bug fixes + + +- OAuth login not working on mobile app ([#23541](https://github.com/RocketChat/Rocket.Chat/pull/23541)) + +
+🔍 Minor changes + + +- Release 4.0.5 ([#23554](https://github.com/RocketChat/Rocket.Chat/pull/23554)) + +
+ +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) +- [@sampaiodiego](https://github.com/sampaiodiego) + +# 4.0.4 +`2021-10-21 · 2 🐛 · 1 🔍 · 4 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.22.1` +- NPM: `6.14.1` +- MongoDB: `3.6, 4.0, 4.2, 4.4, 5.0` +- Apps-Engine: `1.28.0` + +### 🐛 Bug fixes + + +- Queue error handling and unlocking behavior ([#23522](https://github.com/RocketChat/Rocket.Chat/pull/23522)) + +- SAML Users' roles being reset to default on login ([#23411](https://github.com/RocketChat/Rocket.Chat/pull/23411)) + + - Remove `roles` field update on `insertOrUpdateSAMLUser` function; + - Add SAML `syncRoles` event; + +
+🔍 Minor changes + + +- Release 4.0.4 ([#23532](https://github.com/RocketChat/Rocket.Chat/pull/23532)) + +
+ +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@KevLehman](https://github.com/KevLehman) +- [@matheusbsilva137](https://github.com/matheusbsilva137) +- [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) +- [@sampaiodiego](https://github.com/sampaiodiego) + # 4.0.3 `2021-10-18 · 2 🐛 · 1 🔍 · 3 👩‍💻👨‍💻` @@ -15112,7 +15201,7 @@ 🔍 Minor changes -- Add reetp to the issues' bot whitelist ([#12227](https://github.com/RocketChat/Rocket.Chat/pull/12227)) +- Add reetp to the issues' bot whitelist ([#12227](https://github.com/RocketChat/Rocket.Chat/pull/12227) by [@theorenck](https://github.com/theorenck)) - Fix: Remove semver satisfies from Apps details that is already done my marketplace ([#12268](https://github.com/RocketChat/Rocket.Chat/pull/12268)) @@ -15120,7 +15209,7 @@ - Regression: fix modal submit ([#12233](https://github.com/RocketChat/Rocket.Chat/pull/12233)) -- Release 0.70.1 ([#12270](https://github.com/RocketChat/Rocket.Chat/pull/12270) by [@Hudell](https://github.com/Hudell) & [@edzluhan](https://github.com/edzluhan)) +- Release 0.70.1 ([#12270](https://github.com/RocketChat/Rocket.Chat/pull/12270) by [@Hudell](https://github.com/Hudell) & [@edzluhan](https://github.com/edzluhan) & [@theorenck](https://github.com/theorenck))
@@ -15130,6 +15219,7 @@ - [@cardoso](https://github.com/cardoso) - [@edzluhan](https://github.com/edzluhan) - [@kaiiiiiiiii](https://github.com/kaiiiiiiiii) +- [@theorenck](https://github.com/theorenck) - [@timkinnane](https://github.com/timkinnane) ### 👩‍💻👨‍💻 Core Team 🤓 @@ -15139,7 +15229,6 @@ - [@rodrigok](https://github.com/rodrigok) - [@sampaiodiego](https://github.com/sampaiodiego) - [@tassoevan](https://github.com/tassoevan) -- [@theorenck](https://github.com/theorenck) # 0.70.0 `2018-09-28 · 2 ️️️⚠️ · 18 🎉 · 3 🚀 · 35 🐛 · 19 🔍 · 32 👩‍💻👨‍💻` diff --git a/app/utils/rocketchat.info b/app/utils/rocketchat.info index 44d60f6858fa7..ab3f873bf3cfc 100644 --- a/app/utils/rocketchat.info +++ b/app/utils/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "4.1.0-rc.1" + "version": "4.1.0-rc.2" } diff --git a/package-lock.json b/package-lock.json index 2e1aa932c01f4..d20f9a3f19e1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "Rocket.Chat", - "version": "4.1.0-rc.1", + "version": "4.1.0-rc.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index deaa3f77e1718..2186b7a9007fc 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Rocket.Chat", "description": "The Ultimate Open Source WebChat Platform", - "version": "4.1.0-rc.1", + "version": "4.1.0-rc.2", "author": { "name": "Rocket.Chat", "url": "https://rocket.chat/" From ccbe0620bf829ef64c55ade6bc512736c48b9aff Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Tue, 26 Oct 2021 09:44:52 -0300 Subject: [PATCH 028/137] Start scheduler after apps have been loaded (#23539) --- app/apps/server/bridges/scheduler.ts | 2 +- app/apps/server/orchestrator.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/apps/server/bridges/scheduler.ts b/app/apps/server/bridges/scheduler.ts index 698931a25be22..721c3a8c8aa07 100644 --- a/app/apps/server/bridges/scheduler.ts +++ b/app/apps/server/bridges/scheduler.ts @@ -198,7 +198,7 @@ export class AppSchedulerBridge extends SchedulerBridge { } } - private async startScheduler(): Promise { + public async startScheduler(): Promise { if (!this.isConnected) { await this.scheduler.start(); this.isConnected = true; diff --git a/app/apps/server/orchestrator.js b/app/apps/server/orchestrator.js index a68d24197f56b..81eaad8110d8b 100644 --- a/app/apps/server/orchestrator.js +++ b/app/apps/server/orchestrator.js @@ -158,7 +158,8 @@ export class AppServerOrchestrator { return this._manager.load() .then((affs) => console.log(`Loaded the Apps Framework and loaded a total of ${ affs.length } Apps!`)) - .catch((err) => console.warn('Failed to load the Apps Framework and Apps!', err)); + .catch((err) => console.warn('Failed to load the Apps Framework and Apps!', err)) + .then(() => this.getBridges().getSchedulerBridge().startScheduler()); } async unload() { From 32d78f6dab100619f31089994688c21af3281d70 Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Tue, 26 Oct 2021 11:37:41 -0300 Subject: [PATCH 029/137] Revert "Start scheduler after apps have been loaded (#23539)" (#23564) This reverts commit ccbe0620bf829ef64c55ade6bc512736c48b9aff. --- app/apps/server/bridges/scheduler.ts | 2 +- app/apps/server/orchestrator.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/apps/server/bridges/scheduler.ts b/app/apps/server/bridges/scheduler.ts index 721c3a8c8aa07..698931a25be22 100644 --- a/app/apps/server/bridges/scheduler.ts +++ b/app/apps/server/bridges/scheduler.ts @@ -198,7 +198,7 @@ export class AppSchedulerBridge extends SchedulerBridge { } } - public async startScheduler(): Promise { + private async startScheduler(): Promise { if (!this.isConnected) { await this.scheduler.start(); this.isConnected = true; diff --git a/app/apps/server/orchestrator.js b/app/apps/server/orchestrator.js index 81eaad8110d8b..a68d24197f56b 100644 --- a/app/apps/server/orchestrator.js +++ b/app/apps/server/orchestrator.js @@ -158,8 +158,7 @@ export class AppServerOrchestrator { return this._manager.load() .then((affs) => console.log(`Loaded the Apps Framework and loaded a total of ${ affs.length } Apps!`)) - .catch((err) => console.warn('Failed to load the Apps Framework and Apps!', err)) - .then(() => this.getBridges().getSchedulerBridge().startScheduler()); + .catch((err) => console.warn('Failed to load the Apps Framework and Apps!', err)); } async unload() { From d21b9cfb0dfe94490714b19993d672a2f57a1dd3 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Wed, 27 Oct 2021 17:50:07 -0300 Subject: [PATCH 030/137] Bump: fuselage 0.30.1 (#23391) Co-authored-by: dougfabris --- package-lock.json | 197 ++++++++++++++++++++-------------------------- package.json | 26 +++--- 2 files changed, 99 insertions(+), 124 deletions(-) diff --git a/package-lock.json b/package-lock.json index e435c334b6014..e62fd1de74650 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5331,23 +5331,23 @@ } }, "@rocket.chat/css-in-js": { - "version": "0.6.3-dev.322", - "resolved": "https://registry.npmjs.org/@rocket.chat/css-in-js/-/css-in-js-0.6.3-dev.322.tgz", - "integrity": "sha512-LzlPLlMfO/Brnl7oXe/dWezvsVqUAvu9N8KThp1e9ab0YbQzErOkn0KGzIxKgeFinjVPcdLjPRmawyFItK6yrg==", + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@rocket.chat/css-in-js/-/css-in-js-0.30.1.tgz", + "integrity": "sha512-CrMZioM1c8UAniCjDocjoFfapnJJDlapYraWoUZ9Q9wt5i6N3cY5m0nBUX2qGelz+fcPXdenVBnkmBo7yo80ag==", "requires": { "@emotion/hash": "^0.8.0", - "@rocket.chat/css-supports": "^0.6.3-dev.322+b067cee6", - "@rocket.chat/memo": "^0.6.3-dev.322+b067cee6", - "@rocket.chat/stylis-logical-props-middleware": "^0.6.3-dev.322+b067cee6", + "@rocket.chat/css-supports": "^0.30.0", + "@rocket.chat/memo": "^0.30.0", + "@rocket.chat/stylis-logical-props-middleware": "^0.30.0", "stylis": "^4.0.10" } }, "@rocket.chat/css-supports": { - "version": "0.6.3-dev.322", - "resolved": "https://registry.npmjs.org/@rocket.chat/css-supports/-/css-supports-0.6.3-dev.322.tgz", - "integrity": "sha512-86hrV7ctnEu5ancYYkEWI4dS82MQEE5aI1Qln0Vnsc4NstDDvUsc9i5h6hy1RKqekRRpn+HhX0Q3Lh2/4K3wCA==", + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@rocket.chat/css-supports/-/css-supports-0.30.1.tgz", + "integrity": "sha512-Vu+BBoS4qPIOI17Tva1DhvA/ZB1+PThQFl6aA5Sm/FMC588dAIpA5MzGi9qcKG4tZvdua4T7YLJ5FvP3YUYGOQ==", "requires": { - "@rocket.chat/memo": "^0.6.3-dev.322+b067cee6", + "@rocket.chat/memo": "^0.30.0", "tslib": "^2.3.1" }, "dependencies": { @@ -5359,9 +5359,9 @@ } }, "@rocket.chat/emitter": { - "version": "0.6.3-dev.322", - "resolved": "https://registry.npmjs.org/@rocket.chat/emitter/-/emitter-0.6.3-dev.322.tgz", - "integrity": "sha512-ytprj6pgVdHdxL3dpmeJq/IUd3cLpvaF/OV1uVJ3XtRYqvVnMtqfDXS41AoTlAVFYsD9swqrZBIigxdD4Q9t5g==" + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@rocket.chat/emitter/-/emitter-0.30.1.tgz", + "integrity": "sha512-BH1wMBo5AZwgWXIRm4k2M5rz9W7uDR+2xbfo/HP2bIjyTTq9mlU/20w0JBXAR7PaMf8gWgyfAWWGPi4ZBHA+ag==" }, "@rocket.chat/eslint-config": { "version": "0.4.0", @@ -5373,77 +5373,27 @@ } }, "@rocket.chat/fuselage": { - "version": "0.6.3-dev.326", - "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage/-/fuselage-0.6.3-dev.326.tgz", - "integrity": "sha512-t4jEM4kHGjfXgZ5uxickjjUFxJR0G8OIQVadVeX4CdlCX4YtqFbNWEoBvgF1gRCicJFvlJqIADiHtZKGMpZ/kA==", - "requires": { - "@rocket.chat/css-in-js": "^0.6.3-dev.326+35894d44", - "@rocket.chat/css-supports": "^0.6.3-dev.326+35894d44", - "@rocket.chat/fuselage-tokens": "^0.6.3-dev.326+35894d44", - "@rocket.chat/memo": "^0.6.3-dev.326+35894d44", + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage/-/fuselage-0.30.1.tgz", + "integrity": "sha512-tO178KfR8GFRuIm/2Lv5f0mKoHS4ZSKAJB6jE5UOrGehDN4kjJ8lKcyCoF24yBKcp/3q8ZbkNTgyGY1nB+jhlQ==", + "requires": { + "@rocket.chat/css-in-js": "^0.30.0", + "@rocket.chat/css-supports": "^0.30.0", + "@rocket.chat/fuselage-tokens": "^0.30.0", + "@rocket.chat/memo": "^0.30.0", "invariant": "^2.2.4", "react-keyed-flatten-children": "^1.3.0" - }, - "dependencies": { - "@rocket.chat/css-in-js": { - "version": "0.6.3-dev.326", - "resolved": "https://registry.npmjs.org/@rocket.chat/css-in-js/-/css-in-js-0.6.3-dev.326.tgz", - "integrity": "sha512-JPsMfWhm87CmCUVGWgICfjoH2egAZSqqoMNdWNRgWm7H367wzcP51uLu2YF3HMEPa1la3uDE7GNPX1RzQ4TI6w==", - "requires": { - "@emotion/hash": "^0.8.0", - "@rocket.chat/css-supports": "^0.6.3-dev.326+35894d44", - "@rocket.chat/memo": "^0.6.3-dev.326+35894d44", - "@rocket.chat/stylis-logical-props-middleware": "^0.6.3-dev.326+35894d44", - "stylis": "^4.0.10" - } - }, - "@rocket.chat/css-supports": { - "version": "0.6.3-dev.326", - "resolved": "https://registry.npmjs.org/@rocket.chat/css-supports/-/css-supports-0.6.3-dev.326.tgz", - "integrity": "sha512-NzTRiGhOadcQr0iHxLzFWy0K9y8+TMxG21IwGEQt+3FPGxfnhGDNkqyWSSwVuMG7xmRZgl/s7iAYAEUlmc06dg==", - "requires": { - "@rocket.chat/memo": "^0.6.3-dev.326+35894d44", - "tslib": "^2.3.1" - } - }, - "@rocket.chat/fuselage-tokens": { - "version": "0.6.3-dev.326", - "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-tokens/-/fuselage-tokens-0.6.3-dev.326.tgz", - "integrity": "sha512-ORltLhsMzifKKz6SCyCwKNx7wXXAkkw2B+wumgpa/nvOXiYiI5gOp4M/04sVvjVIAHo9+gd1LqB1im1LQiQ8TA==" - }, - "@rocket.chat/memo": { - "version": "0.6.3-dev.326", - "resolved": "https://registry.npmjs.org/@rocket.chat/memo/-/memo-0.6.3-dev.326.tgz", - "integrity": "sha512-Mqtk+fNoSB6vjqdQcc8dNlP+E46D6AlVVGYs02+KRomRm5I3Bjto9ZfwVdHPBEopHY04OSyRmDroMGdwxxj2ww==", - "requires": { - "tslib": "^2.3.0" - } - }, - "@rocket.chat/stylis-logical-props-middleware": { - "version": "0.6.3-dev.326", - "resolved": "https://registry.npmjs.org/@rocket.chat/stylis-logical-props-middleware/-/stylis-logical-props-middleware-0.6.3-dev.326.tgz", - "integrity": "sha512-1k0UjTnMKuyCOTzE2bCrssFY64YXHD5hc8rEMIGxlfJ4O70Q8dGV30kg2pKm4yhXHvUnBg3cxtT98nqvN7kBQg==", - "requires": { - "@rocket.chat/css-supports": "^0.6.3-dev.326+35894d44", - "tslib": "^2.2.0" - } - }, - "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" - } } }, "@rocket.chat/fuselage-hooks": { - "version": "0.6.3-dev.322", - "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-hooks/-/fuselage-hooks-0.6.3-dev.322.tgz", - "integrity": "sha512-J2rsvhFPo/by/y2/z6uBTmDuOqAiocXODsYuFUVp4AyNW4LmZ/KD7o9MfPwlwGjlkbU9889JOabhcvgZ3qLkxQ==" + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-hooks/-/fuselage-hooks-0.30.1.tgz", + "integrity": "sha512-9mfe8yx9MjgjYGDKctHqDkV5Byb5PuAXDter1sl37w/Y9gk8TB1tohU+bF88YthBYSLnqBaNWZMsO2zpKuTGiQ==" }, "@rocket.chat/fuselage-polyfills": { - "version": "0.6.3-dev.322", - "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-polyfills/-/fuselage-polyfills-0.6.3-dev.322.tgz", - "integrity": "sha512-8i1i5087FK1t1+ifwLu5lu4LNz370oDhXDKtHL13vYcowE8FVCME12P9rnP349dFmSPQ2ZhpNyDSu9kaUGw6nA==", + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-polyfills/-/fuselage-polyfills-0.30.1.tgz", + "integrity": "sha512-NfEL4mFHsp+mC0txQ6d2DwS/HO3GmrlFScSxXGAPCCbfPkYCnkcRfGGZqztrwjiYhZjbPf318FjTDU4izp3Flw==", "requires": { "@juggle/resize-observer": "^3.3.1", "clipboard-polyfill": "^3.0.3", @@ -5454,19 +5404,33 @@ } }, "@rocket.chat/fuselage-tokens": { - "version": "0.6.3-dev.322", - "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-tokens/-/fuselage-tokens-0.6.3-dev.322.tgz", - "integrity": "sha512-FDbIEUeANKlZ/X6dVNwZwBHphVoTJRnHuYQUH1Uc65LX4FQe2BlD0dElp6f00z4SGmtSMDuyEWwwiEf2Z61eEA==" + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-tokens/-/fuselage-tokens-0.30.1.tgz", + "integrity": "sha512-bnwuNahkG9+EHYjZ8IpKqjxRAHnWHpQGZWwLaLE0XKt7xP8B/ubEQeV7PLDYI0KPJ5oWJCkk8CSf4FGJ9gSyxg==" }, "@rocket.chat/fuselage-ui-kit": { - "version": "0.6.3-dev.322", - "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-ui-kit/-/fuselage-ui-kit-0.6.3-dev.322.tgz", - "integrity": "sha512-CVB0IRtpmc94fjQsMEIg6TkC2KX4WXBQ1ZZrOxgmuIcj4HQG/gBVah0B1O3ApQvORDijtJxogGE1oTXoLXrf0A==" + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-ui-kit/-/fuselage-ui-kit-0.30.1.tgz", + "integrity": "sha512-0lT5bIAKcwTbH5nJQ6EP+ov4GQKl9w9W1IEOB9v63k904jQf5xpaNsCv6KfEcPIgRp/2tzBrVgFccU+xKR5z2w==", + "requires": { + "@rocket.chat/fuselage": "^0.30.0", + "@rocket.chat/fuselage-hooks": "^0.30.0", + "@rocket.chat/styled": "^0.30.0", + "@rocket.chat/ui-kit": "^0.30.1", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } }, "@rocket.chat/icons": { - "version": "0.6.3-dev.327", - "resolved": "https://registry.npmjs.org/@rocket.chat/icons/-/icons-0.6.3-dev.327.tgz", - "integrity": "sha512-oRazxPrfVP4WPDE4Cs7UCjLsKzUdagKVeQf1f0DbUtQAQRWHEofnzDjakYFtkli/e4Ak1JN9Bcqeq4ejFoU7wQ==" + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@rocket.chat/icons/-/icons-0.30.1.tgz", + "integrity": "sha512-XZi0UitfOnzQOUnk0flqyhqWtRqpoPToZzBvhzIla1r+7bLH7dBxjHlbGjT1EOQ1Xso0hGrbZKLxGnRmakJx8g==" }, "@rocket.chat/livechat": { "version": "1.9.6", @@ -5521,19 +5485,14 @@ } }, "@rocket.chat/logo": { - "version": "0.6.3-dev.332", - "resolved": "https://registry.npmjs.org/@rocket.chat/logo/-/logo-0.6.3-dev.332.tgz", - "integrity": "sha512-SOvRLpOiojO3chdpGN6IQ3bLnPOQO71jBfpZwlq3ApYvc9Xvsr3rQG12qpeqbQYT4CmcnAPXPMfC1CJlPew3dA==", + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@rocket.chat/logo/-/logo-0.30.1.tgz", + "integrity": "sha512-gxAMj0PYEjuHmNIi4TL+r5q4nvQsrYq1iZM8e2QTd9u/KtuAbeSyldMfF/Tk7SyYhhnfTXSBDvuqIbGr8LfTzg==", "requires": { - "@rocket.chat/fuselage-hooks": "^0.6.3-dev.332+687ea423", + "@rocket.chat/fuselage-hooks": "^0.30.0", "tslib": "^2.3.1" }, "dependencies": { - "@rocket.chat/fuselage-hooks": { - "version": "0.6.3-dev.335", - "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-hooks/-/fuselage-hooks-0.6.3-dev.335.tgz", - "integrity": "sha512-tH+GCCfBx+A00Ew/UqIHg/oSobZVND9W/NZS2iw5DIPlItNGQI5Icd7w2Q6F4xo2v0/amXMPpfr+RW8ABk3Fvw==" - }, "tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", @@ -5542,9 +5501,9 @@ } }, "@rocket.chat/memo": { - "version": "0.6.3-dev.322", - "resolved": "https://registry.npmjs.org/@rocket.chat/memo/-/memo-0.6.3-dev.322.tgz", - "integrity": "sha512-kcedVPwL0tvJaCtJFzDSTjibDw5MMEnvgwLxVykG8zcbjouVtGIL/sKqnee3Z0nqKBGWrvK4IchuPfC0ZYjjlw==", + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@rocket.chat/memo/-/memo-0.30.1.tgz", + "integrity": "sha512-xVg4F48kx4XazCi6arpRnwNYYxdtOg1YT2K8AC12iWhWex+mgMzUxCORBEmLzswGqXWZyySwlDeCNXKknyUhjQ==", "requires": { "tslib": "^2.3.0" }, @@ -5557,9 +5516,9 @@ } }, "@rocket.chat/message-parser": { - "version": "0.6.3-dev.326", - "resolved": "https://registry.npmjs.org/@rocket.chat/message-parser/-/message-parser-0.6.3-dev.326.tgz", - "integrity": "sha512-jZyjROB3ueVWZ1ekKoTs4eRSLLE3h5kgIkVSWb4f23hjTnZVRt2kHh1f5YVKSHi5cfzTIv9ue6U0loPCKJqWsA==" + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@rocket.chat/message-parser/-/message-parser-0.30.1.tgz", + "integrity": "sha512-TIFXAlTU88esCJtrOrtKzEy6ysE5TY8wTvJ4BWkjut15/I3LFAC36S4/8UBLn8tRPdV2X/+viPq5wPrpPUFMRQ==" }, "@rocket.chat/mp3-encoder": { "version": "0.24.0", @@ -5612,10 +5571,26 @@ } }, "@rocket.chat/string-helpers": { - "version": "0.6.3-dev.322", - "resolved": "https://registry.npmjs.org/@rocket.chat/string-helpers/-/string-helpers-0.6.3-dev.322.tgz", - "integrity": "sha512-Xk4Zzc/WgBwM9aDIQF2pWD71VrbzYfNGzvjx6Ws19eZQ5c4qQT19wu/lLvHvwop1pjwL0AwUO+Xg8hKTJG+WZA==", + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@rocket.chat/string-helpers/-/string-helpers-0.30.1.tgz", + "integrity": "sha512-q/F8hMFSp478zegXwma0Yo+glbVCDNbxyx/pgb7ICkQS4JvZqr1WL7IUuNbMPcBVUa8KyixYmhSA1+BNGanN6g==", + "requires": { + "tslib": "^2.3.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, + "@rocket.chat/styled": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@rocket.chat/styled/-/styled-0.30.1.tgz", + "integrity": "sha512-SoEYCzeanwvQFhj+JwurcDiE1nFCvyeed54lMRw3JJHqSyDg9N9/Qu21ZO1zlR9S4zaMWaHAifOFyfG4tk2nwQ==", "requires": { + "@rocket.chat/css-in-js": "^0.30.0", "tslib": "^2.3.0" }, "dependencies": { @@ -5627,11 +5602,11 @@ } }, "@rocket.chat/stylis-logical-props-middleware": { - "version": "0.6.3-dev.322", - "resolved": "https://registry.npmjs.org/@rocket.chat/stylis-logical-props-middleware/-/stylis-logical-props-middleware-0.6.3-dev.322.tgz", - "integrity": "sha512-Lv4+nKJ75oWh4nj8vZkV+wZkBTjpsmiuR4YhQuW2PJG+LPoXrQkS3BbhB8B7EZKabsPHiyUWeqz5tvcKj5aqoA==", + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@rocket.chat/stylis-logical-props-middleware/-/stylis-logical-props-middleware-0.30.1.tgz", + "integrity": "sha512-DnGSN89noeaXwv4JK4FNm7oFdkd5XuAtGObJ4lFadmLL/BePiYlps6k+0PAdUeMIxmwzfEXpL5Awv8VODOqs4g==", "requires": { - "@rocket.chat/css-supports": "^0.6.3-dev.322+b067cee6", + "@rocket.chat/css-supports": "^0.30.0", "tslib": "^2.2.0" }, "dependencies": { @@ -5643,9 +5618,9 @@ } }, "@rocket.chat/ui-kit": { - "version": "0.6.3-dev.322", - "resolved": "https://registry.npmjs.org/@rocket.chat/ui-kit/-/ui-kit-0.6.3-dev.322.tgz", - "integrity": "sha512-O+1X2keDPnCR1LZztaRtWRPb9h8D8OSqav+pdnhJ3dEQ7i45x8rbwG+SntTl2rW9RIHFZgu2Z9pnoD9W1hy6Pg==" + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@rocket.chat/ui-kit/-/ui-kit-0.30.1.tgz", + "integrity": "sha512-wxrbYCIq3qAx6aHTVNkyhfpUGH37Ij4oUl6CYE0yO5TiFs/yuFaap9c+6uX3gzKKzuImt195aIVz5NqNO40htg==" }, "@samverschueren/stream-to-observable": { "version": "0.3.1", diff --git a/package.json b/package.json index d4c510bebf55b..0a943bcf123bf 100644 --- a/package.json +++ b/package.json @@ -161,20 +161,20 @@ "@nivo/line": "0.62.0", "@nivo/pie": "0.73.0", "@rocket.chat/apps-engine": "1.28.0", - "@rocket.chat/css-in-js": "^0.6.3-dev.322", - "@rocket.chat/emitter": "^0.6.3-dev.322", - "@rocket.chat/fuselage": "^0.6.3-dev.326", - "@rocket.chat/fuselage-hooks": "^0.6.3-dev.322", - "@rocket.chat/fuselage-polyfills": "^0.6.3-dev.322", - "@rocket.chat/fuselage-tokens": "^0.6.3-dev.322", - "@rocket.chat/fuselage-ui-kit": "^0.6.3-dev.322", - "@rocket.chat/icons": "^0.6.3-dev.327", - "@rocket.chat/logo": "^0.6.3-dev.332", - "@rocket.chat/memo": "^0.6.3-dev.322", - "@rocket.chat/message-parser": "^0.6.3-dev.326", + "@rocket.chat/css-in-js": "^0.30.1", + "@rocket.chat/emitter": "^0.30.1", + "@rocket.chat/fuselage": "^0.30.1", + "@rocket.chat/fuselage-hooks": "^0.30.1", + "@rocket.chat/fuselage-polyfills": "^0.30.1", + "@rocket.chat/fuselage-tokens": "^0.30.1", + "@rocket.chat/fuselage-ui-kit": "^0.30.1", + "@rocket.chat/icons": "^0.30.1", + "@rocket.chat/logo": "^0.30.1", + "@rocket.chat/memo": "^0.30.1", + "@rocket.chat/message-parser": "^0.30.1", "@rocket.chat/mp3-encoder": "^0.24.0", - "@rocket.chat/string-helpers": "^0.6.3-dev.322", - "@rocket.chat/ui-kit": "^0.6.3-dev.322", + "@rocket.chat/string-helpers": "^0.30.1", + "@rocket.chat/ui-kit": "^0.30.1", "@slack/client": "^4.12.0", "@types/cookie": "^0.4.1", "@types/lodash": "^4.14.171", From 06579aae29137ca5cc62ab62affadec18f4b838f Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Wed, 27 Oct 2021 15:13:28 -0600 Subject: [PATCH 031/137] Regression: Routing method not available when called from listeners at startup (#23568) --- server/modules/listeners/listeners.module.ts | 2 +- server/services/meteor/service.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/server/modules/listeners/listeners.module.ts b/server/modules/listeners/listeners.module.ts index a1c420eb69a12..d5bde611c7737 100644 --- a/server/modules/listeners/listeners.module.ts +++ b/server/modules/listeners/listeners.module.ts @@ -152,7 +152,7 @@ export class ListenersModule { service.onEvent('watch.inquiries', async ({ clientAction, inquiry, diff }): Promise => { const config = await getRoutingManagerConfig(); - if (config.autoAssignAgent) { + if (!config || config.autoAssignAgent) { return; } diff --git a/server/services/meteor/service.ts b/server/services/meteor/service.ts index ae182e3efd57f..6d5290673af3f 100644 --- a/server/services/meteor/service.ts +++ b/server/services/meteor/service.ts @@ -282,6 +282,9 @@ export class MeteorService extends ServiceClass implements IMeteor { } getRoutingManagerConfig(): IRoutingManagerConfig { - return RoutingManager.getConfig(); + // return false if called before routing method is set + // this will cause that oplog events received on early stages of server startup + // won't be fired (at least, inquiry events) + return RoutingManager.isMethodSet() && RoutingManager.getConfig(); } } From 90531dd33c09b111f070d4e27302b8049325556b Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 27 Oct 2021 18:30:40 -0300 Subject: [PATCH 032/137] Regression: Prevent settings from getting updated (#23556) --- app/settings/server/SettingsRegistry.ts | 23 +++++++++++++++++-- .../functions/getSettingDefaults.tests.ts | 10 ++++++++ .../server/functions/getSettingDefaults.ts | 12 ++++++++-- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/app/settings/server/SettingsRegistry.ts b/app/settings/server/SettingsRegistry.ts index 40f8c08ba48e0..fa1f4ff3b9d85 100644 --- a/app/settings/server/SettingsRegistry.ts +++ b/app/settings/server/SettingsRegistry.ts @@ -75,6 +75,7 @@ const compareSettingsIgnoringKeys = (keys: Array) => .every((key) => isEqual(a[key as keyof ISetting], b[key as keyof ISetting])); const compareSettings = compareSettingsIgnoringKeys(['value', 'ts', 'createdAt', 'valueSource', 'packageValue', 'processEnvValue', '_updatedAt']); + export class SettingsRegistry { private model: typeof SettingsModel; @@ -108,7 +109,15 @@ export class SettingsRegistry { this._sorter[sorterKey] = this._sorter[sorterKey] ?? -1; } - const settingFromCode = getSettingDefaults({ _id, type: 'string', section, value, sorter: sorter ?? (sorterKey?.length && this._sorter[sorterKey]++), group, ...options }, blockedSettings, hiddenSettings, wizardRequiredSettings); + const settingFromCode = getSettingDefaults({ + _id, + type: 'string', + value, + sorter: sorter ?? (sorterKey?.length && this._sorter[sorterKey]++), + group, + section, + ...options, + }, blockedSettings, hiddenSettings, wizardRequiredSettings); if (isSettingEnterprise(settingFromCode) && !('invalidValue' in settingFromCode)) { SystemLogger.error(`Enterprise setting ${ _id } is missing the invalidValue option`); @@ -117,6 +126,7 @@ export class SettingsRegistry { const settingStored = this.store.getSetting(_id); const settingOverwritten = overwriteSetting(settingFromCode); + try { validateSetting(settingFromCode._id, settingFromCode.type, settingFromCode.value); } catch (e) { @@ -129,7 +139,16 @@ export class SettingsRegistry { if (settingStored && !compareSettings(settingStored, settingOverwritten)) { const { value: _value, ...settingOverwrittenProps } = settingOverwritten; - this.model.upsert({ _id }, { $set: { ...settingOverwrittenProps } }); + + const overwrittenKeys = Object.keys(settingOverwritten); + const removedKeys = Object.keys(settingStored).filter((key) => !['_updatedAt'].includes(key) && !overwrittenKeys.includes(key)); + + this.model.upsert({ _id }, { + $set: { ...settingOverwrittenProps }, + ...removedKeys.length && { + $unset: removedKeys.reduce((unset, key) => ({ ...unset, [key]: 1 }), {}), + }, + }); return; } diff --git a/app/settings/server/functions/getSettingDefaults.tests.ts b/app/settings/server/functions/getSettingDefaults.tests.ts index 1ed12b81a672e..35d5f3f5c0291 100644 --- a/app/settings/server/functions/getSettingDefaults.tests.ts +++ b/app/settings/server/functions/getSettingDefaults.tests.ts @@ -85,4 +85,14 @@ describe('getSettingDefaults', () => { expect(setting).to.have.property('blocked').to.be.equal(true); }); + + it('should not return undefined options', () => { + const setting = getSettingDefaults({ _id: 'test', value: true, type: 'string', section: undefined, group: undefined }, new Set(['test'])); + + expect(setting).to.be.an('object'); + expect(setting).to.have.property('_id'); + + expect(setting).to.not.have.property('section'); + expect(setting).to.not.have.property('group'); + }); }); diff --git a/app/settings/server/functions/getSettingDefaults.ts b/app/settings/server/functions/getSettingDefaults.ts index 3530840fd56ea..e17c97790b1e3 100644 --- a/app/settings/server/functions/getSettingDefaults.ts +++ b/app/settings/server/functions/getSettingDefaults.ts @@ -1,7 +1,15 @@ import { ISetting, ISettingColor, isSettingColor } from '../../../../definition/ISetting'; -export const getSettingDefaults = (setting: Partial & Pick, blockedSettings: Set = new Set(), hiddenSettings: Set = new Set(), wizardRequiredSettings: Set = new Set()): ISetting => { - const { _id, value, sorter, ...options } = setting; +export const getSettingDefaults = ( + setting: Partial & Pick, + blockedSettings: Set = new Set(), + hiddenSettings: Set = new Set(), + wizardRequiredSettings: Set = new Set(), +): ISetting => { + const { _id, value, sorter, ...props } = setting; + + const options = Object.fromEntries(Object.entries(props).filter(([, value]) => value !== undefined)); + return { _id, value, From 2881a4d491963c7736cef0acc09fd91ad12434a3 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 27 Oct 2021 18:31:33 -0300 Subject: [PATCH 033/137] Bump version to 4.1.0-rc.3 --- .docker/Dockerfile.rhel | 2 +- .github/history.json | 41 +++++++++++++++++++++++++- .snapcraft/resources/prepareRocketChat | 2 +- .snapcraft/snap/snapcraft.yaml | 2 +- HISTORY.md | 21 +++++++++++++ app/utils/rocketchat.info | 2 +- package-lock.json | 2 +- package.json | 2 +- 8 files changed, 67 insertions(+), 7 deletions(-) diff --git a/.docker/Dockerfile.rhel b/.docker/Dockerfile.rhel index 7423ccc886e5d..41e468ee30605 100644 --- a/.docker/Dockerfile.rhel +++ b/.docker/Dockerfile.rhel @@ -1,6 +1,6 @@ FROM registry.access.redhat.com/ubi8/nodejs-12 -ENV RC_VERSION 4.1.0-rc.2 +ENV RC_VERSION 4.1.0-rc.3 MAINTAINER buildmaster@rocket.chat diff --git a/.github/history.json b/.github/history.json index 5da1a7c53b85d..6ce5077829b5f 100644 --- a/.github/history.json +++ b/.github/history.json @@ -66958,6 +66958,45 @@ ] } ] + }, + "4.1.0-rc.3": { + "node_version": "12.22.1", + "npm_version": "6.14.1", + "apps_engine_version": "1.28.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "23556", + "title": "Regression: Prevent settings from getting updated", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "23568", + "title": "Regression: Routing method not available when called from listeners at startup", + "userLogin": "KevLehman", + "milestone": "4.1.0", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "23391", + "title": "Bump: fuselage 0.30.1", + "userLogin": "ggazzo", + "contributors": [ + "dougfabris" + ] + } + ] } } -} \ No newline at end of file +} diff --git a/.snapcraft/resources/prepareRocketChat b/.snapcraft/resources/prepareRocketChat index 57d9849e3d4fc..25689a259fd27 100755 --- a/.snapcraft/resources/prepareRocketChat +++ b/.snapcraft/resources/prepareRocketChat @@ -1,6 +1,6 @@ #!/bin/bash -curl -SLf "https://releases.rocket.chat/4.1.0-rc.2/download/" -o rocket.chat.tgz +curl -SLf "https://releases.rocket.chat/4.1.0-rc.3/download/" -o rocket.chat.tgz tar xf rocket.chat.tgz --strip 1 diff --git a/.snapcraft/snap/snapcraft.yaml b/.snapcraft/snap/snapcraft.yaml index 783af012953ae..9555f99826a1f 100644 --- a/.snapcraft/snap/snapcraft.yaml +++ b/.snapcraft/snap/snapcraft.yaml @@ -7,7 +7,7 @@ # 5. `snapcraft snap` name: rocketchat-server -version: 4.1.0-rc.2 +version: 4.1.0-rc.3 summary: Rocket.Chat server description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ confinement: strict diff --git a/HISTORY.md b/HISTORY.md index b04da2665dd11..580b4581b34cf 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,27 @@ # 4.1.0 (Under Release Candidate Process) +## 4.1.0-rc.3 +`2021-10-27 · 3 🔍 · 3 👩‍💻👨‍💻` + +
+🔍 Minor changes + + +- Bump: fuselage 0.30.1 ([#23391](https://github.com/RocketChat/Rocket.Chat/pull/23391)) + +- Regression: Prevent settings from getting updated ([#23556](https://github.com/RocketChat/Rocket.Chat/pull/23556)) + +- Regression: Routing method not available when called from listeners at startup ([#23568](https://github.com/RocketChat/Rocket.Chat/pull/23568)) + +
+ +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@KevLehman](https://github.com/KevLehman) +- [@dougfabris](https://github.com/dougfabris) +- [@sampaiodiego](https://github.com/sampaiodiego) + ## 4.1.0-rc.2 `2021-10-25 · 1 🐛 · 1 🔍 · 3 👩‍💻👨‍💻` diff --git a/app/utils/rocketchat.info b/app/utils/rocketchat.info index ab3f873bf3cfc..71761e2c92ace 100644 --- a/app/utils/rocketchat.info +++ b/app/utils/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "4.1.0-rc.2" + "version": "4.1.0-rc.3" } diff --git a/package-lock.json b/package-lock.json index 94be7458e3212..96a4b7c4c63f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "Rocket.Chat", - "version": "4.1.0-rc.2", + "version": "4.1.0-rc.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 926127b511c55..c1c07c1f0d4fa 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Rocket.Chat", "description": "The Ultimate Open Source WebChat Platform", - "version": "4.1.0-rc.2", + "version": "4.1.0-rc.3", "author": { "name": "Rocket.Chat", "url": "https://rocket.chat/" From 6b6fdde8282a0878e25af666edd8a55d9cca9c57 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Wed, 27 Oct 2021 20:35:28 -0600 Subject: [PATCH 034/137] Regression: Debounce call based on params on omnichannel queue dispatch (#23577) --- .../livechat-enterprise/server/lib/Helper.js | 4 +-- .../server/lib/debounceByParams.ts | 33 +++++++++++++++++++ package-lock.json | 8 +++++ package.json | 1 + 4 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 ee/app/livechat-enterprise/server/lib/debounceByParams.ts diff --git a/ee/app/livechat-enterprise/server/lib/Helper.js b/ee/app/livechat-enterprise/server/lib/Helper.js index 38a265350103d..59e3425657ba8 100644 --- a/ee/app/livechat-enterprise/server/lib/Helper.js +++ b/ee/app/livechat-enterprise/server/lib/Helper.js @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import moment from 'moment'; -import debounce from 'lodash.debounce'; +import { memoizeDebounce } from './debounceByParams'; import { LivechatDepartment, Users, @@ -105,7 +105,7 @@ export const dispatchWaitingQueueStatus = async (department) => { // When dealing with lots of queued items we need to make sure to notify their position // but we don't need to notify _each_ change that takes place, just their final position -export const debouncedDispatchWaitingQueueStatus = debounce(dispatchWaitingQueueStatus, 1200); +export const debouncedDispatchWaitingQueueStatus = memoizeDebounce(dispatchWaitingQueueStatus, 1200); export const processWaitingQueue = async (department) => { const queue = department || 'Public'; diff --git a/ee/app/livechat-enterprise/server/lib/debounceByParams.ts b/ee/app/livechat-enterprise/server/lib/debounceByParams.ts new file mode 100644 index 0000000000000..5c75fd6aaa6f0 --- /dev/null +++ b/ee/app/livechat-enterprise/server/lib/debounceByParams.ts @@ -0,0 +1,33 @@ +import debounce from 'lodash.debounce'; +import mem from 'mem'; + +export interface IMemoizeDebouncedFunction any> { + (...args: Parameters): void; + flush: (...args: Parameters) => void; +} + +// Debounce `func` based on passed parameters +// ref: https://github.com/lodash/lodash/issues/2403#issuecomment-816137402 +export function memoizeDebounce any>( + func: F, + wait = 0, + options: any = {}, +): IMemoizeDebouncedFunction { + const debounceMemo = mem( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + (..._args: Parameters) => debounce(func, wait, options), + ); + + function wrappedFunction( + this: IMemoizeDebouncedFunction, + ...args: Parameters + ): ReturnType | undefined { + return debounceMemo(...args)(...args); + } + + wrappedFunction.flush = (...args: Parameters): void => { + debounceMemo(...args).flush(); + }; + + return (wrappedFunction as unknown) as IMemoizeDebouncedFunction; +} diff --git a/package-lock.json b/package-lock.json index e62fd1de74650..280f6371bb071 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9743,6 +9743,14 @@ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.171.tgz", "integrity": "sha512-7eQ2xYLLI/LsicL2nejW9Wyko3lcpN6O/z0ZLHrEQsg280zIdCv1t/0m6UtBjUHokCGBQ3gYTbHzDkZ1xOBwwg==" }, + "@types/lodash.debounce": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.6.tgz", + "integrity": "sha512-4WTmnnhCfDvvuLMaF3KV4Qfki93KebocUF45msxhYyjMttZDQYzHkO639ohhk8+oco2cluAFL3t5+Jn4mleylQ==", + "requires": { + "@types/lodash": "*" + } + }, "@types/lodash.get": { "version": "4.4.6", "resolved": "https://registry.npmjs.org/@types/lodash.get/-/lodash.get-4.4.6.tgz", diff --git a/package.json b/package.json index 0a943bcf123bf..01e59259d2a21 100644 --- a/package.json +++ b/package.json @@ -178,6 +178,7 @@ "@slack/client": "^4.12.0", "@types/cookie": "^0.4.1", "@types/lodash": "^4.14.171", + "@types/lodash.debounce": "^4.0.6", "adm-zip": "0.4.14", "agenda": "github:RocketChat/agenda#3.1.2", "apn": "2.2.0", From b4de1cf0e2f107c109cd7d90e85c8449858c3fb9 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 27 Oct 2021 23:45:41 -0300 Subject: [PATCH 035/137] Bump version to 4.1.0-rc.4 --- .docker/Dockerfile.rhel | 2 +- .github/history.json | 22 + .snapcraft/resources/prepareRocketChat | 2 +- .snapcraft/snap/snapcraft.yaml | 2 +- HISTORY.md | 2107 +++++++++++------------- app/utils/rocketchat.info | 2 +- package-lock.json | 2 +- package.json | 2 +- 8 files changed, 1018 insertions(+), 1123 deletions(-) diff --git a/.docker/Dockerfile.rhel b/.docker/Dockerfile.rhel index 41e468ee30605..b52f652759200 100644 --- a/.docker/Dockerfile.rhel +++ b/.docker/Dockerfile.rhel @@ -1,6 +1,6 @@ FROM registry.access.redhat.com/ubi8/nodejs-12 -ENV RC_VERSION 4.1.0-rc.3 +ENV RC_VERSION 4.1.0-rc.4 MAINTAINER buildmaster@rocket.chat diff --git a/.github/history.json b/.github/history.json index 6ce5077829b5f..a5452342705b7 100644 --- a/.github/history.json +++ b/.github/history.json @@ -66997,6 +66997,28 @@ ] } ] + }, + "4.1.0-rc.4": { + "node_version": "12.22.1", + "npm_version": "6.14.1", + "apps_engine_version": "1.28.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "23577", + "title": "Regression: Debounce call based on params on omnichannel queue dispatch", + "userLogin": "KevLehman", + "contributors": [ + "KevLehman" + ] + } + ] } } } diff --git a/.snapcraft/resources/prepareRocketChat b/.snapcraft/resources/prepareRocketChat index 25689a259fd27..31e33dbcfa7ea 100755 --- a/.snapcraft/resources/prepareRocketChat +++ b/.snapcraft/resources/prepareRocketChat @@ -1,6 +1,6 @@ #!/bin/bash -curl -SLf "https://releases.rocket.chat/4.1.0-rc.3/download/" -o rocket.chat.tgz +curl -SLf "https://releases.rocket.chat/4.1.0-rc.4/download/" -o rocket.chat.tgz tar xf rocket.chat.tgz --strip 1 diff --git a/.snapcraft/snap/snapcraft.yaml b/.snapcraft/snap/snapcraft.yaml index 9555f99826a1f..f7d11bb6fa6ab 100644 --- a/.snapcraft/snap/snapcraft.yaml +++ b/.snapcraft/snap/snapcraft.yaml @@ -7,7 +7,7 @@ # 5. `snapcraft snap` name: rocketchat-server -version: 4.1.0-rc.3 +version: 4.1.0-rc.4 summary: Rocket.Chat server description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ confinement: strict diff --git a/HISTORY.md b/HISTORY.md index 1253a50c0909e..8ccee74bcea24 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,21 @@ # 4.1.0 (Under Release Candidate Process) +## 4.1.0-rc.4 +`2021-10-27 · 1 🔍 · 1 👩‍💻👨‍💻` + +
+🔍 Minor changes + + +- Regression: Debounce call based on params on omnichannel queue dispatch ([#23577](https://github.com/RocketChat/Rocket.Chat/pull/23577)) + +
+ +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@KevLehman](https://github.com/KevLehman) + ## 4.1.0-rc.3 `2021-10-27 · 3 🔍 · 3 👩‍💻👨‍💻` @@ -36,10 +51,10 @@ - Regression: Mail body contains `undefined` text ([#23552](https://github.com/RocketChat/Rocket.Chat/pull/23552)) - ### Before - ![image](https://user-images.githubusercontent.com/2263066/138733018-10449892-5c2d-46fb-9355-00e98e0d6c9f.png) - - ### After + ### Before + ![image](https://user-images.githubusercontent.com/2263066/138733018-10449892-5c2d-46fb-9355-00e98e0d6c9f.png) + + ### After ![image](https://user-images.githubusercontent.com/2263066/138733074-a1b88a77-bf64-41c3-a6c3-ac9e1cb63de1.png)
@@ -89,19 +104,19 @@ - Make Livechat Instructions setting multi-line ([#23515](https://github.com/RocketChat/Rocket.Chat/pull/23515)) - Since now we're supporting markdown text on this field (via this PR - https://github.com/RocketChat/Rocket.Chat.Livechat/pull/648), it would be nice to make this setting multiline so users can have more space to edit the text + Since now we're supporting markdown text on this field (via this PR - https://github.com/RocketChat/Rocket.Chat.Livechat/pull/648), it would be nice to make this setting multiline so users can have more space to edit the text ![image](https://user-images.githubusercontent.com/34130764/138146712-13e4968b-5312-4d53-b44c-b5699c5e49c1.png) - optimized groups.listAll response time ([#22941](https://github.com/RocketChat/Rocket.Chat/pull/22941)) - groups.listAll endpoint was having performance issues, specially when the total number of groups was high. This happened because the endpoint was loading all objects in memory then using splice to paginate, instead of paginating beforehand. - - Considering 70k groups, this was the performance improvement: - - before - ![image](https://user-images.githubusercontent.com/28611993/129601314-bdf89337-79fa-4446-9f44-95264af4adb3.png) - - after + groups.listAll endpoint was having performance issues, specially when the total number of groups was high. This happened because the endpoint was loading all objects in memory then using splice to paginate, instead of paginating beforehand. + + Considering 70k groups, this was the performance improvement: + + before + ![image](https://user-images.githubusercontent.com/28611993/129601314-bdf89337-79fa-4446-9f44-95264af4adb3.png) + + after ![image](https://user-images.githubusercontent.com/28611993/129601358-5872e166-f923-4c1c-b21d-eb9507365ecf.png) ### 🐛 Bug fixes @@ -109,8 +124,7 @@ - **APPS:** Communication problem when updating and uninstalling apps in cluster ([#23418](https://github.com/RocketChat/Rocket.Chat/pull/23418)) - - Make the hook responsible for receiving app update events inside a cluster fetch the app's package (zip file) in the correct place. - + - Make the hook responsible for receiving app update events inside a cluster fetch the app's package (zip file) in the correct place. - Also shows a warning message on uninstalls inside a cluster. As there are many servers writing to the same place, some race conditions may occur. This prevents problems related to terminating the process in the middle due to errors being thrown and leaving the server in a faulty state. - **ENTERPRISE:** Omnichannel agent is not leaving the room when a forwarded chat is queued ([#23404](https://github.com/RocketChat/Rocket.Chat/pull/23404)) @@ -137,10 +151,10 @@ - Markdown quote message style ([#23462](https://github.com/RocketChat/Rocket.Chat/pull/23462)) - Before: - ![image](https://user-images.githubusercontent.com/17487063/137496669-3abecab4-cf90-45cb-8b1b-d9411a5682dd.png) - - After: + Before: + ![image](https://user-images.githubusercontent.com/17487063/137496669-3abecab4-cf90-45cb-8b1b-d9411a5682dd.png) + + After: ![image](https://user-images.githubusercontent.com/17487063/137496905-fd727f90-f707-4ec6-8139-ba2eb1a2146e.png) - MONGO_OPTIONS being ignored for oplog connection ([#23314](https://github.com/RocketChat/Rocket.Chat/pull/23314) by [@cuonghuunguyen](https://github.com/cuonghuunguyen)) @@ -157,8 +171,8 @@ - Read only description in team creation ([#23213](https://github.com/RocketChat/Rocket.Chat/pull/23213)) - ![image](https://user-images.githubusercontent.com/27704687/133608433-8ca788a3-71a8-4d40-8c40-8156ab03c606.png) - + ![image](https://user-images.githubusercontent.com/27704687/133608433-8ca788a3-71a8-4d40-8c40-8156ab03c606.png) + ![image](https://user-images.githubusercontent.com/27704687/133608400-4cdc7a67-95e5-46c6-8c65-29ab107cd314.png) - resumeToken not working ([#23379](https://github.com/RocketChat/Rocket.Chat/pull/23379)) @@ -167,8 +181,7 @@ - SAML Users' roles being reset to default on login ([#23411](https://github.com/RocketChat/Rocket.Chat/pull/23411)) - - Remove `roles` field update on `insertOrUpdateSAMLUser` function; - + - Remove `roles` field update on `insertOrUpdateSAMLUser` function; - Add SAML `syncRoles` event; - Server crashing when Routing method is not available at start ([#23473](https://github.com/RocketChat/Rocket.Chat/pull/23473)) @@ -247,14 +260,14 @@ - Chore: Startup Time ([#23210](https://github.com/RocketChat/Rocket.Chat/pull/23210)) - The settings logic has been improved as a whole. - - All the logic to get the data from the env var was confusing. - - Setting default values was tricky to understand. - - Every time the server booted, all settings were updated and callbacks were called 2x or more (horrible for environments with multiple instances and generating a turbulent startup). - + The settings logic has been improved as a whole. + + All the logic to get the data from the env var was confusing. + + Setting default values was tricky to understand. + + Every time the server booted, all settings were updated and callbacks were called 2x or more (horrible for environments with multiple instances and generating a turbulent startup). + `Settings.get(......, callback);` was deprecated. We now have better methods for each case. - Chore: Update Apps-Engine version ([#23375](https://github.com/RocketChat/Rocket.Chat/pull/23375)) @@ -351,8 +364,7 @@ - SAML Users' roles being reset to default on login ([#23411](https://github.com/RocketChat/Rocket.Chat/pull/23411)) - - Remove `roles` field update on `insertOrUpdateSAMLUser` function; - + - Remove `roles` field update on `insertOrUpdateSAMLUser` function; - Add SAML `syncRoles` event;
@@ -384,8 +396,7 @@ - **APPS:** Communication problem when updating and uninstalling apps in cluster ([#23418](https://github.com/RocketChat/Rocket.Chat/pull/23418)) - - Make the hook responsible for receiving app update events inside a cluster fetch the app's package (zip file) in the correct place. - + - Make the hook responsible for receiving app update events inside a cluster fetch the app's package (zip file) in the correct place. - Also shows a warning message on uninstalls inside a cluster. As there are many servers writing to the same place, some race conditions may occur. This prevents problems related to terminating the process in the middle due to errors being thrown and leaving the server in a faulty state. - Server crashing when Routing method is not available at start ([#23473](https://github.com/RocketChat/Rocket.Chat/pull/23473)) @@ -512,20 +523,18 @@ - **ENTERPRISE:** "Download CSV" button doesn't work in the Engagement Dashboard's Active Users section ([#23013](https://github.com/RocketChat/Rocket.Chat/pull/23013)) - - Fix "Download CSV" button in the Engagement Dashboard's Active Users section; - - - Add column headers to the CSV file downloaded from the Engagement Dashboard's Active Users section; - + - Fix "Download CSV" button in the Engagement Dashboard's Active Users section; + - Add column headers to the CSV file downloaded from the Engagement Dashboard's Active Users section; - Split the data in multiple CSV files. - **ENTERPRISE:** CSV file downloaded in the Engagement Dashboard's New Users section contains undefined data ([#23014](https://github.com/RocketChat/Rocket.Chat/pull/23014)) - - Fix CSV file downloaded in the Engagement Dashboard's New Users section; + - Fix CSV file downloaded in the Engagement Dashboard's New Users section; - Add column headers to the CSV file downloaded from the Engagement Dashboard's New Users section. - **ENTERPRISE:** Missing headers in CSV files downloaded from the Engagement Dashboard ([#23223](https://github.com/RocketChat/Rocket.Chat/pull/23223)) - - Add headers to all CSV files downloaded from the "Messages" and "Channels" tabs from the Engagement Dashboard; + - Add headers to all CSV files downloaded from the "Messages" and "Channels" tabs from the Engagement Dashboard; - Add headers to the CSV file downloaded from the "Users by time of day" section (in the "Users" tab). - LDAP Refactoring ([#23171](https://github.com/RocketChat/Rocket.Chat/pull/23171)) @@ -540,24 +549,17 @@ - Remove deprecated endpoints ([#23162](https://github.com/RocketChat/Rocket.Chat/pull/23162)) - The following REST endpoints were removed: - - - - `/api/v1/emoji-custom` - - - `/api/v1/info` - - - `/api/v1/permissions` - - - `/api/v1/permissions.list` - - The following Real time API Methods were removed: - - - - `getFullUserData` - - - `getServerInfo` - + The following REST endpoints were removed: + + - `/api/v1/emoji-custom` + - `/api/v1/info` + - `/api/v1/permissions` + - `/api/v1/permissions.list` + + The following Real time API Methods were removed: + + - `getFullUserData` + - `getServerInfo` - `livechat:saveOfficeHours` - Remove Google Vision features ([#23160](https://github.com/RocketChat/Rocket.Chat/pull/23160)) @@ -566,8 +568,8 @@ - Remove old migrations up to version 2.4.14 ([#23277](https://github.com/RocketChat/Rocket.Chat/pull/23277)) - To update to version 4.0.0 you'll need to be running at least version 3.0.0, otherwise you might loose some database migrations which might have unexpected effects. - + To update to version 4.0.0 you'll need to be running at least version 3.0.0, otherwise you might loose some database migrations which might have unexpected effects. + This aims to clean up the code, since upgrades jumping 2 major versions are too risky and hard to maintain, we'll keep only migration from that last major (in this case 3.x). - Remove patch info from endpoint /api/info for non-logged in users ([#16050](https://github.com/RocketChat/Rocket.Chat/pull/16050) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) @@ -576,18 +578,18 @@ - Stop sending audio notifications via stream ([#23108](https://github.com/RocketChat/Rocket.Chat/pull/23108)) - Remove audio preferences and make them tied to desktop notification preferences. - + Remove audio preferences and make them tied to desktop notification preferences. + TL;DR: new message sounds will play only if you receive a desktop notification. you'll still be able to chose to not play any sound though - Webhook will fail if user is not part of the channel ([#23310](https://github.com/RocketChat/Rocket.Chat/pull/23310)) - Remove deprecated behavior added by https://github.com/RocketChat/Rocket.Chat/pull/18024 that accepts webhook integrations sending messages even if the user is not part of the channel. - - Starting from 4.0.0 the webhook request will fail with `error-not-allowed` error: - - ``` - {"success":false,"error":"error-not-allowed"} + Remove deprecated behavior added by https://github.com/RocketChat/Rocket.Chat/pull/18024 that accepts webhook integrations sending messages even if the user is not part of the channel. + + Starting from 4.0.0 the webhook request will fail with `error-not-allowed` error: + + ``` + {"success":false,"error":"error-not-allowed"} ``` ### 🎉 New features @@ -605,26 +607,23 @@ - Seats Cap ([#23017](https://github.com/RocketChat/Rocket.Chat/pull/23017) by [@g-thome](https://github.com/g-thome)) - - Adding New Members - - Awareness of seats usage while adding new members - - Seats Cap about to be reached - - Seats Cap reached - - Request more seats - - - Warning Admins - - System telling admins max seats are about to exceed - - System telling admins max seats were exceed - - Metric on Info Page - - Request more seats - - - Warning Members - - Invite link - - Block creating new invite links - - Block existing invite links (feedback on register process) - - Register to Workspaces - - - Emails - - System telling admins max seats are about to exceed + - Adding New Members + - Awareness of seats usage while adding new members + - Seats Cap about to be reached + - Seats Cap reached + - Request more seats + - Warning Admins + - System telling admins max seats are about to exceed + - System telling admins max seats were exceed + - Metric on Info Page + - Request more seats + - Warning Members + - Invite link + - Block creating new invite links + - Block existing invite links (feedback on register process) + - Register to Workspaces + - Emails + - System telling admins max seats are about to exceed - System telling admins max seats were exceed ### 🚀 Improvements @@ -632,10 +631,10 @@ - **APPS:** New storage strategy for Apps-Engine file packages ([#22657](https://github.com/RocketChat/Rocket.Chat/pull/22657)) - This is an enabler for our initiative to support NPM packages in the Apps-Engine. - - Currently, the packages (zip files) for Rocket.Chat Apps are stored as a base64 encoded string in a document in the database, which constrains us due to the size limit of a document in MongoDB (16Mb). - + This is an enabler for our initiative to support NPM packages in the Apps-Engine. + + Currently, the packages (zip files) for Rocket.Chat Apps are stored as a base64 encoded string in a document in the database, which constrains us due to the size limit of a document in MongoDB (16Mb). + When we allow apps to include NPM packages, the size of the App package itself will be potentially _very large_ (I'm looking at you `node_modules`). Thus we'll be changing the strategy to store apps either with GridFS or the host's File System itself. - **APPS:** Return task ids when using the scheduler api ([#23023](https://github.com/RocketChat/Rocket.Chat/pull/23023)) @@ -675,9 +674,9 @@ - "Read Only" and "Allow Reacting" system messages are missing in rooms ([#23037](https://github.com/RocketChat/Rocket.Chat/pull/23037)) - - Add system message to notify changes on the **"Read Only"** setting; - - Add system message to notify changes on the **"Allow Reacting"** setting; - - Fix "Allow Reacting" setting's description (updated from "Only authorized users can write new messages" to "Only authorized users can react to messages"). + - Add system message to notify changes on the **"Read Only"** setting; + - Add system message to notify changes on the **"Allow Reacting"** setting; + - Fix "Allow Reacting" setting's description (updated from "Only authorized users can write new messages" to "Only authorized users can react to messages"). ![system-messages](https://user-images.githubusercontent.com/36537004/130883527-9eb47fcd-c8e5-41fb-af34-5d99bd0a6780.PNG) - Add check before placing chat on-hold to confirm that contact sent last message ([#23053](https://github.com/RocketChat/Rocket.Chat/pull/23053)) @@ -692,9 +691,9 @@ - Inaccurate use of 'Mobile notifications' instead of 'Push notifications' in i18n strings ([#22978](https://github.com/RocketChat/Rocket.Chat/pull/22978)) - - Fix inaccurate use of 'Mobile notifications' (which is misleading in German) by 'Push notifications'; - - Update `'Notification_Mobile_Default_For'` key to `'Notification_Push_Default_For'` (and text to 'Send Push Notifications For' for English Language); - - Update `'Accounts_Default_User_Preferences_mobileNotifications'` key to `'Accounts_Default_User_Preferences_pushNotifications'`; + - Fix inaccurate use of 'Mobile notifications' (which is misleading in German) by 'Push notifications'; + - Update `'Notification_Mobile_Default_For'` key to `'Notification_Push_Default_For'` (and text to 'Send Push Notifications For' for English Language); + - Update `'Accounts_Default_User_Preferences_mobileNotifications'` key to `'Accounts_Default_User_Preferences_pushNotifications'`; - Update `'Mobile_Notifications_Default_Alert'` key to `'Mobile_Push_Notifications_Default_Alert'`; - Logging out from other clients ([#23276](https://github.com/RocketChat/Rocket.Chat/pull/23276)) @@ -703,7 +702,7 @@ - Modals is cutting pixels of the content ([#23243](https://github.com/RocketChat/Rocket.Chat/pull/23243)) - Fuselage Dependency: [543](https://github.com/RocketChat/Rocket.Chat.Fuselage/pull/543) + Fuselage Dependency: [543](https://github.com/RocketChat/Rocket.Chat.Fuselage/pull/543) ![image](https://user-images.githubusercontent.com/27704687/134049227-3cd1deed-34ba-454f-a95e-e99b79a7a7b9.png) - Omnichannel On hold chats being forwarded to offline agents ([#23185](https://github.com/RocketChat/Rocket.Chat/pull/23185)) @@ -712,15 +711,15 @@ - Prevent users to edit an existing role when adding a new one with the same name used before. ([#22407](https://github.com/RocketChat/Rocket.Chat/pull/22407) by [@lucassartor](https://github.com/lucassartor)) - ### before - ![Peek 2021-07-13 16-31](https://user-images.githubusercontent.com/27704687/125513721-953d84f4-1c95-45ca-80e1-b00992b874f6.gif) - - ### after + ### before + ![Peek 2021-07-13 16-31](https://user-images.githubusercontent.com/27704687/125513721-953d84f4-1c95-45ca-80e1-b00992b874f6.gif) + + ### after ![Peek 2021-07-13 16-34](https://user-images.githubusercontent.com/27704687/125514098-91ee8014-51e5-4c62-9027-5538acf57d08.gif) - Remove doubled "Canned Responses" strings ([#23056](https://github.com/RocketChat/Rocket.Chat/pull/23056)) - - Remove doubled canned response setting introduced in #22703 (by setting id change); + - Remove doubled canned response setting introduced in #22703 (by setting id change); - Update "Canned Responses" keys to "Canned_Responses". - Remove margin from quote inside quote ([#21779](https://github.com/RocketChat/Rocket.Chat/pull/21779)) @@ -731,21 +730,16 @@ - Sidebar not closing when clicking in Home or Directory on mobile view ([#23218](https://github.com/RocketChat/Rocket.Chat/pull/23218)) - ### Additional fixed - - - Merge Burger menu components into a single component - - - Show a badge with no-read messages in the Burger Button: - ![image](https://user-images.githubusercontent.com/27704687/133679378-20fea2c0-4ac1-4b4e-886e-45154cc6afea.png) - + ### Additional fixed + - Merge Burger menu components into a single component + - Show a badge with no-read messages in the Burger Button: + ![image](https://user-images.githubusercontent.com/27704687/133679378-20fea2c0-4ac1-4b4e-886e-45154cc6afea.png) - remove useSidebarClose hook - Stop queue when Omnichannel is disabled or the routing method does not support it ([#23261](https://github.com/RocketChat/Rocket.Chat/pull/23261)) - - Add missing key logs - - - Stop queue (and logs) when livechat is disabled or when routing method does not support queue - + - Add missing key logs + - Stop queue (and logs) when livechat is disabled or when routing method does not support queue - Stop ignoring offline bot agents from delegation (previously, if a bot was offline, even with "Assign new conversations to bot agent" enabled, bot will be ignored and chat will be left in limbo (since bot was assigned, but offline). - Toolbox click not working on Safari(iOS) ([#23244](https://github.com/RocketChat/Rocket.Chat/pull/23244)) @@ -852,17 +846,17 @@ - Regression: Blank screen in Jitsi video calls ([#23322](https://github.com/RocketChat/Rocket.Chat/pull/23322)) - - Fix Jitsi calls being disposed even when "Open in new window" setting is disabled; + - Fix Jitsi calls being disposed even when "Open in new window" setting is disabled; - Fix misspelling on `CallJitsWithData.js` file name. - Regression: Create new loggers based on server log level ([#23297](https://github.com/RocketChat/Rocket.Chat/pull/23297)) - Regression: Fix app storage migration ([#23286](https://github.com/RocketChat/Rocket.Chat/pull/23286)) - The previous version of this migration didn't take into consideration apps that were installed prior to [Rocket.Chat@3.8.0](https://github.com/RocketChat/Rocket.Chat/releases/tag/3.8.0), which [removed the typescript compiler from the server](https://github.com/RocketChat/Rocket.Chat/pull/18687) and into the CLI. As a result, the zip files inside each installed app's document in the database had typescript files in them instead of the now required javascript files. - - As the new strategy of source code storage for apps changes the way the app is loaded, those zip files containing the source code are read everytime the app is started (or [in this particular case, updated](https://github.com/RocketChat/Rocket.Chat/pull/23286/files#diff-caf9f7a22478639e58d6514be039140a42ce1ab2d999c3efe5678c38ee36d0ccR43)), and as the zips' contents were wrong, the operation was failing. - + The previous version of this migration didn't take into consideration apps that were installed prior to [Rocket.Chat@3.8.0](https://github.com/RocketChat/Rocket.Chat/releases/tag/3.8.0), which [removed the typescript compiler from the server](https://github.com/RocketChat/Rocket.Chat/pull/18687) and into the CLI. As a result, the zip files inside each installed app's document in the database had typescript files in them instead of the now required javascript files. + + As the new strategy of source code storage for apps changes the way the app is loaded, those zip files containing the source code are read everytime the app is started (or [in this particular case, updated](https://github.com/RocketChat/Rocket.Chat/pull/23286/files#diff-caf9f7a22478639e58d6514be039140a42ce1ab2d999c3efe5678c38ee36d0ccR43)), and as the zips' contents were wrong, the operation was failing. + The fix extract the data from old apps and creates new zip files with the compiled `js` already present. - Regression: Fix Bugsnag not started error ([#23308](https://github.com/RocketChat/Rocket.Chat/pull/23308)) @@ -1039,10 +1033,8 @@ - **ENTERPRISE:** Maximum waiting time for chats in Omnichannel queue ([#22955](https://github.com/RocketChat/Rocket.Chat/pull/22955)) - - Add new settings to support closing chats that have been too long on waiting queue - - - Moved old settings to new "Queue Management" section - + - Add new settings to support closing chats that have been too long on waiting queue + - Moved old settings to new "Queue Management" section - Fix issue when closing a livechat room that caused client to not to know if room was open or not - Banner for the updates regarding authentication services ([#23055](https://github.com/RocketChat/Rocket.Chat/pull/23055) by [@g-thome](https://github.com/g-thome)) @@ -1057,10 +1049,10 @@ - Separate RegEx Settings for Channels and Usernames validation ([#21937](https://github.com/RocketChat/Rocket.Chat/pull/21937) by [@aditya-mitra](https://github.com/aditya-mitra)) - Now, there are 2 separate settings for validating names - One for **channels** and another for **usernames**. - - This change also removes the old `UTF8_Names_Validation` setting and adds 2 new settings `UTF8_User_Names_Validation` and `UTF8_Channel_Names_Validation`. - + Now, there are 2 separate settings for validating names - One for **channels** and another for **usernames**. + + This change also removes the old `UTF8_Names_Validation` setting and adds 2 new settings `UTF8_User_Names_Validation` and `UTF8_Channel_Names_Validation`. + https://user-images.githubusercontent.com/55396651/116969904-af5bb800-acd4-11eb-9fc4-dacac60cb08f.mp4 ### 🚀 Improvements @@ -1076,13 +1068,13 @@ - Rewrite File Upload Modal ([#22750](https://github.com/RocketChat/Rocket.Chat/pull/22750)) - Image preview: - ![image](https://user-images.githubusercontent.com/40830821/127223432-dccd2182-aec0-430f-8d70-03ac88aec791.png) - - Video preview: - ![image](https://user-images.githubusercontent.com/40830821/127225982-f8b21840-0d9c-4aff-a354-16188c7ed66e.png) - - Files larger than 10mb: + Image preview: + ![image](https://user-images.githubusercontent.com/40830821/127223432-dccd2182-aec0-430f-8d70-03ac88aec791.png) + + Video preview: + ![image](https://user-images.githubusercontent.com/40830821/127225982-f8b21840-0d9c-4aff-a354-16188c7ed66e.png) + + Files larger than 10mb: ![image](https://user-images.githubusercontent.com/40830821/127222611-5265040f-a06b-4ec5-b528-89b40e6a9072.png) - Types from currentChatsPage.tsx ([#22967](https://github.com/RocketChat/Rocket.Chat/pull/22967)) @@ -1098,14 +1090,14 @@ - "Users By Time of the Day" chart displays incorrect data for Local Timezone ([#22836](https://github.com/RocketChat/Rocket.Chat/pull/22836)) - - Add local timezone conversion to the "Users By Time of the Day" chart in the Engagement Dashboard; + - Add local timezone conversion to the "Users By Time of the Day" chart in the Engagement Dashboard; - Simplify date creations by using `endOf` and `startOf` methods. - Atlassian Crowd connection not working ([#22996](https://github.com/RocketChat/Rocket.Chat/pull/22996) by [@piotrkochan](https://github.com/piotrkochan)) - Audio recording doesn't stop in direct messages on channel switch ([#22880](https://github.com/RocketChat/Rocket.Chat/pull/22880)) - - Cancel audio recordings on message bar destroy event. + - Cancel audio recordings on message bar destroy event. ![test-22372](https://user-images.githubusercontent.com/36537004/128569780-d83747b0-fb9c-4dc6-9bc5-7ae573e720c8.gif) - Bad words falling if message is empty ([#22930](https://github.com/RocketChat/Rocket.Chat/pull/22930)) @@ -1130,23 +1122,21 @@ - Return transcript/dashboards based on timezone settings ([#22850](https://github.com/RocketChat/Rocket.Chat/pull/22850)) - - Added new setting to manage timezones - - - Applied new setting to omnichannel dashboards (realtime, analytics) [NOTE: Other dashboards aren't using this setting actually) - + - Added new setting to manage timezones + - Applied new setting to omnichannel dashboards (realtime, analytics) [NOTE: Other dashboards aren't using this setting actually) - Change getAnalyticsBetweenDate query to filter out system messages instead of substracting them - Tab margin style ([#22851](https://github.com/RocketChat/Rocket.Chat/pull/22851)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/128103848-2a25ba7e-0e59-4502-9bcd-2569cad9379a.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/128103848-2a25ba7e-0e59-4502-9bcd-2569cad9379a.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/128103633-ec7b93fc-4667-4dc9-bad3-bfffaff3974e.png) - Threads and discussions searches don't display proper results ([#22914](https://github.com/RocketChat/Rocket.Chat/pull/22914)) - - _Fix_ issue in discussions search (which wasn't working after a search with no results was made); + - _Fix_ issue in discussions search (which wasn't working after a search with no results was made); - _Improve_ discussions and threads searches: both searches (`chat.getDiscussions` and `chat.getThreadsList`) are now case insensitive (do NOT differ capital from lower letters) and match incomplete words or terms. - Threads List being requested more than expected ([#22879](https://github.com/RocketChat/Rocket.Chat/pull/22879)) @@ -1181,8 +1171,8 @@ - Chore: Script to start Rocket.Chat in HA mode during development ([#22398](https://github.com/RocketChat/Rocket.Chat/pull/22398)) - Sometimes we need to start Rocket.Chat in High-Availability mode (cluster) during development to test how a feature behaves or hunt down a bug. Currently, this involves a lot of commands with details that might be lost if you haven't done it in a while. - + Sometimes we need to start Rocket.Chat in High-Availability mode (cluster) during development to test how a feature behaves or hunt down a bug. Currently, this involves a lot of commands with details that might be lost if you haven't done it in a while. + This PR intends to provide a really simple way for us to start many instances of Rocket.Chat connected in a cluster. - Chore: Update Livechat widget to 1.9.4 ([#22990](https://github.com/RocketChat/Rocket.Chat/pull/22990)) @@ -1199,13 +1189,13 @@ - Regression: File upload name suggestion ([#22953](https://github.com/RocketChat/Rocket.Chat/pull/22953)) - Before: - ![image](https://user-images.githubusercontent.com/40830821/129774936-ecdbe9a1-5e3f-4a0a-ad1e-6f13eb15c60b.png) - ![image](https://user-images.githubusercontent.com/40830821/129775011-fb0df01d-74e4-41ae-bb47-dcf4cc17735e.png) - - - After: - ![image](https://user-images.githubusercontent.com/40830821/129774877-928a8aa0-c003-4e57-8b33-ea6accc32774.png) + Before: + ![image](https://user-images.githubusercontent.com/40830821/129774936-ecdbe9a1-5e3f-4a0a-ad1e-6f13eb15c60b.png) + ![image](https://user-images.githubusercontent.com/40830821/129775011-fb0df01d-74e4-41ae-bb47-dcf4cc17735e.png) + + + After: + ![image](https://user-images.githubusercontent.com/40830821/129774877-928a8aa0-c003-4e57-8b33-ea6accc32774.png) ![image](https://user-images.githubusercontent.com/40830821/129774972-d67debaf-0ce9-44fb-93cb-d7612dd18edf.png) - Regression: Fix creation of self-DMs ([#23015](https://github.com/RocketChat/Rocket.Chat/pull/23015)) @@ -1273,8 +1263,7 @@ - Fix Auto Selection algorithm on community edition ([#22991](https://github.com/RocketChat/Rocket.Chat/pull/22991)) - - When using the autoselection algo on community editions, all agents were marked as unavailable due to an unapplied filter - + - When using the autoselection algo on community editions, all agents were marked as unavailable due to an unapplied filter - Fixed an issue when both user & system setting to manange EE max number of chats allowed were set to 0
@@ -1314,7 +1303,7 @@ - Apps-Engine's scheduler failing to update run tasks ([#22882](https://github.com/RocketChat/Rocket.Chat/pull/22882)) - [Agenda](https://github.com/agenda/agenda), the library that manages scheduling, depended on setting a job property named `nextRunAt` as `undefined` to signal whether it should be run on schedule or not. [Rocket.Chat's current Mongo driver](https://github.com/RocketChat/Rocket.Chat/pull/22399) ignores `undefined` values when updating documents and this was causing jobs to never stop running as Agenda couldn't clear that property (set them as `undefined`). + [Agenda](https://github.com/agenda/agenda), the library that manages scheduling, depended on setting a job property named `nextRunAt` as `undefined` to signal whether it should be run on schedule or not. [Rocket.Chat's current Mongo driver](https://github.com/RocketChat/Rocket.Chat/pull/22399) ignores `undefined` values when updating documents and this was causing jobs to never stop running as Agenda couldn't clear that property (set them as `undefined`). This updates Rocket.Chat's dependency on Agenda.js to point to [a fork that fixes the problem](https://github.com/RocketChat/agenda/releases/tag/3.1.2). - Close omnichannel conversations when agent is deactivated ([#22917](https://github.com/RocketChat/Rocket.Chat/pull/22917)) @@ -1368,7 +1357,7 @@ - Monitoring Track messages' round trip time ([#22676](https://github.com/RocketChat/Rocket.Chat/pull/22676)) - Track messages' roundtrip time from backend saves time to the time when received back from the oplog allowing track of oplog slowness. + Track messages' roundtrip time from backend saves time to the time when received back from the oplog allowing track of oplog slowness. Prometheus metric: `rocketchat_messages_roundtrip_time` - REST endpoint to remove User from Role ([#20485](https://github.com/RocketChat/Rocket.Chat/pull/20485) by [@Cosnavel](https://github.com/Cosnavel) & [@lucassartor](https://github.com/lucassartor)) @@ -1380,22 +1369,19 @@ - Change message deletion confirmation modal to toast ([#22544](https://github.com/RocketChat/Rocket.Chat/pull/22544)) - Changed a timed modal for a toast message + Changed a timed modal for a toast message ![image](https://user-images.githubusercontent.com/40830821/124192670-0646f900-da9c-11eb-941c-9ae35421f6ef.png) - Configuration for indices in Apps-Engine models ([#22705](https://github.com/RocketChat/Rocket.Chat/pull/22705)) - * Add `appId` field to the data saved by the Scheduler - - * Add `appId` index to `rocketchat_apps_persistence` model - - * Skip "trash collection" when deleting records from `rocketchat_apps_persistence` - - * Add a new setting to control for how long we should keep logs from the apps - - ![image](https://user-images.githubusercontent.com/1810309/126246666-907f9d98-1d84-4dfe-a80a-7dd874d36fa8.png) - - + * Add `appId` field to the data saved by the Scheduler + * Add `appId` index to `rocketchat_apps_persistence` model + * Skip "trash collection" when deleting records from `rocketchat_apps_persistence` + * Add a new setting to control for how long we should keep logs from the apps + + ![image](https://user-images.githubusercontent.com/1810309/126246666-907f9d98-1d84-4dfe-a80a-7dd874d36fa8.png) + + ![image](https://user-images.githubusercontent.com/1810309/126246655-2ce3cb5f-b2f5-456e-a9c4-beccd9b3ef41.png) - Make `shortcut` field of canned responses unique ([#22700](https://github.com/RocketChat/Rocket.Chat/pull/22700)) @@ -1418,38 +1404,37 @@ - Replace remaing discussion creation modals with React modal. ([#22448](https://github.com/RocketChat/Rocket.Chat/pull/22448)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/123840524-cbe72b80-d8e4-11eb-9ddb-23a9f9d90aac.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/123840524-cbe72b80-d8e4-11eb-9ddb-23a9f9d90aac.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/123840219-74e15680-d8e4-11eb-95aa-00a990ffe0e7.png) - Return open room if available for visitors ([#22742](https://github.com/RocketChat/Rocket.Chat/pull/22742)) - Rewrite Enter Encryption Password Modal ([#22456](https://github.com/RocketChat/Rocket.Chat/pull/22456)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/123182889-bbf3c580-d466-11eb-8d4d-9cfc3d224e33.png) - - ### after - ![image](https://user-images.githubusercontent.com/27704687/123182916-cada7800-d466-11eb-96ee-850be190d419.png) - - ### Aditional Improves: - + ### before + ![image](https://user-images.githubusercontent.com/27704687/123182889-bbf3c580-d466-11eb-8d4d-9cfc3d224e33.png) + + ### after + ![image](https://user-images.githubusercontent.com/27704687/123182916-cada7800-d466-11eb-96ee-850be190d419.png) + + ### Aditional Improves: - Added a visual validation in the password field - Rewrite OTR modals ([#22583](https://github.com/RocketChat/Rocket.Chat/pull/22583)) - ![image](https://user-images.githubusercontent.com/40830821/124513267-cb510800-ddb0-11eb-8165-f103029c348f.png) - ![image](https://user-images.githubusercontent.com/40830821/124513354-04897800-ddb1-11eb-96f4-41fe906ca0d7.png) + ![image](https://user-images.githubusercontent.com/40830821/124513267-cb510800-ddb0-11eb-8165-f103029c348f.png) + ![image](https://user-images.githubusercontent.com/40830821/124513354-04897800-ddb1-11eb-96f4-41fe906ca0d7.png) ![image](https://user-images.githubusercontent.com/40830821/124513395-1b2fcf00-ddb1-11eb-83e4-3f8f9b4676ba.png) - Rewrite Save Encryption Password Modal ([#22447](https://github.com/RocketChat/Rocket.Chat/pull/22447)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/122980201-c337a800-d36e-11eb-8e2b-68534cea8e1e.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/122980201-c337a800-d36e-11eb-8e2b-68534cea8e1e.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/122980409-f8dc9100-d36e-11eb-9c15-aff779c84a91.png) - Rewrite sidebar footer as React Component ([#22687](https://github.com/RocketChat/Rocket.Chat/pull/22687)) @@ -1464,12 +1449,12 @@ - Wrong error message when trying to create a blocked username ([#22452](https://github.com/RocketChat/Rocket.Chat/pull/22452) by [@lucassartor](https://github.com/lucassartor)) - When trying to create a user with a blocked username, the UI was showing generic error message that it wasn't very detailed. - - Old error message: - ![image](https://user-images.githubusercontent.com/49413772/123120080-6d203e80-d41a-11eb-8c87-64e34334c856.png) - - New error message: + When trying to create a user with a blocked username, the UI was showing generic error message that it wasn't very detailed. + + Old error message: + ![image](https://user-images.githubusercontent.com/49413772/123120080-6d203e80-d41a-11eb-8c87-64e34334c856.png) + + New error message: ![aaa](https://user-images.githubusercontent.com/49413772/123120251-8c1ed080-d41a-11eb-8dc2-d7484923d851.PNG) ### 🐛 Bug fixes @@ -1477,19 +1462,19 @@ - **ENTERPRISE:** Engagement Dashboard displaying incorrect data about active users ([#22381](https://github.com/RocketChat/Rocket.Chat/pull/22381)) - - Fix sessions' and users' grouping in the Engagement Dashboard API endpoints; - - Fix the data displayed in the charts from the "Active users", "Users by time of day" and "When is the chat busier?" sections of the Engagement Dashboard; + - Fix sessions' and users' grouping in the Engagement Dashboard API endpoints; + - Fix the data displayed in the charts from the "Active users", "Users by time of day" and "When is the chat busier?" sections of the Engagement Dashboard; - Replace label used to describe the amount of Active Users in the License section of the Info page. - **ENTERPRISE:** Make AutoSelect algo take current agent load in consideration ([#22611](https://github.com/RocketChat/Rocket.Chat/pull/22611)) - **ENTERPRISE:** Race condition on Omnichannel visitor abandoned callback ([#22413](https://github.com/RocketChat/Rocket.Chat/pull/22413)) - As you can see [here](https://github.com/RocketChat/Rocket.Chat/blob/857791c39c97b51b5b6fd3718e0c816959a81c3b/ee/app/livechat-enterprise/server/lib/Helper.js#L127) the `predictedVisitorAbandonment` flag is not set if the room object doesn't have `v.lastMessageTs` property. So we need to always make sure the `v.lastMessageTs` is set before this method is called. - - Currently the `v.lastMessageTs` is being set in [this](https://github.com/RocketChat/Rocket.Chat/blob/857791c39c97b51b5b6fd3718e0c816959a81c3b/app/livechat/server/hooks/saveLastVisitorMessageTs.js#L4) (lets call this **hook-1**) hook which has `HIGH` priority - and the `predictedVisitorAbandonment` check is inturn performed in [this](https://github.com/RocketChat/Rocket.Chat/blob/857791c39c97b51b5b6fd3718e0c816959a81c3b/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.js#L5) (let call this **hook-2**) hook which is also `HIGH` priority. - + As you can see [here](https://github.com/RocketChat/Rocket.Chat/blob/857791c39c97b51b5b6fd3718e0c816959a81c3b/ee/app/livechat-enterprise/server/lib/Helper.js#L127) the `predictedVisitorAbandonment` flag is not set if the room object doesn't have `v.lastMessageTs` property. So we need to always make sure the `v.lastMessageTs` is set before this method is called. + + Currently the `v.lastMessageTs` is being set in [this](https://github.com/RocketChat/Rocket.Chat/blob/857791c39c97b51b5b6fd3718e0c816959a81c3b/app/livechat/server/hooks/saveLastVisitorMessageTs.js#L4) (lets call this **hook-1**) hook which has `HIGH` priority + and the `predictedVisitorAbandonment` check is inturn performed in [this](https://github.com/RocketChat/Rocket.Chat/blob/857791c39c97b51b5b6fd3718e0c816959a81c3b/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.js#L5) (let call this **hook-2**) hook which is also `HIGH` priority. + So ideally we'd except the **hook-1** to be called b4 **hook-2**, however currently since both of them are at same priority, there is no way to control which one is executed first. Hence in this PR, I'm making the priority of **hook-2** as `MEDIUM` to keeping the priority of **hook-1** the same as b4, i.e. `HIGH`. This should make sure that the **hook-1** is always executed b4 **hook-2** - Admin page crashing when commit hash is null ([#22057](https://github.com/RocketChat/Rocket.Chat/pull/22057) by [@cprice-kgi](https://github.com/cprice-kgi)) @@ -1498,41 +1483,39 @@ - Blank screen in message auditing DM tab ([#22763](https://github.com/RocketChat/Rocket.Chat/pull/22763)) - The DM tab in message auditing was displaying a blank screen, instead of the actual tab. - + The DM tab in message auditing was displaying a blank screen, instead of the actual tab. + ![image](https://user-images.githubusercontent.com/28611993/127041404-dfca7f6a-2b8b-4c15-9cbd-c6238fac0063.png) - Bugs in AutoCompleteDepartment ([#22414](https://github.com/RocketChat/Rocket.Chat/pull/22414)) - Call button is still displayed when the user doesn't have permission to use it ([#22170](https://github.com/RocketChat/Rocket.Chat/pull/22170)) - - Hide 'Call' buttons from the tab bar for muted users; - + - Hide 'Call' buttons from the tab bar for muted users; - Display an error when a muted user attempts to enter a call using the 'Click to Join!' button. - Can't see full user profile on team's room ([#22355](https://github.com/RocketChat/Rocket.Chat/pull/22355)) - ### before - ![before](https://user-images.githubusercontent.com/27704687/121966860-bbac4980-cd45-11eb-8d48-2b0457110fc7.gif) - - ### after - ![after](https://user-images.githubusercontent.com/27704687/121966870-bea73a00-cd45-11eb-9c89-ec52ac17e20f.gif) - - ### aditional fix :rocket: - + ### before + ![before](https://user-images.githubusercontent.com/27704687/121966860-bbac4980-cd45-11eb-8d48-2b0457110fc7.gif) + + ### after + ![after](https://user-images.githubusercontent.com/27704687/121966870-bea73a00-cd45-11eb-9c89-ec52ac17e20f.gif) + + ### aditional fix :rocket: - unnecessary `TeamsMembers` component removed - Cannot create a discussion from top left sidebar as a user ([#22618](https://github.com/RocketChat/Rocket.Chat/pull/22618) by [@lucassartor](https://github.com/lucassartor)) - When trying to create a discussion using the top left sidebar modal with an role that don't have the `view-other-user-channels ` permission, an empty list would be shown, which is a wrong behavior. - Also, when being able to use this modal, discussions were listed as options, which is also a wrong behavior as there can't be nested discussions. - - This PR looks to fix both these issues. - - **Old behavior:** - ![old](https://user-images.githubusercontent.com/49413772/124960017-3c333280-dff2-11eb-86cd-b2638311517e.png) - - **New behavior:** + When trying to create a discussion using the top left sidebar modal with an role that don't have the `view-other-user-channels ` permission, an empty list would be shown, which is a wrong behavior. + Also, when being able to use this modal, discussions were listed as options, which is also a wrong behavior as there can't be nested discussions. + + This PR looks to fix both these issues. + + **Old behavior:** + ![old](https://user-images.githubusercontent.com/49413772/124960017-3c333280-dff2-11eb-86cd-b2638311517e.png) + + **New behavior:** ![image](https://user-images.githubusercontent.com/49413772/124958882-05a8e800-dff1-11eb-8203-b34a4f1c98a0.png) - Channel is automatically getting added to the first option in move to team feature ([#22670](https://github.com/RocketChat/Rocket.Chat/pull/22670)) @@ -1547,12 +1530,12 @@ - Create discussion modal - cancel button and invite users alignment ([#22718](https://github.com/RocketChat/Rocket.Chat/pull/22718)) - Changes in "open discussion" modal - - > Added cancel button - > Fixed alignment in invite user - - + Changes in "open discussion" modal + + > Added cancel button + > Fixed alignment in invite user + + ![image](https://user-images.githubusercontent.com/28611993/126388304-6ac76574-6924-426e-843d-afd53dc1c874.png) - crush in the getChannelHistory method ([#22667](https://github.com/RocketChat/Rocket.Chat/pull/22667) by [@MaestroArt](https://github.com/MaestroArt)) @@ -1587,29 +1570,27 @@ - Quote message not working for Livechat visitors ([#22586](https://github.com/RocketChat/Rocket.Chat/pull/22586)) - ### Before: - ![image](https://user-images.githubusercontent.com/34130764/124583613-de2b1180-de70-11eb-82aa-18564b317626.png) - ### After: + ### Before: + ![image](https://user-images.githubusercontent.com/34130764/124583613-de2b1180-de70-11eb-82aa-18564b317626.png) + ### After: ![image](https://user-images.githubusercontent.com/34130764/124583775-12063700-de71-11eb-8ab5-b0169fac2d40.png) - Redirect to login after delete own account ([#22499](https://github.com/RocketChat/Rocket.Chat/pull/22499)) - Redirect the user to login after delete own account - - ### Aditional fixes: - - - Visual issue in password input on Delete Own Account Modal - - ### before - ![image](https://user-images.githubusercontent.com/27704687/123711503-f5ea1080-d846-11eb-96aa-8ed638ca665c.png) - - ### after + Redirect the user to login after delete own account + + ### Aditional fixes: + - Visual issue in password input on Delete Own Account Modal + + ### before + ![image](https://user-images.githubusercontent.com/27704687/123711503-f5ea1080-d846-11eb-96aa-8ed638ca665c.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/123711336-b3c0cf00-d846-11eb-9408-a686d8668ba5.png) - Remove stack traces from Meteor errors when debug setting is disabled ([#22699](https://github.com/RocketChat/Rocket.Chat/pull/22699)) - - Fix 'not iterable' errors in the `normalizeMessage` function; - + - Fix 'not iterable' errors in the `normalizeMessage` function; - Remove stack traces from errors thrown by the `jitsi:updateTimeout` (and other `Meteor.Error`s) method. - Rewrite CurrentChats to TS ([#22424](https://github.com/RocketChat/Rocket.Chat/pull/22424)) @@ -1698,16 +1679,15 @@ - Regression: Data in the "Active Users" section is delayed in 1 day ([#22794](https://github.com/RocketChat/Rocket.Chat/pull/22794)) - - Fix 1 day delay in the Engagement Dashboard's "Active Users" section; - - - Downgrade `@nivo/line` version. - **Expected behavior:** + - Fix 1 day delay in the Engagement Dashboard's "Active Users" section; + - Downgrade `@nivo/line` version. + **Expected behavior:** ![active-users-engagement-dashboard](https://user-images.githubusercontent.com/36537004/127372185-390dc42f-bc90-4841-a22b-731f0aafcafe.PNG) - Regression: Data in the "New Users" section is delayed in 1 day ([#22751](https://github.com/RocketChat/Rocket.Chat/pull/22751)) - - Update nivo version (which was causing errors in the bar chart); - - Fix 1 day delay in '7 days' and '30 days' periods; + - Update nivo version (which was causing errors in the bar chart); + - Fix 1 day delay in '7 days' and '30 days' periods; - Update tooltip theme. - Regression: Federation warnings on ci ([#22765](https://github.com/RocketChat/Rocket.Chat/pull/22765) by [@g-thome](https://github.com/g-thome)) @@ -1732,9 +1712,9 @@ - Regression: Fix tooltip style in the "Busiest Chat Times" chart ([#22813](https://github.com/RocketChat/Rocket.Chat/pull/22813)) - - Fix tooltip in the Engagement Dashboard's "Busiest Chat Times" chart (Hours). - - **Expected behavior:** + - Fix tooltip in the Engagement Dashboard's "Busiest Chat Times" chart (Hours). + + **Expected behavior:** ![busiest-times-ed](https://user-images.githubusercontent.com/36537004/127527827-465397ed-f089-4fb7-9ab2-6fa8cea6abdf.PNG) - Regression: Fix users not being able to see the scope of the canned m… ([#22760](https://github.com/RocketChat/Rocket.Chat/pull/22760)) @@ -1751,10 +1731,10 @@ - Regression: Prevent custom status from being visible in sequential messages ([#22733](https://github.com/RocketChat/Rocket.Chat/pull/22733)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/126641946-866dae96-1983-43a5-b689-b24670473ad0.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/126641946-866dae96-1983-43a5-b689-b24670473ad0.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/126641752-3163eb95-1cd4-4d99-a61a-4d06d9e7e13e.png) - Regression: Properly force newline in attachment fields ([#22727](https://github.com/RocketChat/Rocket.Chat/pull/22727)) @@ -1935,32 +1915,30 @@ - Add `teams.convertToChannel` endpoint ([#22188](https://github.com/RocketChat/Rocket.Chat/pull/22188)) - - Add new `teams.converToChannel` endpoint; - - - Update `ConvertToTeam` modal text (since this action can now be reversed); - + - Add new `teams.converToChannel` endpoint; + - Update `ConvertToTeam` modal text (since this action can now be reversed); - Remove corresponding team memberships when a team is deleted or converted to a channel; - Add setting to configure default role for user on manual registration ([#20650](https://github.com/RocketChat/Rocket.Chat/pull/20650) by [@lucassartor](https://github.com/lucassartor)) - Add an `admin` setting to determine the initial `role` for new users who registered manually (through the register form and via API, not using an authentication service), normally all new users are assigned to the `user` role. - - The setting can be found in `Admin`->`Accounts`->`Registration`. - - ![image](https://user-images.githubusercontent.com/49413772/107252603-47b70900-6a14-11eb-9cc6-df76720b7365.png) - The setting initial value is false, so the default behaviour stays the same while creating a new server or upgrading one. - - https://user-images.githubusercontent.com/49413772/107253220-ddeb2f00-6a14-11eb-85b4-f770dbbe4970.mp4 - + Add an `admin` setting to determine the initial `role` for new users who registered manually (through the register form and via API, not using an authentication service), normally all new users are assigned to the `user` role. + + The setting can be found in `Admin`->`Accounts`->`Registration`. + + ![image](https://user-images.githubusercontent.com/49413772/107252603-47b70900-6a14-11eb-9cc6-df76720b7365.png) + The setting initial value is false, so the default behaviour stays the same while creating a new server or upgrading one. + + https://user-images.githubusercontent.com/49413772/107253220-ddeb2f00-6a14-11eb-85b4-f770dbbe4970.mp4 + Video showing an example of the setting being used and creating an new user with the default roles via API. - Content-Security-Policy for inline scripts ([#20724](https://github.com/RocketChat/Rocket.Chat/pull/20724)) - Security policies were applied for inline scripts cases. Due to the libraries and components we use it is not possible to disable inline styles and images as they would break Oembeds and other libraries. - - - basically the inline scripts were moved to a js file - + Security policies were applied for inline scripts cases. Due to the libraries and components we use it is not possible to disable inline styles and images as they would break Oembeds and other libraries. + + + basically the inline scripts were moved to a js file + and besides that some suggars syntax like `addScript` and `addStyle` were added, this way the application already takes care of inserting the elements and providing the content automatically. - Open modals in side effects outside React ([#22247](https://github.com/RocketChat/Rocket.Chat/pull/22247)) @@ -1976,17 +1954,15 @@ - Add BBB and Jitsi to Team ([#22312](https://github.com/RocketChat/Rocket.Chat/pull/22312)) - Added 2 new settings: - - - `Admin > Video Conference > Big Blue Button > Enable for teams` - + Added 2 new settings: + - `Admin > Video Conference > Big Blue Button > Enable for teams` - `Admin > Video Conference > Jitsi > Enable in teams` - Add debouncing to units selects filters ([#22097](https://github.com/RocketChat/Rocket.Chat/pull/22097)) - Add modal to close chats when tags/comments are not required ([#22245](https://github.com/RocketChat/Rocket.Chat/pull/22245) by [@rafaelblink](https://github.com/rafaelblink)) - When neither tags or comments are required to close a livechat, show this modal instead: + When neither tags or comments are required to close a livechat, show this modal instead: ![Screen Shot 2021-05-20 at 7 33 19 PM](https://user-images.githubusercontent.com/20868078/119057741-6af23c80-b9a3-11eb-902f-f8a7458ad11c.png) - Fallback messages on contextual bar ([#22376](https://github.com/RocketChat/Rocket.Chat/pull/22376)) @@ -2009,10 +1985,10 @@ - Remove differentiation between public x private channels in sidebar ([#22160](https://github.com/RocketChat/Rocket.Chat/pull/22160)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/119752184-e7d55880-be72-11eb-9167-be2f305ddb3f.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/119752184-e7d55880-be72-11eb-9167-be2f305ddb3f.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/119752125-c8d6c680-be72-11eb-8444-2e0c7cb1c600.png) - Rewrite create direct modal ([#22209](https://github.com/RocketChat/Rocket.Chat/pull/22209)) @@ -2021,8 +1997,8 @@ - Rewrite Create Discussion Modal (only through sidebar) ([#22224](https://github.com/RocketChat/Rocket.Chat/pull/22224)) - This is only available by creating a new discussion when clicking on the sidebar button. Other places will be implemented afterwards. - + This is only available by creating a new discussion when clicking on the sidebar button. Other places will be implemented afterwards. + ![image](https://user-images.githubusercontent.com/40830821/120556093-6af63180-c3d2-11eb-97ea-63c5423049dc.png) - Send only relevant data via WebSocket ([#22258](https://github.com/RocketChat/Rocket.Chat/pull/22258)) @@ -2036,12 +2012,12 @@ - **EE:** Canned responses can't be deleted ([#22095](https://github.com/RocketChat/Rocket.Chat/pull/22095) by [@rafaelblink](https://github.com/rafaelblink)) - Deletion button has been removed from the edition option. - - ## Before - ![image](https://user-images.githubusercontent.com/2493803/119059416-9f1b2c80-b9a6-11eb-933a-4efa1ac0552a.png) - - ### After + Deletion button has been removed from the edition option. + + ## Before + ![image](https://user-images.githubusercontent.com/2493803/119059416-9f1b2c80-b9a6-11eb-933a-4efa1ac0552a.png) + + ### After ![Rocket Chat (2)](https://user-images.githubusercontent.com/2493803/119172517-72b1ef80-ba3c-11eb-9178-04a12176f312.gif) - **ENTERPRISE:** Omnichannel enterprise permissions being added back to its default roles ([#22322](https://github.com/RocketChat/Rocket.Chat/pull/22322)) @@ -2050,19 +2026,19 @@ - **ENTERPRISE:** Prevent Visitor Abandonment after forwarding chat ([#22243](https://github.com/RocketChat/Rocket.Chat/pull/22243)) - Currently the Visitor Abandonment timer isn't affected when the chat is forwarded. However this is affecting the UX in certain situations like eg: A bot forwarding a chat to an human agent - ![image](https://user-images.githubusercontent.com/34130764/120896383-e4925780-c63e-11eb-937e-ffd7c4836159.png) - + Currently the Visitor Abandonment timer isn't affected when the chat is forwarded. However this is affecting the UX in certain situations like eg: A bot forwarding a chat to an human agent + ![image](https://user-images.githubusercontent.com/34130764/120896383-e4925780-c63e-11eb-937e-ffd7c4836159.png) + To solve this issue, we'll now be stoping the Visitor Abandonment timer once a chat is forwarded. - **IMPROVE:** Prevent creation of duplicated roles and new `roles.update` endpoint ([#22279](https://github.com/RocketChat/Rocket.Chat/pull/22279) by [@lucassartor](https://github.com/lucassartor)) - Currently, the action of updating a role is broken: because roles have their `_id` = `name`, when updating a role there's no way to validate if the user is trying to update or create a new role with a name that already exists - which causes wrong behaviors, such as roles with the same name and not being able to update them. - - To proper fix this, this PR looks to change the creation of roles. Now, roles have a unique `_id` value and there's a endpoint to update roles: `/api/v1/roles.update`. - - Doing so, it's possible to validate on both endpoints (`roles.create` and `roles.update`) to not allow roles with duplicated names. - + Currently, the action of updating a role is broken: because roles have their `_id` = `name`, when updating a role there's no way to validate if the user is trying to update or create a new role with a name that already exists - which causes wrong behaviors, such as roles with the same name and not being able to update them. + + To proper fix this, this PR looks to change the creation of roles. Now, roles have a unique `_id` value and there's a endpoint to update roles: `/api/v1/roles.update`. + + Doing so, it's possible to validate on both endpoints (`roles.create` and `roles.update`) to not allow roles with duplicated names. + **OBS:** The unique id changes only reflect new roles, the standard roles (such as admin and user) still have `_id` = `name`, but new roles now **can't** have the same name as them. - `channels.history`, `groups.history` and `im.history` REST endpoints not respecting hide system message config ([#22364](https://github.com/RocketChat/Rocket.Chat/pull/22364)) @@ -2079,10 +2055,10 @@ - Can't delete file from Room's file list ([#22191](https://github.com/RocketChat/Rocket.Chat/pull/22191)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/120215931-bb239700-c20c-11eb-9494-d4bc017df390.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/120215931-bb239700-c20c-11eb-9494-d4bc017df390.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/120216113-f8882480-c20c-11eb-9afb-b127e66a43da.png) - Cancel button and success toast at Leave Team modal ([#22373](https://github.com/RocketChat/Rocket.Chat/pull/22373)) @@ -2093,10 +2069,10 @@ - Convert and Move team permission ([#22350](https://github.com/RocketChat/Rocket.Chat/pull/22350)) - ### before - https://user-images.githubusercontent.com/45966964/114909360-5c04f100-9e1d-11eb-9363-f308e5d0be68.mp4 - - ### after + ### before + https://user-images.githubusercontent.com/45966964/114909360-5c04f100-9e1d-11eb-9363-f308e5d0be68.mp4 + + ### after https://user-images.githubusercontent.com/45966964/114909388-61fad200-9e1d-11eb-9bbe-114b55954a9f.mp4 - CORS error while interacting with any action button on Livechat ([#22150](https://github.com/RocketChat/Rocket.Chat/pull/22150)) @@ -2115,50 +2091,50 @@ - Members tab visual issues ([#22138](https://github.com/RocketChat/Rocket.Chat/pull/22138)) - ## Before - ![image](https://user-images.githubusercontent.com/27704687/119558283-95fbd800-bd77-11eb-91b4-91821f365bf3.png) - - ## After + ## Before + ![image](https://user-images.githubusercontent.com/27704687/119558283-95fbd800-bd77-11eb-91b4-91821f365bf3.png) + + ## After ![image](https://user-images.githubusercontent.com/27704687/119558120-6947c080-bd77-11eb-8ecb-7fedc07afa82.png) - Memory leak generated by Stream Cast usage ([#22329](https://github.com/RocketChat/Rocket.Chat/pull/22329)) - Stream Cast uses a different approach to broadcast data to the instances, it uses the DDP subscription method that requires a collection on the other side, if no collection exists with the given name `broadcast-stream` it caches in memory waiting for the collection to be set later. The cache is cleared only when a reconnection happens. - + Stream Cast uses a different approach to broadcast data to the instances, it uses the DDP subscription method that requires a collection on the other side, if no collection exists with the given name `broadcast-stream` it caches in memory waiting for the collection to be set later. The cache is cleared only when a reconnection happens. + This PR overrides the function that processes the data for that specific connection, preventing the cache and everything else to be processed since we already have our low-level listener to process the data. - Message box hiding on mobile view (Safari) ([#22212](https://github.com/RocketChat/Rocket.Chat/pull/22212)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/120404256-5b1c1600-c31c-11eb-96e9-860e4132db5f.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/120404256-5b1c1600-c31c-11eb-96e9-860e4132db5f.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/120404406-acc4a080-c31c-11eb-9efb-c2ad88664fda.png) - Missing burger menu on direct messages ([#22211](https://github.com/RocketChat/Rocket.Chat/pull/22211)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/120403671-09bf5700-c31b-11eb-92a1-a2f589bd85fc.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/120403671-09bf5700-c31b-11eb-92a1-a2f589bd85fc.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/120403693-1643af80-c31b-11eb-8027-dbdc4f560647.png) - Missing Throbber while thread list is loading ([#22316](https://github.com/RocketChat/Rocket.Chat/pull/22316)) - ### before - List was starting with no results even if there's results: - - ![image](https://user-images.githubusercontent.com/27704687/121606744-1e8ba100-ca25-11eb-9b31-706fb998d05f.png) - - ### after + ### before + List was starting with no results even if there's results: + + ![image](https://user-images.githubusercontent.com/27704687/121606744-1e8ba100-ca25-11eb-9b31-706fb998d05f.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/121606635-e97f4e80-ca24-11eb-81f7-af8b0cc41c89.png) - Not possible to edit some messages inside threads ([#22325](https://github.com/RocketChat/Rocket.Chat/pull/22325)) - ### Before - ![before](https://user-images.githubusercontent.com/27704687/121755733-4eeb4200-caee-11eb-9d77-1b498c38c478.gif) - - ### After + ### Before + ![before](https://user-images.githubusercontent.com/27704687/121755733-4eeb4200-caee-11eb-9d77-1b498c38c478.gif) + + ### After ![after](https://user-images.githubusercontent.com/27704687/121755736-514d9c00-caee-11eb-9897-78fcead172f2.gif) - Notifications not using user's name ([#22309](https://github.com/RocketChat/Rocket.Chat/pull/22309)) @@ -2185,10 +2161,10 @@ - Sidebar not closing when clicking on a channel ([#22271](https://github.com/RocketChat/Rocket.Chat/pull/22271)) - ### before - ![before](https://user-images.githubusercontent.com/27704687/121074843-c6e20100-c7aa-11eb-88db-76e39b57b064.gif) - - ### after + ### before + ![before](https://user-images.githubusercontent.com/27704687/121074843-c6e20100-c7aa-11eb-88db-76e39b57b064.gif) + + ### after ![after](https://user-images.githubusercontent.com/27704687/121074860-cb0e1e80-c7aa-11eb-9e96-06d75044b763.gif) - Sound notification is not emitted when the Omnichannel chat comes from another department ([#22291](https://github.com/RocketChat/Rocket.Chat/pull/22291)) @@ -2199,9 +2175,9 @@ - Undefined error when forwarding chats to offline department ([#22154](https://github.com/RocketChat/Rocket.Chat/pull/22154) by [@rafaelblink](https://github.com/rafaelblink)) - ![Screen Shot 2021-05-26 at 5 29 17 PM](https://user-images.githubusercontent.com/59577424/119727520-c495b380-be48-11eb-88a2-158017c7ad0a.png) - - Omnichannel agents are facing the error shown above when forwarding chats to offline departments. + ![Screen Shot 2021-05-26 at 5 29 17 PM](https://user-images.githubusercontent.com/59577424/119727520-c495b380-be48-11eb-88a2-158017c7ad0a.png) + + Omnichannel agents are facing the error shown above when forwarding chats to offline departments. The error usually takes place when the routing system algorithm is **Manual Selection**. - Unread bar in channel flash quickly and then disappear ([#22275](https://github.com/RocketChat/Rocket.Chat/pull/22275)) @@ -2232,15 +2208,15 @@ - Chore: Change modals for remove user from team && leave team ([#22141](https://github.com/RocketChat/Rocket.Chat/pull/22141)) - ![image](https://user-images.githubusercontent.com/40830821/119576154-93f14380-bd8e-11eb-8885-f889f2939bf4.png) + ![image](https://user-images.githubusercontent.com/40830821/119576154-93f14380-bd8e-11eb-8885-f889f2939bf4.png) ![image](https://user-images.githubusercontent.com/40830821/119576219-b5eac600-bd8e-11eb-832c-ea7a17a56bdd.png) - Chore: Check PR Title on every submission ([#22140](https://github.com/RocketChat/Rocket.Chat/pull/22140)) - Chore: Enable push gateway only if the server is registered ([#22346](https://github.com/RocketChat/Rocket.Chat/pull/22346) by [@lucassartor](https://github.com/lucassartor)) - Currently, when creating an unregistered server, the default value of the push gateway setting is set to true and is disabled (it can't be changed unless the server is registered). This is a wrong behavior as an unregistered server **can't** use the push gateway. - + Currently, when creating an unregistered server, the default value of the push gateway setting is set to true and is disabled (it can't be changed unless the server is registered). This is a wrong behavior as an unregistered server **can't** use the push gateway. + This PR creates a validation to check if the server is registered when enabling the push gateway. That way, even if the push gateway setting is turned on, but the server is unregistered, the push gateway **won't** work - it will behave like it is off. - Chore: Enforce TypeScript on Storybook ([#22317](https://github.com/RocketChat/Rocket.Chat/pull/22317)) @@ -2257,7 +2233,7 @@ - Chore: Update delete team modal to new design ([#22127](https://github.com/RocketChat/Rocket.Chat/pull/22127)) - Now the modal has only 2 steps (steps 1 and 2 were merged) + Now the modal has only 2 steps (steps 1 and 2 were merged) ![image](https://user-images.githubusercontent.com/40830821/119414580-2e398480-bcc6-11eb-9a47-515568257974.png) - Language update from LingoHub 🤖 on 2021-05-31Z ([#22196](https://github.com/RocketChat/Rocket.Chat/pull/22196)) @@ -2284,10 +2260,10 @@ - Regression: Missing flexDirection on select field ([#22300](https://github.com/RocketChat/Rocket.Chat/pull/22300)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/121425905-532a2a80-c949-11eb-885f-e8ddaf5c8d5c.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/121425905-532a2a80-c949-11eb-885f-e8ddaf5c8d5c.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/121425770-283fd680-c949-11eb-8d94-86886f174599.png) - Regression: RoomProvider using wrong types ([#22370](https://github.com/RocketChat/Rocket.Chat/pull/22370)) @@ -2428,50 +2404,50 @@ - **ENTERPRISE:** Introduce Load Rotation routing algorithm for Omnichannel ([#22090](https://github.com/RocketChat/Rocket.Chat/pull/22090) by [@rafaelblink](https://github.com/rafaelblink)) - This PR introduces a new Auto Chat Distribution (ACD) algorithm for Omnichannel: **Load Rotation**. - The algorithm distributes chats to agents one by one, which means that when a new chat arrives, the agent with the oldest routing assignment time will be selected to serve the chat, regardless of the number of chats in progress each agent has. - + This PR introduces a new Auto Chat Distribution (ACD) algorithm for Omnichannel: **Load Rotation**. + The algorithm distributes chats to agents one by one, which means that when a new chat arrives, the agent with the oldest routing assignment time will be selected to serve the chat, regardless of the number of chats in progress each agent has. + ![Screen Shot 2021-05-20 at 5 17 40 PM](https://user-images.githubusercontent.com/59577424/119043752-c61a3400-b98f-11eb-8543-f3176879af1d.png) - Back button for Omnichannel ([#21647](https://github.com/RocketChat/Rocket.Chat/pull/21647) by [@rafaelblink](https://github.com/rafaelblink)) - New Message Parser ([#21962](https://github.com/RocketChat/Rocket.Chat/pull/21962)) - The objective is to put an end to the confusion that we face having multiple parsers, and the problems that this brings, it is still experimental then users need to choose to use it. - - The benefits are multiple. no more unexpected cases or grammatical collisions (in addition to more flexible nested cases like bold within link labels). - Besides, we no longer render raw html, instead we use components, so the xss attacks are over (the easy ones at least). Without further discoveries and at the fronted, we only reder what is delivered thus improving our performance. + The objective is to put an end to the confusion that we face having multiple parsers, and the problems that this brings, it is still experimental then users need to choose to use it. + + The benefits are multiple. no more unexpected cases or grammatical collisions (in addition to more flexible nested cases like bold within link labels). + Besides, we no longer render raw html, instead we use components, so the xss attacks are over (the easy ones at least). Without further discoveries and at the fronted, we only reder what is delivered thus improving our performance. This can be used in multiple places, (message, alert, sidenav and in the entire mobile application.) - Option to notify failed login attempts to a channel ([#21968](https://github.com/RocketChat/Rocket.Chat/pull/21968)) - Option to prevent users from using Invisible status ([#20084](https://github.com/RocketChat/Rocket.Chat/pull/20084) by [@lucassartor](https://github.com/lucassartor)) - Add an `admin` option to allow/disallow the `Invisible` status option from all users. This option is available in the `Accounts` section. - - ![2021-01-06-11-55-22](https://user-images.githubusercontent.com/49413772/103782988-ebc52300-5016-11eb-8a29-dd540c21e11c.gif) - - If the option is turned off, the `users.setStatus` endpoint is also restricted from users trying to change their status to `Invisible`, throwing the following error: - ```json - { - "success": false, - "error": "Invisible status is disabled [error-not-allowed]", - "stack": "Error: Invisible status is disabled [error-not-allowed]\n at DDPCommon.MethodInvocation. (app/api/server/v1/users.js:425:13)\n at packages/dispatch_run-as-user.js:211:14\n at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1234:12)\n at Object.Meteor.runAsUser (packages/dispatch_run-as-user.js:210:33)\n at Object.post (app/api/server/v1/users.js:415:10)\n at app/api/server/api.js:394:82\n at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1234:12)\n at Object._internalRouteActionHandler [as action] (app/api/server/api.js:394:39)\n at Route.share.Route.Route._callEndpoint (packages/nimble_restivus/lib/route.coffee:150:32)\n at packages/nimble_restivus/lib/route.coffee:59:33\n at packages/simple_json-routes.js:98:9", - "errorType": "error-not-allowed", - "details": { - "method": "users.setStatus" - } - } + Add an `admin` option to allow/disallow the `Invisible` status option from all users. This option is available in the `Accounts` section. + + ![2021-01-06-11-55-22](https://user-images.githubusercontent.com/49413772/103782988-ebc52300-5016-11eb-8a29-dd540c21e11c.gif) + + If the option is turned off, the `users.setStatus` endpoint is also restricted from users trying to change their status to `Invisible`, throwing the following error: + ```json + { + "success": false, + "error": "Invisible status is disabled [error-not-allowed]", + "stack": "Error: Invisible status is disabled [error-not-allowed]\n at DDPCommon.MethodInvocation. (app/api/server/v1/users.js:425:13)\n at packages/dispatch_run-as-user.js:211:14\n at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1234:12)\n at Object.Meteor.runAsUser (packages/dispatch_run-as-user.js:210:33)\n at Object.post (app/api/server/v1/users.js:415:10)\n at app/api/server/api.js:394:82\n at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1234:12)\n at Object._internalRouteActionHandler [as action] (app/api/server/api.js:394:39)\n at Route.share.Route.Route._callEndpoint (packages/nimble_restivus/lib/route.coffee:150:32)\n at packages/nimble_restivus/lib/route.coffee:59:33\n at packages/simple_json-routes.js:98:9", + "errorType": "error-not-allowed", + "details": { + "method": "users.setStatus" + } + } ``` - Paginated and Filtered selects on new/edit unit ([#22052](https://github.com/RocketChat/Rocket.Chat/pull/22052) by [@rafaelblink](https://github.com/rafaelblink)) - REQUIRES https://github.com/RocketChat/Rocket.Chat.Fuselage/pull/447 - - Adds infinite scrolling selects to the units edit/create with the ability to be filtered by text as well - - ![Screen Shot 2021-05-17 at 9 24 19 AM](https://user-images.githubusercontent.com/20868078/118487999-abc32a80-b6f1-11eb-8d58-d031111ea0fb.png) - + REQUIRES https://github.com/RocketChat/Rocket.Chat.Fuselage/pull/447 + + Adds infinite scrolling selects to the units edit/create with the ability to be filtered by text as well + + ![Screen Shot 2021-05-17 at 9 24 19 AM](https://user-images.githubusercontent.com/20868078/118487999-abc32a80-b6f1-11eb-8d58-d031111ea0fb.png) + This Affects the monitors and departments inputs - Remove exif metadata from uploaded files ([#22044](https://github.com/RocketChat/Rocket.Chat/pull/22044)) @@ -2499,17 +2475,13 @@ - Inconsistent and misleading 2FA settings ([#22042](https://github.com/RocketChat/Rocket.Chat/pull/22042) by [@lucassartor](https://github.com/lucassartor)) - Currently, there are some inconsistencies and incorrect behaviors on the 2FA settings, such as: - - - - When disabling the TOTP 2FA, all 2FA are disabled; - - - There are no option to disable only the TOTP 2FA; - - - If 2FA are disabled, the other settings aren't blocked (the e-mail 2FA setting, for example); - - - It lacks some labels to warn the user of some specific 2FA options. - + Currently, there are some inconsistencies and incorrect behaviors on the 2FA settings, such as: + + - When disabling the TOTP 2FA, all 2FA are disabled; + - There are no option to disable only the TOTP 2FA; + - If 2FA are disabled, the other settings aren't blocked (the e-mail 2FA setting, for example); + - It lacks some labels to warn the user of some specific 2FA options. + This PR looks to fix those issues. - LDAP port setting input type to allow only numbers ([#21912](https://github.com/RocketChat/Rocket.Chat/pull/21912) by [@Deepak-learner](https://github.com/Deepak-learner)) @@ -2531,16 +2503,16 @@ - **APPS:** Scheduler duplicating recurrent tasks after server restart ([#21866](https://github.com/RocketChat/Rocket.Chat/pull/21866)) - Reintroduces the old method for creating recurring tasks in the apps' scheduler bridge to ensure tasks won't be duplicated. - - By introducing the [`skipImmediate` property option](https://github.com/RocketChat/Rocket.Chat/pull/21353) at the [`scheduleRecurring`](https://github.com/RocketChat/Rocket.Chat/blob/f8171f464ed8a7487795651767695fb33a1c709e/app/apps/server/bridges/scheduler.js#L119) method, the `every` method from _agenda.js_, which ensured no duplicates were created, was removed in favor of a more manual procedure. The new procedure was not taking into account the management of duplicates and as a result multiple copies of the same task could be created and they would get executed at the same time. - + Reintroduces the old method for creating recurring tasks in the apps' scheduler bridge to ensure tasks won't be duplicated. + + By introducing the [`skipImmediate` property option](https://github.com/RocketChat/Rocket.Chat/pull/21353) at the [`scheduleRecurring`](https://github.com/RocketChat/Rocket.Chat/blob/f8171f464ed8a7487795651767695fb33a1c709e/app/apps/server/bridges/scheduler.js#L119) method, the `every` method from _agenda.js_, which ensured no duplicates were created, was removed in favor of a more manual procedure. The new procedure was not taking into account the management of duplicates and as a result multiple copies of the same task could be created and they would get executed at the same time. + In the case of server restarts, every time this event happened and the app had the `startupSetting` configured to use _recurring tasks_, they would get recreated the same number of times. In the case of a server that restarts frequently (_n_ times), there would be the same (_n_) number of tasks duplicated (and running) in the system. - **ENTERPRISE:** Omnichannel Monitors can't forward chats to departments that they are not supervising ([#22128](https://github.com/RocketChat/Rocket.Chat/pull/22128)) - Currently, Omnichannel Monitors just can't forward chats to a department that is part of a `Business Unit` they're not supervising. This issue is causing critical problems on customer operations since this behaviour is not by design. - The reason this issue is taking place is that, by design, Monitors just have access to departments related to the `Business Units` they're monitoring, but this restriction is designed only for Omnichannel management areas, which means in case the monitor is, also, an agent, they're supposed to be able to forward a chat to any available departments regardless the `Business Units` it's associated with. + Currently, Omnichannel Monitors just can't forward chats to a department that is part of a `Business Unit` they're not supervising. This issue is causing critical problems on customer operations since this behaviour is not by design. + The reason this issue is taking place is that, by design, Monitors just have access to departments related to the `Business Units` they're monitoring, but this restriction is designed only for Omnichannel management areas, which means in case the monitor is, also, an agent, they're supposed to be able to forward a chat to any available departments regardless the `Business Units` it's associated with. So, initially, the restriction was implemented on the `Department Model` and, now, we're implementing the logic properly and introducing a new parameter to department endpoints, so the client will define which type of departments it needs. - **ENTERPRISE:** Omnichannel Monitors can't forward chats to departments that they are not supervising ([#22142](https://github.com/RocketChat/Rocket.Chat/pull/22142)) @@ -2579,18 +2551,18 @@ - Correcting a the wrong Archived label in edit room ([#21717](https://github.com/RocketChat/Rocket.Chat/pull/21717) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - ![image](https://user-images.githubusercontent.com/45966964/116584997-3cd78a80-a918-11eb-81fa-8a7eb5318ae9.png) - + ![image](https://user-images.githubusercontent.com/45966964/116584997-3cd78a80-a918-11eb-81fa-8a7eb5318ae9.png) + A label exists for Archived, and it has not been used. So I replaced it with the existing one. the label 'Archived' does not exist. - Custom OAuth not being completely deleted ([#21637](https://github.com/RocketChat/Rocket.Chat/pull/21637) by [@siva2204](https://github.com/siva2204)) - Directory Table's Sort Function ([#21921](https://github.com/RocketChat/Rocket.Chat/pull/21921)) - ### TableRow Margin Issue: - ![image](https://user-images.githubusercontent.com/27704687/116907348-d6a07f80-ac17-11eb-9411-edfe0906bfe1.png) - - ### Table Sort Action Issue: + ### TableRow Margin Issue: + ![image](https://user-images.githubusercontent.com/27704687/116907348-d6a07f80-ac17-11eb-9411-edfe0906bfe1.png) + + ### Table Sort Action Issue: ![directory](https://user-images.githubusercontent.com/27704687/116907441-f20b8a80-ac17-11eb-8790-bfce19e89a67.gif) - Discussion names showing a random value ([#22172](https://github.com/RocketChat/Rocket.Chat/pull/22172)) @@ -2601,54 +2573,54 @@ - Emails being sent with HTML entities getting escaped multiple times ([#21994](https://github.com/RocketChat/Rocket.Chat/pull/21994) by [@bhavayAnand9](https://github.com/bhavayAnand9)) - fixes an issue where if password contains special HTML character like &, in the email it would end up something like `&amp;` - - - password was going through multiple escapeHTML function calls - `secure&123 => secure&123 => secure&amp;123 + fixes an issue where if password contains special HTML character like &, in the email it would end up something like `&amp;` + + + password was going through multiple escapeHTML function calls + `secure&123 => secure&123 => secure&amp;123 ` - Error when you look at the members list of a room in which you are not a member ([#21952](https://github.com/RocketChat/Rocket.Chat/pull/21952) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - Before, when you look at the members of a room in which you are not a member the app crashed, i corrected this problem. - Indeed, there was a check on each currentSubscription. to see if it was not undefined except on currentSubscription.blocker - + Before, when you look at the members of a room in which you are not a member the app crashed, i corrected this problem. + Indeed, there was a check on each currentSubscription. to see if it was not undefined except on currentSubscription.blocker + https://user-images.githubusercontent.com/45966964/117087470-d3101400-ad4f-11eb-8f44-0ebca830a4d8.mp4 - errors when viewing a room that you're not subscribed to ([#21984](https://github.com/RocketChat/Rocket.Chat/pull/21984)) - Files list will not show deleted files. ([#21732](https://github.com/RocketChat/Rocket.Chat/pull/21732) by [@Darshilp326](https://github.com/Darshilp326)) - When you delete files from the header option, deleted files will not be shown. - + When you delete files from the header option, deleted files will not be shown. + https://user-images.githubusercontent.com/55157259/115730786-38552400-a3a4-11eb-9684-7f510920db66.mp4 - Fixed the fact that when a team was deleted, not all channels were unlinked from the team ([#21942](https://github.com/RocketChat/Rocket.Chat/pull/21942) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - Fixed the fact that when a team was deleted, not all channels were unlinked from the team. Only the first room of the rooms list was unlinked. - - After the fix, there is nos more errors: - - + Fixed the fact that when a team was deleted, not all channels were unlinked from the team. Only the first room of the rooms list was unlinked. + + After the fix, there is nos more errors: + + https://user-images.githubusercontent.com/45966964/117055182-2a47c180-ad1b-11eb-806f-07fb3fa7ec12.mp4 - Fixing Jitsi call ended Issue. ([#21808](https://github.com/RocketChat/Rocket.Chat/pull/21808)) - The new rewrite in react of contextual call component broke the Jitsi "click to join" messages. The issue being after 10 seconds of initiating the call, the message "click to join" always returned "Call Ended" even if the call was still going on. - This was due to the fact that after closing the contextual bar, the react component gets unmounted and we are not able to keep track of ongoing call and increase jitsi room timeout. - - This PR solves this issue by using the setInterval methods on component will unmount. When the call component unmounts, we keep on checking the state of jitsi call and based on conditions increase the jitsi room timeout. After the call is ended all setInterval calls are closed. - + The new rewrite in react of contextual call component broke the Jitsi "click to join" messages. The issue being after 10 seconds of initiating the call, the message "click to join" always returned "Call Ended" even if the call was still going on. + This was due to the fact that after closing the contextual bar, the react component gets unmounted and we are not able to keep track of ongoing call and increase jitsi room timeout. + + This PR solves this issue by using the setInterval methods on component will unmount. When the call component unmounts, we keep on checking the state of jitsi call and based on conditions increase the jitsi room timeout. After the call is ended all setInterval calls are closed. + This PR also removes the implementation of HEARTBEAT events of JitsiBridge. This is because this is no longer needed and all logic is being taken care of by the unmount function. - Handle NPS errors instead of throwing them ([#21945](https://github.com/RocketChat/Rocket.Chat/pull/21945)) - Header Tag Visual Issues ([#21991](https://github.com/RocketChat/Rocket.Chat/pull/21991)) - ### Normal - ![image](https://user-images.githubusercontent.com/27704687/117504793-69635600-af59-11eb-8b79-9d8f631490ee.png) - - ### Hover + ### Normal + ![image](https://user-images.githubusercontent.com/27704687/117504793-69635600-af59-11eb-8b79-9d8f631490ee.png) + + ### Hover ![image](https://user-images.githubusercontent.com/27704687/117504934-97489a80-af59-11eb-87c3-0a62731e9ce3.png) - Horizontal scrollbar not showing on tables ([#21852](https://github.com/RocketChat/Rocket.Chat/pull/21852)) @@ -2657,17 +2629,17 @@ - iFrame size on embedded videos ([#21992](https://github.com/RocketChat/Rocket.Chat/pull/21992)) - ### Before - ![image](https://user-images.githubusercontent.com/27704687/117508802-8bf86d80-af5f-11eb-9eb8-29e55b73eac5.png) - - ### After + ### Before + ![image](https://user-images.githubusercontent.com/27704687/117508802-8bf86d80-af5f-11eb-9eb8-29e55b73eac5.png) + + ### After ![image](https://user-images.githubusercontent.com/27704687/117508870-a4688800-af5f-11eb-9176-7f24de5fc424.png) - Incorrect error message when opening channel in anonymous read ([#22066](https://github.com/RocketChat/Rocket.Chat/pull/22066) by [@lucassartor](https://github.com/lucassartor)) - Every time you open a public channel with threads in it when using anonymous read an `Incorrect User` error will be thrown. - This is an incorrect behaviour as everything that is public should be valid for an anonymous user. - + Every time you open a public channel with threads in it when using anonymous read an `Incorrect User` error will be thrown. + This is an incorrect behaviour as everything that is public should be valid for an anonymous user. + Some files are adapted to that and have already removed this kind of incorrect error, but there are some that need some fix, this PR aims to do that. - Incorrect Team's Info spacing ([#22021](https://github.com/RocketChat/Rocket.Chat/pull/22021)) @@ -2680,21 +2652,19 @@ - Make the FR translation consistent with the 'room' translation + typos ([#21913](https://github.com/RocketChat/Rocket.Chat/pull/21913) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - In the FR translation files, there were two terms that were used to refer to **'room'**: - - - 'salon' (149 times used) - - ![image](https://user-images.githubusercontent.com/45966964/116829860-ac62a980-aba6-11eb-8212-e6f15ed0af82.png) - - - - 'salle' (46 times used) - - ![image](https://user-images.githubusercontent.com/45966964/116829871-be444c80-aba6-11eb-9b42-e213fee6586a.png) - - The problem is that both were used in the same context and sometimes even in the same option list. - However, since 'salon' is a better translation and was also in the majority, I used the translation 'salon' wherever 'salle' was marked. - - For example: + In the FR translation files, there were two terms that were used to refer to **'room'**: + - 'salon' (149 times used) + + ![image](https://user-images.githubusercontent.com/45966964/116829860-ac62a980-aba6-11eb-8212-e6f15ed0af82.png) + + - 'salle' (46 times used) + + ![image](https://user-images.githubusercontent.com/45966964/116829871-be444c80-aba6-11eb-9b42-e213fee6586a.png) + + The problem is that both were used in the same context and sometimes even in the same option list. + However, since 'salon' is a better translation and was also in the majority, I used the translation 'salon' wherever 'salle' was marked. + + For example: ![image](https://user-images.githubusercontent.com/45966964/116830523-1da45b80-abab-11eb-81f8-5225d51cecc6.png) - Maximum 25 channels can be loaded in the teams' channels list ([#21708](https://github.com/RocketChat/Rocket.Chat/pull/21708) by [@Jeanstaquet](https://github.com/Jeanstaquet)) @@ -2709,8 +2679,8 @@ - No warning message is sent when user is removed from a team's main channel ([#21949](https://github.com/RocketChat/Rocket.Chat/pull/21949)) - - Send a warning message to a team's main channel when a user is removed from the team; - - Trigger events while removing a user from a team's main channel; + - Send a warning message to a team's main channel when a user is removed from the team; + - Trigger events while removing a user from a team's main channel; - Fix `usersCount` field in the team's main room when a user is removed from the team (`usersCount` is now decreased by 1). - Not possible accept video call if "Hide right sidebar with click" is enabled ([#22175](https://github.com/RocketChat/Rocket.Chat/pull/22175)) @@ -2731,14 +2701,14 @@ - Prevent the userInfo tab to return 'User not found' each time if a certain member of a DM group has been deleted ([#21970](https://github.com/RocketChat/Rocket.Chat/pull/21970) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - Prevent the userInfo tab to return 'User not found' if a member of a DM group has been deleted. - This happens if the user that has been deleted is the one originally displayed on the userInfo tab in a DM group with >2 users. - + Prevent the userInfo tab to return 'User not found' if a member of a DM group has been deleted. + This happens if the user that has been deleted is the one originally displayed on the userInfo tab in a DM group with >2 users. + https://user-images.githubusercontent.com/45966964/117221081-db785580-ae08-11eb-9b33-2314a99eb037.mp4 - Prune messages not cleaning up unread threads ([#21326](https://github.com/RocketChat/Rocket.Chat/pull/21326) by [@renancleyson-dev](https://github.com/renancleyson-dev)) - Fixes permanent unread messages when admin prune at least two different thread messages in the room that were unread by some user. + Fixes permanent unread messages when admin prune at least two different thread messages in the room that were unread by some user. ![screencapture-localhost-3000-channel-general-thread-2021-03-26-13_17_16](https://user-images.githubusercontent.com/43624243/112678973-62b9cd00-8e4a-11eb-9af9-56f17cc66baf.png) - Redirect on remove user from channel by user profile tab ([#21951](https://github.com/RocketChat/Rocket.Chat/pull/21951)) @@ -2749,8 +2719,8 @@ - Removed fields from User Info for which the user doesn't have permissions. ([#20923](https://github.com/RocketChat/Rocket.Chat/pull/20923) by [@Darshilp326](https://github.com/Darshilp326)) - Removed LastLogin, CreatedAt and Roles for users who don't have permission. - + Removed LastLogin, CreatedAt and Roles for users who don't have permission. + https://user-images.githubusercontent.com/55157259/109381351-f2c62e80-78ff-11eb-9289-e11072bf62f8.mp4 - Replace `query` param by `name`, `username` and `status` on the `teams.members` endpoint ([#21539](https://github.com/RocketChat/Rocket.Chat/pull/21539)) @@ -2763,39 +2733,39 @@ - Unable to edit a 'direct' room setting in the admin due to the room name ([#21636](https://github.com/RocketChat/Rocket.Chat/pull/21636) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - When you are in the admin and want to change a room 'd' setting, it doesn't work because it takes into account the name that is set automatically and therefore tries to save that name. Since the name is not valid and should not be registered, we cannot change the setting for the 'd' room. - I made sure that when you want to change a setting in a 'd' room, that you don't take the name into account - - - https://user-images.githubusercontent.com/45966964/115150919-cd85af00-a06a-11eb-9667-ef3dcfc5adb6.mp4 - - + When you are in the admin and want to change a room 'd' setting, it doesn't work because it takes into account the name that is set automatically and therefore tries to save that name. Since the name is not valid and should not be registered, we cannot change the setting for the 'd' room. + I made sure that when you want to change a setting in a 'd' room, that you don't take the name into account + + + https://user-images.githubusercontent.com/45966964/115150919-cd85af00-a06a-11eb-9667-ef3dcfc5adb6.mp4 + + Behind the scene, the name is not saved - Unable to edit a user who does not have an email via the admin or via the user's profile ([#21626](https://github.com/RocketChat/Rocket.Chat/pull/21626) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - If a user does not have an email address, they cannot change it via their profile or via the admin. I fixed this issue. I have created several profiles and there was one that didn't have an email, I don't know how I did it, I am working on it. I had not modified the db to delete his email, hence the fix - - in admin - - https://user-images.githubusercontent.com/45966964/115112617-9b9b1c80-9f86-11eb-8e3a-950c3c1a1746.mp4 - - - - in the user profile - + If a user does not have an email address, they cannot change it via their profile or via the admin. I fixed this issue. I have created several profiles and there was one that didn't have an email, I don't know how I did it, I am working on it. I had not modified the db to delete his email, hence the fix + + in admin + + https://user-images.githubusercontent.com/45966964/115112617-9b9b1c80-9f86-11eb-8e3a-950c3c1a1746.mp4 + + + + in the user profile + https://user-images.githubusercontent.com/45966964/115112620-a0f86700-9f86-11eb-97b1-56eaba42216b.mp4 - Unable to get channels, sort by most recent message ([#21701](https://github.com/RocketChat/Rocket.Chat/pull/21701) by [@sumukhah](https://github.com/sumukhah)) - Unable to update app manually ([#21215](https://github.com/RocketChat/Rocket.Chat/pull/21215)) - It allows for update of apps using a zip file. - - When installing apps using the zip file, either by url or the file form, if the app was already installed, an error would be thrown stating the condition and forbidding the installation. Now, when sending a zip file of an app that is already installed, the user is presented with the following modal: - - ![2021-04-30-113936_627x235_scrot](https://user-images.githubusercontent.com/733282/116711383-2cbbbb80-a9a9-11eb-8c77-22d6802cb9f5.png) - + It allows for update of apps using a zip file. + + When installing apps using the zip file, either by url or the file form, if the app was already installed, an error would be thrown stating the condition and forbidding the installation. Now, when sending a zip file of an app that is already installed, the user is presented with the following modal: + + ![2021-04-30-113936_627x235_scrot](https://user-images.githubusercontent.com/733282/116711383-2cbbbb80-a9a9-11eb-8c77-22d6802cb9f5.png) + If the app also requires permissions to be reviewed, the modal that handles permission reviews will be shown after this one is accepted. - Unpin message reactivity ([#22029](https://github.com/RocketChat/Rocket.Chat/pull/22029)) @@ -2806,20 +2776,20 @@ - User Impersonation through sendMessage API ([#20391](https://github.com/RocketChat/Rocket.Chat/pull/20391) by [@lucassartor](https://github.com/lucassartor)) - Create a new permission: `message-impersonate`. For new installs only bot role will have the permission and for updating installs the permission will also be given to user role, so it won't break running deployments. - - If a message is being sent with `avatar` or `alias` properties, it validates if the sender has the `message-impersonate` permission, if not, an error is throwed: - ```json - { - "success": false, - "error": "Not enough permission", - "stack": "Error: Not enough permission\n ..." - } + Create a new permission: `message-impersonate`. For new installs only bot role will have the permission and for updating installs the permission will also be given to user role, so it won't break running deployments. + + If a message is being sent with `avatar` or `alias` properties, it validates if the sender has the `message-impersonate` permission, if not, an error is throwed: + ```json + { + "success": false, + "error": "Not enough permission", + "stack": "Error: Not enough permission\n ..." + } ``` - Visibility of burger menu on certain width ([#20736](https://github.com/RocketChat/Rocket.Chat/pull/20736) by [@yash-rajpal](https://github.com/yash-rajpal)) - Burger was not visible on a certain width, specifically between 600 to 780. if width is more than 780px sidebar is shown, if less than 600 then burger icon was shown. But it wasn't shown between 600px to 780 px. + Burger was not visible on a certain width, specifically between 600 to 780. if width is more than 780px sidebar is shown, if less than 600 then burger icon was shown. But it wasn't shown between 600px to 780 px. It was because for showing burger icon we were only checking for `isMobile` which is lenght only less than 600. So i added one more check for condition if length is less than 780 px. - When closing chats a comment is always required ([#21947](https://github.com/RocketChat/Rocket.Chat/pull/21947)) @@ -2834,8 +2804,8 @@ - Wrong icon on "Move to team" option in the channel info actions ([#21944](https://github.com/RocketChat/Rocket.Chat/pull/21944)) - ![image](https://user-images.githubusercontent.com/40830821/117061659-d9bf6c80-acf8-11eb-8e29-be47e702dedd.png) - + ![image](https://user-images.githubusercontent.com/40830821/117061659-d9bf6c80-acf8-11eb-8e29-be47e702dedd.png) + Depends on https://github.com/RocketChat/Rocket.Chat.Fuselage/pull/444
@@ -2852,10 +2822,8 @@ - Add two more test cases to the slash-command test suite ([#21317](https://github.com/RocketChat/Rocket.Chat/pull/21317) by [@EduardoPicolo](https://github.com/EduardoPicolo)) - Added two more test cases to the slash-command test suite: - - - 'should return an error when the command does not exist''; - + Added two more test cases to the slash-command test suite: + - 'should return an error when the command does not exist''; - 'should return an error when no command is provided'; - Bump actions/stale from v3.0.8 to v3.0.18 ([#21877](https://github.com/RocketChat/Rocket.Chat/pull/21877) by [@dependabot[bot]](https://github.com/dependabot[bot])) @@ -2890,9 +2858,9 @@ - i18n: Add missing translation string in account preference ([#21448](https://github.com/RocketChat/Rocket.Chat/pull/21448) by [@sumukhah](https://github.com/sumukhah)) - "Test Desktop Notifications" was missing in translation, Added to the file. - Screenshot 2021-04-05 at 3 58 01 PM - + "Test Desktop Notifications" was missing in translation, Added to the file. + Screenshot 2021-04-05 at 3 58 01 PM + Screenshot 2021-04-05 at 3 58 32 PM - i18n: Correct a typo in German ([#21711](https://github.com/RocketChat/Rocket.Chat/pull/21711) by [@Jeanstaquet](https://github.com/Jeanstaquet)) @@ -2919,10 +2887,10 @@ - Regression: discussions display on sidebar ([#22157](https://github.com/RocketChat/Rocket.Chat/pull/22157)) - ### group by type active - ![image](https://user-images.githubusercontent.com/27704687/119741996-37a92500-be5d-11eb-8b36-4067a7a229f1.png) - - ### group by type inactive + ### group by type active + ![image](https://user-images.githubusercontent.com/27704687/119741996-37a92500-be5d-11eb-8b36-4067a7a229f1.png) + + ### group by type inactive ![image](https://user-images.githubusercontent.com/27704687/119742054-56a7b700-be5d-11eb-8810-e31d4216f573.png) - regression: fix departments with empty ancestors not being returned ([#22068](https://github.com/RocketChat/Rocket.Chat/pull/22068)) @@ -2933,8 +2901,8 @@ - regression: Fix Users list in the Administration ([#22034](https://github.com/RocketChat/Rocket.Chat/pull/22034) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - The app crashed if no custom fields for user profiles have been created by the admin. I fixed this issue. This bug was introduced by a recent commit. - + The app crashed if no custom fields for user profiles have been created by the admin. I fixed this issue. This bug was introduced by a recent commit. + https://user-images.githubusercontent.com/45966964/118210838-5b3a9b80-b46b-11eb-9fe5-5b813848190c.mp4 - Regression: Improve migration 225 ([#22099](https://github.com/RocketChat/Rocket.Chat/pull/22099)) @@ -2951,7 +2919,7 @@ - Regression: not allowed to edit roles due to a new verification ([#22159](https://github.com/RocketChat/Rocket.Chat/pull/22159)) - introduced by https://github.com/RocketChat/Rocket.Chat/pull/21905 + introduced by https://github.com/RocketChat/Rocket.Chat/pull/21905 ![Peek 2021-05-26 22-21](https://user-images.githubusercontent.com/27704687/119750970-b9567e00-be70-11eb-9d52-04c8595950df.gif) - regression: Select Team Modal margin ([#22030](https://github.com/RocketChat/Rocket.Chat/pull/22030)) @@ -2962,10 +2930,10 @@ - Regression: Visual issue on sort list item ([#22158](https://github.com/RocketChat/Rocket.Chat/pull/22158)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/119743703-d84d1400-be60-11eb-97cc-c8256b2c8b07.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/119743703-d84d1400-be60-11eb-97cc-c8256b2c8b07.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/119743638-b18edd80-be60-11eb-828d-22cc5e1b2f5b.png) - Release 3.14.2 ([#22135](https://github.com/RocketChat/Rocket.Chat/pull/22135)) @@ -3151,12 +3119,12 @@ - Paginated and Filtered selects on new/edit unit ([#22052](https://github.com/RocketChat/Rocket.Chat/pull/22052) by [@rafaelblink](https://github.com/rafaelblink)) - REQUIRES https://github.com/RocketChat/Rocket.Chat.Fuselage/pull/447 - - Adds infinite scrolling selects to the units edit/create with the ability to be filtered by text as well - - ![Screen Shot 2021-05-17 at 9 24 19 AM](https://user-images.githubusercontent.com/20868078/118487999-abc32a80-b6f1-11eb-8d58-d031111ea0fb.png) - + REQUIRES https://github.com/RocketChat/Rocket.Chat.Fuselage/pull/447 + + Adds infinite scrolling selects to the units edit/create with the ability to be filtered by text as well + + ![Screen Shot 2021-05-17 at 9 24 19 AM](https://user-images.githubusercontent.com/20868078/118487999-abc32a80-b6f1-11eb-8d58-d031111ea0fb.png) + This Affects the monitors and departments inputs ### 🚀 Improvements @@ -3232,24 +3200,18 @@ - New set of rules for client code ([#21318](https://github.com/RocketChat/Rocket.Chat/pull/21318)) - This _small_ PR does the following: - - - - Now **React** is the web client's first-class citizen, being **loaded before Blaze**. Thus, `BlazeLayout` calls render templates inside of a React component (`BlazeLayoutWrapper`); - - - Main client startup code, including polyfills, is written in **TypeScript**; - - - At the moment, routes are treated as regular startup code; it's expected that `FlowRouter` will be deprecated in favor of a new routing library; - - - **React** was updated to major version **17**, deprecating the usage of `React` as namespace (e.g. use `memo()` instead of `React.memo()`); - - - The `client/` and `ee/client/` directory are linted with a **custom ESLint configuration** that includes: - - **Prettier**; - - `react-hooks/*` rules for TypeScript files; - - `react/no-multi-comp`, enforcing the rule of **one single React component per module**; - - `react/display-name`, which enforces that **React components must have a name for debugging**; - - `import/named`, avoiding broken named imports. - + This _small_ PR does the following: + + - Now **React** is the web client's first-class citizen, being **loaded before Blaze**. Thus, `BlazeLayout` calls render templates inside of a React component (`BlazeLayoutWrapper`); + - Main client startup code, including polyfills, is written in **TypeScript**; + - At the moment, routes are treated as regular startup code; it's expected that `FlowRouter` will be deprecated in favor of a new routing library; + - **React** was updated to major version **17**, deprecating the usage of `React` as namespace (e.g. use `memo()` instead of `React.memo()`); + - The `client/` and `ee/client/` directory are linted with a **custom ESLint configuration** that includes: + - **Prettier**; + - `react-hooks/*` rules for TypeScript files; + - `react/no-multi-comp`, enforcing the rule of **one single React component per module**; + - `react/display-name`, which enforces that **React components must have a name for debugging**; + - `import/named`, avoiding broken named imports. - A bunch of components were refactored to match the new ESLint rules. - On Hold system messages ([#21360](https://github.com/RocketChat/Rocket.Chat/pull/21360) by [@rafaelblink](https://github.com/rafaelblink)) @@ -3258,15 +3220,12 @@ - Password history ([#21607](https://github.com/RocketChat/Rocket.Chat/pull/21607)) - - Store each user's previously used passwords in a `passwordHistory` field (in the `users` record); - - - Users' previously used passwords are stored in their `passwordHistory` even when the setting is disabled; - - - Add "Password History" setting -- when enabled, it blocks users from reusing their most recent passwords; - - - Convert `comparePassword` file to TypeScript. - - ![Password_Change](https://user-images.githubusercontent.com/36537004/115035168-ac726200-9ea2-11eb-93c6-fc8182ba5f3f.png) + - Store each user's previously used passwords in a `passwordHistory` field (in the `users` record); + - Users' previously used passwords are stored in their `passwordHistory` even when the setting is disabled; + - Add "Password History" setting -- when enabled, it blocks users from reusing their most recent passwords; + - Convert `comparePassword` file to TypeScript. + + ![Password_Change](https://user-images.githubusercontent.com/36537004/115035168-ac726200-9ea2-11eb-93c6-fc8182ba5f3f.png) ![Password_History](https://user-images.githubusercontent.com/36537004/115035175-ad0af880-9ea2-11eb-9f40-94c6327a9854.png) - REST endpoint `teams.update` ([#21134](https://github.com/RocketChat/Rocket.Chat/pull/21134) by [@g-thome](https://github.com/g-thome)) @@ -3284,18 +3243,14 @@ - Add error messages to the creation of channels or usernames containing reserved words ([#21016](https://github.com/RocketChat/Rocket.Chat/pull/21016)) - Display error messages when the user attempts to create or edit users' or channels' names with any of the following words (**case-insensitive**): - - - admin; - - - administrator; - - - system; - - - user. - ![create-channel](https://user-images.githubusercontent.com/36537004/110132223-b421ef80-7da9-11eb-82bc-f0d4e1df967f.png) - ![register-username](https://user-images.githubusercontent.com/36537004/110132234-b71ce000-7da9-11eb-904e-580233625951.png) - ![change-channel](https://user-images.githubusercontent.com/36537004/110143057-96f31e00-7db5-11eb-994a-39ae9e63392e.png) + Display error messages when the user attempts to create or edit users' or channels' names with any of the following words (**case-insensitive**): + - admin; + - administrator; + - system; + - user. + ![create-channel](https://user-images.githubusercontent.com/36537004/110132223-b421ef80-7da9-11eb-82bc-f0d4e1df967f.png) + ![register-username](https://user-images.githubusercontent.com/36537004/110132234-b71ce000-7da9-11eb-904e-580233625951.png) + ![change-channel](https://user-images.githubusercontent.com/36537004/110143057-96f31e00-7db5-11eb-994a-39ae9e63392e.png) ![change-username](https://user-images.githubusercontent.com/36537004/110143065-98244b00-7db5-11eb-9d13-afc5dc9866de.png) - add permission check when adding a channel to a team ([#21689](https://github.com/RocketChat/Rocket.Chat/pull/21689) by [@g-thome](https://github.com/g-thome)) @@ -3320,8 +3275,7 @@ - Resize custom emojis on upload instead of saving at max res ([#21593](https://github.com/RocketChat/Rocket.Chat/pull/21593)) - - Create new MediaService (ideally, should be in charge of all media-related operations) - + - Create new MediaService (ideally, should be in charge of all media-related operations) - Resize emojis to 128x128 ### 🐛 Bug fixes @@ -3341,25 +3295,25 @@ - Allows more than 25 discussions/files to be loaded in the contextualbar ([#21511](https://github.com/RocketChat/Rocket.Chat/pull/21511) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - In some places, you could not load more than 25 threads/discussions/files on the screen when searching the lists in the contextualbar. - Threads & list are numbered for a better view of the solution - - + In some places, you could not load more than 25 threads/discussions/files on the screen when searching the lists in the contextualbar. + Threads & list are numbered for a better view of the solution + + https://user-images.githubusercontent.com/45966964/114222225-93335800-996e-11eb-833f-568e83129aae.mp4 - Allows more than 25 threads to be loaded, fixes #21507 ([#21508](https://github.com/RocketChat/Rocket.Chat/pull/21508) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - Allows to display more than 25 users maximum in the users list ([#21518](https://github.com/RocketChat/Rocket.Chat/pull/21518) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - Now when you scroll to the bottom of the users list, it shows more users. Before the fix, the limit for the query for loadMore was calculated so that no additional users could be loaded. - - Before - - https://user-images.githubusercontent.com/45966964/114249739-baece500-999b-11eb-9bb0-3a5bcee18ad8.mp4 - - After - - + Now when you scroll to the bottom of the users list, it shows more users. Before the fix, the limit for the query for loadMore was calculated so that no additional users could be loaded. + + Before + + https://user-images.githubusercontent.com/45966964/114249739-baece500-999b-11eb-9bb0-3a5bcee18ad8.mp4 + + After + + https://user-images.githubusercontent.com/45966964/114249895-364e9680-999c-11eb-985c-47aedc763488.mp4 - App installation from marketplace not correctly displaying the permissions ([#21470](https://github.com/RocketChat/Rocket.Chat/pull/21470)) @@ -3426,19 +3380,19 @@ - Margins on contextual bar information ([#21457](https://github.com/RocketChat/Rocket.Chat/pull/21457)) - ### Room - **Before** - ![image](https://user-images.githubusercontent.com/27704687/115080812-ba8fa500-9ed9-11eb-9078-3625603bf92b.png) - - **After** - ![image](https://user-images.githubusercontent.com/27704687/115080966-e9a61680-9ed9-11eb-929f-6516c1563e99.png) - - ### Livechat + ### Room + **Before** + ![image](https://user-images.githubusercontent.com/27704687/115080812-ba8fa500-9ed9-11eb-9078-3625603bf92b.png) + + **After** + ![image](https://user-images.githubusercontent.com/27704687/115080966-e9a61680-9ed9-11eb-929f-6516c1563e99.png) + + ### Livechat ![image](https://user-images.githubusercontent.com/27704687/113640101-1859fc80-9651-11eb-88f8-09a899953988.png) - Message Block ordering ([#21464](https://github.com/RocketChat/Rocket.Chat/pull/21464)) - Reactions should come before reply button. + Reactions should come before reply button. ![image](https://user-images.githubusercontent.com/40830821/113748926-6f0e1780-96df-11eb-93a5-ddcfa891413e.png) - Message link null corrupts message rendering ([#21579](https://github.com/RocketChat/Rocket.Chat/pull/21579) by [@g-thome](https://github.com/g-thome)) @@ -3491,19 +3445,15 @@ - Typos/missing elements in the French translation ([#21525](https://github.com/RocketChat/Rocket.Chat/pull/21525) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - - I have corrected some typos in the translation - - - I added a translation for missing words - - - I took the opportunity to correct a mistranslated word - - - Test_Desktop_Notifications was missing in the EN and FR file + - I have corrected some typos in the translation + - I added a translation for missing words + - I took the opportunity to correct a mistranslated word + - Test_Desktop_Notifications was missing in the EN and FR file ![image](https://user-images.githubusercontent.com/45966964/114290186-e7792d80-9a7d-11eb-8164-3b5e72e93703.png) - Updating a message causing URLs to be parsed even within markdown code ([#21489](https://github.com/RocketChat/Rocket.Chat/pull/21489)) - - Fix `updateMessage` to avoid parsing URLs inside markdown - + - Fix `updateMessage` to avoid parsing URLs inside markdown - Honor `parseUrls` property when updating messages - Use async await in TeamChannels delete channel action ([#21534](https://github.com/RocketChat/Rocket.Chat/pull/21534)) @@ -3516,8 +3466,8 @@ - Wrong user in user info ([#21451](https://github.com/RocketChat/Rocket.Chat/pull/21451)) - Fixed some race conditions in admin. - + Fixed some race conditions in admin. + Self DMs used to be created with the userId duplicated. Sometimes rooms can have 2 equal uids, but it's a self DM. Fixed a getter so this isn't a problem anymore.
@@ -3526,30 +3476,22 @@ - Doc: Corrected links to documentation of rocket.chat README.md ([#20478](https://github.com/RocketChat/Rocket.Chat/pull/20478) by [@joshi008](https://github.com/joshi008)) - The link for documentation in the readme was previously https://rocket.chat/docs/ while that was not working and according to the website it was https://docs.rocket.chat/ - The link for deployment methods in readme was corrected from https://rocket.chat/docs/installation/paas-deployments/ to https://docs.rocket.chat/installation/paas-deployments + The link for documentation in the readme was previously https://rocket.chat/docs/ while that was not working and according to the website it was https://docs.rocket.chat/ + The link for deployment methods in readme was corrected from https://rocket.chat/docs/installation/paas-deployments/ to https://docs.rocket.chat/installation/paas-deployments Some more links to the documentations were giving 404 error which hence updated. - [Improve] Remove useless tabbar options from Omnichannel rooms ([#21561](https://github.com/RocketChat/Rocket.Chat/pull/21561) by [@rafaelblink](https://github.com/rafaelblink)) - A React-based replacement for BlazeLayout ([#21527](https://github.com/RocketChat/Rocket.Chat/pull/21527)) - - The Meteor package **`kadira:blaze-layout` was removed**; - - - A **global subscription** for the current application layout (**`appLayout`**) replaces `BlazeLayout` entirely; - - - The **`#react-root` element** is rendered on server-side instead of dynamically injected into the DOM tree; - - - The **"page loading" throbber** is now rendered on the React tree; - - - The **`renderRouteComponent` helper was removed**; - - - Some code run without any criteria on **`main` template** module was moved into **client startup modules**; - - - React portals used to embed Blaze templates have their own subscription (**`blazePortals`**); - - - Some **route components were refactored** to remove a URL path trap originally disabled by `renderRouteComponent`; - + - The Meteor package **`kadira:blaze-layout` was removed**; + - A **global subscription** for the current application layout (**`appLayout`**) replaces `BlazeLayout` entirely; + - The **`#react-root` element** is rendered on server-side instead of dynamically injected into the DOM tree; + - The **"page loading" throbber** is now rendered on the React tree; + - The **`renderRouteComponent` helper was removed**; + - Some code run without any criteria on **`main` template** module was moved into **client startup modules**; + - React portals used to embed Blaze templates have their own subscription (**`blazePortals`**); + - Some **route components were refactored** to remove a URL path trap originally disabled by `renderRouteComponent`; - A new component to embed the DOM nodes generated by **`RoomManager`** was created. - Add ')' after Date and Time in DB migration ([#21519](https://github.com/RocketChat/Rocket.Chat/pull/21519) by [@im-adithya](https://github.com/im-adithya)) @@ -3572,8 +3514,8 @@ - Chore: Meteor update to 2.1.1 ([#21494](https://github.com/RocketChat/Rocket.Chat/pull/21494)) - Basically Node update to version 12.22.1 - + Basically Node update to version 12.22.1 + Meteor change log https://github.com/meteor/meteor/blob/devel/History.md#v211-2021-04-06 - Chore: Remove control character from room model operation ([#21493](https://github.com/RocketChat/Rocket.Chat/pull/21493)) @@ -3582,8 +3524,7 @@ - Fix: Missing module `eventemitter3` for micro services ([#21611](https://github.com/RocketChat/Rocket.Chat/pull/21611)) - - Fix error when running micro services after version 3.12 - + - Fix error when running micro services after version 3.12 - Fix build of docker image version latest for micro services - Language update from LingoHub 🤖 on 2021-04-05Z ([#21446](https://github.com/RocketChat/Rocket.Chat/pull/21446)) @@ -3596,12 +3537,9 @@ - QoL improvements to add channel to team flow ([#21778](https://github.com/RocketChat/Rocket.Chat/pull/21778)) - - Fixed canAccessRoom validation - - - Added e2e tests - - - Removed channels that user cannot add to the team from autocomplete suggestions - + - Fixed canAccessRoom validation + - Added e2e tests + - Removed channels that user cannot add to the team from autocomplete suggestions - Improved error messages - Regression: Bold, italic and strike render (Original markdown) ([#21747](https://github.com/RocketChat/Rocket.Chat/pull/21747)) @@ -3624,10 +3562,10 @@ - Regression: Legacy Banner Position ([#21598](https://github.com/RocketChat/Rocket.Chat/pull/21598)) - ### Before: - ![image](https://user-images.githubusercontent.com/27704687/114961773-dc3c4e00-9e3f-11eb-9a32-e882db3fbfbc.png) - - ### After + ### Before: + ![image](https://user-images.githubusercontent.com/27704687/114961773-dc3c4e00-9e3f-11eb-9a32-e882db3fbfbc.png) + + ### After ![image](https://user-images.githubusercontent.com/27704687/114961673-a6976500-9e3f-11eb-9238-a12870d7db8f.png) - regression: Markdown broken on safari ([#21780](https://github.com/RocketChat/Rocket.Chat/pull/21780)) @@ -3837,62 +3775,56 @@ - **APPS:** New event interfaces for pre/post user leaving a room ([#20917](https://github.com/RocketChat/Rocket.Chat/pull/20917) by [@lucassartor](https://github.com/lucassartor)) - Added events and errors that trigger when a user leaves a room. + Added events and errors that trigger when a user leaves a room. That way it can communicate with the Apps-Engine by the `IPreRoomUserLeave` and `IPostRoomUserLeave` event interfaces. - **Enterprise:** Omnichannel On-Hold Queue ([#20945](https://github.com/RocketChat/Rocket.Chat/pull/20945)) - ### About this feature - This feature has been introduced to deal with Inactive chats. A chat is considered Inactive if an Omnichannel End User (aka Visitor) has not replied back to an agent in some time. These types of inactive chats become very important when an organisation has a limit set for `Max Simultaneous Chats per agent` which is defined by the following setting :point_down: , as more number of Inactive chats would directly affect an agent's productivity. - ![image](https://user-images.githubusercontent.com/34130764/111533003-4d7ad980-878c-11eb-8c1c-2796678a07db.png) - - Before this feature, we only had one option to deal with such Inactive/Abandoned chats - which was to auto close abandoned chats via this setting :point_down: - ![image](https://user-images.githubusercontent.com/34130764/111534353-e65e2480-878d-11eb-82a5-71368064ef45.png) - - however closing a chat isn't a best option for some cases. Let me take an example to explain a scenario - - > An agent is assisting a customer for installing a very huge software which is likely to take more than 20-30 minutes to download. In such scenarios closing a chat isn't the best approach since even after the lengthy download the customer might still need some assist from the agent. - > So basically this chat is going to block the agent's queue until the customer is able to finish his time-consuming download task in which he/she doesn't require any agent's assistance. Due to the `Max Simultaneous Chats per agent` limit, the agent is also not able to use this extra time to help other customer thus affecting his overall productivity. - - **So how does the On-Hold feature solve this problem?** - With the On-Hold feature, an agent is now able to place a chat on-hold. On-Hold chats **don’t count towards the maximum number of concurrent chats** an agent can have. So in our above example, the agent can simply now place the customer on-hold for 20-30 minutes until the customer downloads the software and within this time, the agent can serve other customers - hence increasing the productivity of an agent. - - ---------------------------------------- - ### Working of the new On-Hold feature - - #### How can you place a chat on Hold ? - - A chat can be placed on-hold via 2 means - - 1. Automatically place Abandoned chats On-hold - ![image](https://user-images.githubusercontent.com/34130764/111537074-06431780-8791-11eb-8d23-99f5d9f8ec45.png) - Via this :top: option you can define a timer which will get started when a customer sends a message. If we don't receive any message from the customer within this timer, the timer will get expired and the chat will be considered as Abandoned. - ![image](https://user-images.githubusercontent.com/34130764/111537346-53bf8480-8791-11eb-8dc7-260633b4e98f.png) - The via this :top: setting you can choose to automatically place this abandoned chat On Hold - - 2. Manually place a chat On Hold - As an admin, you can allow an agent to manually place a chat on-hold. To do so, you'll need to turn on this :point_down: setting - ![image](https://user-images.githubusercontent.com/34130764/111537545-97b28980-8791-11eb-86fd-db45b87e9cc1.png) - Now an agent will be able to see a new `On Hold` button within their `Visitor Info Panel` like this :point_down: , provided the agent has sent the last message - ![image](https://user-images.githubusercontent.com/34130764/111537853-f24be580-8791-11eb-9561-d77ba430c625.png) - - #### How can you resume a On Hold chat ? - An On Hold chat can be resumed via 2 means - - - 1. If the Customer sends a message - If the Customer / Omnichannel End User sends a message to the On Hold chat, the On Hold chat will get automatically resumed. - - 2. Manually by agent - An Agent can manually resume the On Hold chat via clicking the `Resume` button in the bottom of a chat room. - ![image](https://user-images.githubusercontent.com/34130764/111538666-f88e9180-8792-11eb-8d14-01453b8e3db0.png) - - #### What would happen if the agent already reached maximum chats, and a On-Hold chat gets resumed ? - Based on how the chat was resumed, there are multiple cases are each case is dealt differently - - - - If an agent manually tries to resume the On Hold chat, he/she will get an error saying `Maximum Simultaneous chat limit reached` - + ### About this feature + This feature has been introduced to deal with Inactive chats. A chat is considered Inactive if an Omnichannel End User (aka Visitor) has not replied back to an agent in some time. These types of inactive chats become very important when an organisation has a limit set for `Max Simultaneous Chats per agent` which is defined by the following setting :point_down: , as more number of Inactive chats would directly affect an agent's productivity. + ![image](https://user-images.githubusercontent.com/34130764/111533003-4d7ad980-878c-11eb-8c1c-2796678a07db.png) + + Before this feature, we only had one option to deal with such Inactive/Abandoned chats - which was to auto close abandoned chats via this setting :point_down: + ![image](https://user-images.githubusercontent.com/34130764/111534353-e65e2480-878d-11eb-82a5-71368064ef45.png) + + however closing a chat isn't a best option for some cases. Let me take an example to explain a scenario + + > An agent is assisting a customer for installing a very huge software which is likely to take more than 20-30 minutes to download. In such scenarios closing a chat isn't the best approach since even after the lengthy download the customer might still need some assist from the agent. + > So basically this chat is going to block the agent's queue until the customer is able to finish his time-consuming download task in which he/she doesn't require any agent's assistance. Due to the `Max Simultaneous Chats per agent` limit, the agent is also not able to use this extra time to help other customer thus affecting his overall productivity. + + **So how does the On-Hold feature solve this problem?** + With the On-Hold feature, an agent is now able to place a chat on-hold. On-Hold chats **don’t count towards the maximum number of concurrent chats** an agent can have. So in our above example, the agent can simply now place the customer on-hold for 20-30 minutes until the customer downloads the software and within this time, the agent can serve other customers - hence increasing the productivity of an agent. + + ---------------------------------------- + ### Working of the new On-Hold feature + + #### How can you place a chat on Hold ? + + A chat can be placed on-hold via 2 means + 1. Automatically place Abandoned chats On-hold + ![image](https://user-images.githubusercontent.com/34130764/111537074-06431780-8791-11eb-8d23-99f5d9f8ec45.png) + Via this :top: option you can define a timer which will get started when a customer sends a message. If we don't receive any message from the customer within this timer, the timer will get expired and the chat will be considered as Abandoned. + ![image](https://user-images.githubusercontent.com/34130764/111537346-53bf8480-8791-11eb-8dc7-260633b4e98f.png) + The via this :top: setting you can choose to automatically place this abandoned chat On Hold + 2. Manually place a chat On Hold + As an admin, you can allow an agent to manually place a chat on-hold. To do so, you'll need to turn on this :point_down: setting + ![image](https://user-images.githubusercontent.com/34130764/111537545-97b28980-8791-11eb-86fd-db45b87e9cc1.png) + Now an agent will be able to see a new `On Hold` button within their `Visitor Info Panel` like this :point_down: , provided the agent has sent the last message + ![image](https://user-images.githubusercontent.com/34130764/111537853-f24be580-8791-11eb-9561-d77ba430c625.png) + + #### How can you resume a On Hold chat ? + An On Hold chat can be resumed via 2 means + + 1. If the Customer sends a message + If the Customer / Omnichannel End User sends a message to the On Hold chat, the On Hold chat will get automatically resumed. + 2. Manually by agent + An Agent can manually resume the On Hold chat via clicking the `Resume` button in the bottom of a chat room. + ![image](https://user-images.githubusercontent.com/34130764/111538666-f88e9180-8792-11eb-8d14-01453b8e3db0.png) + + #### What would happen if the agent already reached maximum chats, and a On-Hold chat gets resumed ? + Based on how the chat was resumed, there are multiple cases are each case is dealt differently + + - If an agent manually tries to resume the On Hold chat, he/she will get an error saying `Maximum Simultaneous chat limit reached` - If a customer replies back on an On Hold chat and the last serving agent has reached maximum capacity, then this customer will be placed on the queue again from where based on the Routing Algorithm selected, the chat will get transferred to any available agent - Ability to hide 'Room topic changed' system messages ([#21062](https://github.com/RocketChat/Rocket.Chat/pull/21062) by [@Tirieru](https://github.com/Tirieru)) @@ -3903,39 +3835,33 @@ - Teams ([#20966](https://github.com/RocketChat/Rocket.Chat/pull/20966) by [@g-thome](https://github.com/g-thome)) - ## Teams - - - - You can easily group your users as Teams on Rocket.Chat. The feature takes the hassle out of managing multiple users one by one and allows you to handle them at the same time efficiently. - - - - - Teams can be public or private and each team can have its own channels, which also can be public or private. - - - It's possible to add existing channels to a Team or create new ones inside a Team. - - - It's possible to invite people outside a Team to join Team's channels. - - - It's possible to convert channels to Teams - - - It's possible to add all team members to a channel at once - - - Team members have roles - - - ![image](https://user-images.githubusercontent.com/70927132/113421955-4f56b680-93a2-11eb-80dc-9b70a3f09b3e.png) - - - - **Quickly onboard new users with Autojoin channels** - - Teams can have Auto-join channels – channels to which the team members are automatically added, so you don’t need to go through the manual process of adding users repetitively - - ![image](https://user-images.githubusercontent.com/70927132/113419284-81194e80-939d-11eb-9fff-aeb05cbc8089.png) - - **Instantly mention multiple members at once** (available in EE) - + ## Teams + + + + You can easily group your users as Teams on Rocket.Chat. The feature takes the hassle out of managing multiple users one by one and allows you to handle them at the same time efficiently. + + + - Teams can be public or private and each team can have its own channels, which also can be public or private. + - It's possible to add existing channels to a Team or create new ones inside a Team. + - It's possible to invite people outside a Team to join Team's channels. + - It's possible to convert channels to Teams + - It's possible to add all team members to a channel at once + - Team members have roles + + + ![image](https://user-images.githubusercontent.com/70927132/113421955-4f56b680-93a2-11eb-80dc-9b70a3f09b3e.png) + + + + **Quickly onboard new users with Autojoin channels** + + Teams can have Auto-join channels – channels to which the team members are automatically added, so you don’t need to go through the manual process of adding users repetitively + + ![image](https://user-images.githubusercontent.com/70927132/113419284-81194e80-939d-11eb-9fff-aeb05cbc8089.png) + + **Instantly mention multiple members at once** (available in EE) + With Teams, you don’t need to remember everyone’s name to communicate with a team quickly. Just mention a Team — @engineers, for instance — and all members will be instantly notified. ### 🚀 Improvements @@ -3945,22 +3871,22 @@ - Added modal-box for preview after recording audio. ([#20370](https://github.com/RocketChat/Rocket.Chat/pull/20370) by [@Darshilp326](https://github.com/Darshilp326)) - A modal box will be displayed so that users can change the filename and add description. - - **Before** - - https://user-images.githubusercontent.com/55157259/105687301-4e2a8880-5f1e-11eb-873d-dc8a880a2fc8.mp4 - - **After** - + A modal box will be displayed so that users can change the filename and add description. + + **Before** + + https://user-images.githubusercontent.com/55157259/105687301-4e2a8880-5f1e-11eb-873d-dc8a880a2fc8.mp4 + + **After** + https://user-images.githubusercontent.com/55157259/105687342-597db400-5f1e-11eb-8b61-8f9d9ebad0c4.mp4 - Adds toast after follow/unfollow messages and following icon for followed messages without threads. ([#20025](https://github.com/RocketChat/Rocket.Chat/pull/20025) by [@RonLek](https://github.com/RonLek)) - There was no alert on following/unfollowing a message previously. Also, it was impossible to make out a followed message with no threads from an unfollowed one. - - This PR would show an alert on following/unfollowing a message and also display a small bell icon (similar to the ones for starred and pinned messages) when a message with no thread is followed. - + There was no alert on following/unfollowing a message previously. Also, it was impossible to make out a followed message with no threads from an unfollowed one. + + This PR would show an alert on following/unfollowing a message and also display a small bell icon (similar to the ones for starred and pinned messages) when a message with no thread is followed. + https://user-images.githubusercontent.com/28918901/103813540-43e73e00-5086-11eb-8592-2877eb650f3e.mp4 - Back to threads list button on threads contextual bar ([#20882](https://github.com/RocketChat/Rocket.Chat/pull/20882)) @@ -3973,12 +3899,12 @@ - Improve Apps permission modal ([#21193](https://github.com/RocketChat/Rocket.Chat/pull/21193) by [@lucassartor](https://github.com/lucassartor)) - Improve the UI of the Apps permission modal when installing an App that requires permissions. - - **New UI:** - ![after](https://user-images.githubusercontent.com/49413772/111685622-e817fe80-8806-11eb-998d-b56623560e74.PNG) - - **Old UI:** + Improve the UI of the Apps permission modal when installing an App that requires permissions. + + **New UI:** + ![after](https://user-images.githubusercontent.com/49413772/111685622-e817fe80-8806-11eb-998d-b56623560e74.PNG) + + **Old UI:** ![before](https://user-images.githubusercontent.com/49413772/111685897-375e2f00-8807-11eb-814e-cb8060dc1830.PNG) - Make debug logs of Apps configurable via Log_Level setting in the Admin panel ([#21000](https://github.com/RocketChat/Rocket.Chat/pull/21000) by [@cuonghuunguyen](https://github.com/cuonghuunguyen)) @@ -3989,15 +3915,15 @@ - Sort Users List In Case Insensitive Manner ([#20790](https://github.com/RocketChat/Rocket.Chat/pull/20790) by [@aditya-mitra](https://github.com/aditya-mitra)) - The users listed in the admin panel were sorted in a case-sensitive manner , where the capitals came first and then the small letters (like - *A B C a b c*). This Change fixes this by sorting the names in a caseinsensitive manner (now - *A a B b C c*). - - ### Before - - ![before](https://user-images.githubusercontent.com/55396651/108189880-3fa74980-7137-11eb-99da-6498707b4bf8.png) - - - ### With This Change - + The users listed in the admin panel were sorted in a case-sensitive manner , where the capitals came first and then the small letters (like - *A B C a b c*). This Change fixes this by sorting the names in a caseinsensitive manner (now - *A a B b C c*). + + ### Before + + ![before](https://user-images.githubusercontent.com/55396651/108189880-3fa74980-7137-11eb-99da-6498707b4bf8.png) + + + ### With This Change + ![after](https://user-images.githubusercontent.com/55396651/108190177-9dd42c80-7137-11eb-8b4e-b7cef4ba512f.png) ### 🐛 Bug fixes @@ -4011,12 +3937,12 @@ - **APPS:** Warn message while installing app in air-gapped environment ([#20992](https://github.com/RocketChat/Rocket.Chat/pull/20992) by [@lucassartor](https://github.com/lucassartor)) - Change **error** message to a **warn** message when uploading a `.zip` file app into a air-gapped environment. - - The **error** message was giving the impression for the user that the app wasn't properly being installed , which it wasn't the case: - ![error](https://user-images.githubusercontent.com/49413772/109855273-d3e4d680-7c36-11eb-824b-ad455d24710c.PNG) - - A more detailed **warn** message can fix that impression for the user: + Change **error** message to a **warn** message when uploading a `.zip` file app into a air-gapped environment. + + The **error** message was giving the impression for the user that the app wasn't properly being installed , which it wasn't the case: + ![error](https://user-images.githubusercontent.com/49413772/109855273-d3e4d680-7c36-11eb-824b-ad455d24710c.PNG) + + A more detailed **warn** message can fix that impression for the user: ![warn](https://user-images.githubusercontent.com/49413772/109855383-f2e36880-7c36-11eb-8d61-c442980bd8fd.PNG) - Add missing `unreads` field to `users.info` REST endpoint ([#20905](https://github.com/RocketChat/Rocket.Chat/pull/20905)) @@ -4031,10 +3957,10 @@ - Correct direction for admin mapview text ([#20897](https://github.com/RocketChat/Rocket.Chat/pull/20897) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) - ![Screenshot from 2021-02-25 02-49-21](https://user-images.githubusercontent.com/38764067/109068512-f8602080-7715-11eb-8e22-d610f9d046d8.png) - ![Screenshot from 2021-02-25 02-49-46](https://user-images.githubusercontent.com/38764067/109068516-fa29e400-7715-11eb-9119-1c79abce278f.png) - ![Screenshot from 2021-02-25 02-49-57](https://user-images.githubusercontent.com/38764067/109068519-fbf3a780-7715-11eb-8b3d-0dc32f898725.png) - + ![Screenshot from 2021-02-25 02-49-21](https://user-images.githubusercontent.com/38764067/109068512-f8602080-7715-11eb-8e22-d610f9d046d8.png) + ![Screenshot from 2021-02-25 02-49-46](https://user-images.githubusercontent.com/38764067/109068516-fa29e400-7715-11eb-9119-1c79abce278f.png) + ![Screenshot from 2021-02-25 02-49-57](https://user-images.githubusercontent.com/38764067/109068519-fbf3a780-7715-11eb-8b3d-0dc32f898725.png) + The text says the share button will be on the left of the messagebox once enabled. However, it actually is on the right. - Correct ignored message CSS ([#20928](https://github.com/RocketChat/Rocket.Chat/pull/20928) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) @@ -4051,13 +3977,13 @@ - Custom emojis to override default ([#20359](https://github.com/RocketChat/Rocket.Chat/pull/20359) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) - Due to the sequence of the imports and how the emojiRenderer prioritizes lists, the custom emojis could not override the emojione emojis. Making two small changes fixed the issue. - - With the custom emoji for `:facepalm:` added, you can check out the result below: - ### Before - ![Screenshot from 2021-01-25 02-20-04](https://user-images.githubusercontent.com/38764067/105643088-dfb0e080-5eb3-11eb-8a00-582c53fbe9a4.png) - - ### After + Due to the sequence of the imports and how the emojiRenderer prioritizes lists, the custom emojis could not override the emojione emojis. Making two small changes fixed the issue. + + With the custom emoji for `:facepalm:` added, you can check out the result below: + ### Before + ![Screenshot from 2021-01-25 02-20-04](https://user-images.githubusercontent.com/38764067/105643088-dfb0e080-5eb3-11eb-8a00-582c53fbe9a4.png) + + ### After ![Screenshot from 2021-01-25 02-18-58](https://user-images.githubusercontent.com/38764067/105643076-cdcf3d80-5eb3-11eb-84b8-5dbc4f1135df.png) - Empty URL in user avatar doesn't show error and enables save ([#20440](https://github.com/RocketChat/Rocket.Chat/pull/20440) by [@im-adithya](https://github.com/im-adithya)) @@ -4070,12 +3996,12 @@ - Fix the search list showing the last channel ([#21160](https://github.com/RocketChat/Rocket.Chat/pull/21160) by [@shrinish123](https://github.com/shrinish123)) - The search list now also properly shows the last channel - Before : - - ![searchlist](https://user-images.githubusercontent.com/56491104/111471487-f3a7ee80-874e-11eb-9c6e-19bbf0731d60.png) - - After : + The search list now also properly shows the last channel + Before : + + ![searchlist](https://user-images.githubusercontent.com/56491104/111471487-f3a7ee80-874e-11eb-9c6e-19bbf0731d60.png) + + After : ![search_final](https://user-images.githubusercontent.com/56491104/111471521-fe628380-874e-11eb-8fa3-d1edb57587e1.png) - Follow thread action on threads list ([#20881](https://github.com/RocketChat/Rocket.Chat/pull/20881)) @@ -4100,13 +4026,13 @@ - Multi Select isn't working in Export Messages ([#21236](https://github.com/RocketChat/Rocket.Chat/pull/21236) by [@PriyaBihani](https://github.com/PriyaBihani)) - While exporting messages, we were not able to select multiple Users like this: - - https://user-images.githubusercontent.com/69837339/111953057-169a2000-8b0c-11eb-94a4-0e1657683f96.mp4 - - Now we can select multiple users: - - + While exporting messages, we were not able to select multiple Users like this: + + https://user-images.githubusercontent.com/69837339/111953057-169a2000-8b0c-11eb-94a4-0e1657683f96.mp4 + + Now we can select multiple users: + + https://user-images.githubusercontent.com/69837339/111953097-274a9600-8b0c-11eb-9177-bec388b042bd.mp4 - New Channel popover not closing ([#21080](https://github.com/RocketChat/Rocket.Chat/pull/21080)) @@ -4115,31 +4041,31 @@ - OEmbedURLWidget - Show Full Embedded Text Description ([#20569](https://github.com/RocketChat/Rocket.Chat/pull/20569) by [@aditya-mitra](https://github.com/aditya-mitra)) - Embeds were cutoff when either _urls had a long description_. - This was handled by removing `overflow:hidden;text-overflow:ellipsis;` from the inline styles in [`oembedUrlWidget.html`](https://github.com/RocketChat/Rocket.Chat/blob/develop/app/oembed/client/oembedUrlWidget.html#L28). - - ### Earlier - - ![earlier](https://user-images.githubusercontent.com/55396651/107110825-00dcde00-6871-11eb-866e-13cabc5b0d05.png) - - ### Now - + Embeds were cutoff when either _urls had a long description_. + This was handled by removing `overflow:hidden;text-overflow:ellipsis;` from the inline styles in [`oembedUrlWidget.html`](https://github.com/RocketChat/Rocket.Chat/blob/develop/app/oembed/client/oembedUrlWidget.html#L28). + + ### Earlier + + ![earlier](https://user-images.githubusercontent.com/55396651/107110825-00dcde00-6871-11eb-866e-13cabc5b0d05.png) + + ### Now + ![now](https://user-images.githubusercontent.com/55396651/107110794-ca06c800-6870-11eb-9b3b-168679936612.png) - Reactions list showing users in reactions option of message action. ([#20753](https://github.com/RocketChat/Rocket.Chat/pull/20753) by [@Darshilp326](https://github.com/Darshilp326)) - Reactions list shows emojis with respected users who have reacted with that emoji. - + Reactions list shows emojis with respected users who have reacted with that emoji. + https://user-images.githubusercontent.com/55157259/107857609-5870e000-6e55-11eb-8137-494a9f71b171.mp4 - Removing truncation from profile ([#20352](https://github.com/RocketChat/Rocket.Chat/pull/20352) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) - Truncating text in profile view was making some information completely inaccessible. Removed it from the user status and the custom fields where if the information is longer, the user would actually want to see all of it. - - ### Before - ![Screenshot from 2021-01-24 20-54-44](https://user-images.githubusercontent.com/38764067/105634935-7e264d00-5e86-11eb-8a6c-9f2a363e0f6c.png) - - ### After + Truncating text in profile view was making some information completely inaccessible. Removed it from the user status and the custom fields where if the information is longer, the user would actually want to see all of it. + + ### Before + ![Screenshot from 2021-01-24 20-54-44](https://user-images.githubusercontent.com/38764067/105634935-7e264d00-5e86-11eb-8a6c-9f2a363e0f6c.png) + + ### After ![Screenshot from 2021-01-24 20-54-06](https://user-images.githubusercontent.com/38764067/105634940-82eb0100-5e86-11eb-8b90-e97a43c5e938.png) - Replace wrong field description on Room Information panel ([#21395](https://github.com/RocketChat/Rocket.Chat/pull/21395) by [@rafaelblink](https://github.com/rafaelblink)) @@ -4150,8 +4076,8 @@ - Set establishing to false if OTR timeouts ([#21183](https://github.com/RocketChat/Rocket.Chat/pull/21183) by [@Darshilp326](https://github.com/Darshilp326)) - Set establishing false if OTR timeouts. - + Set establishing false if OTR timeouts. + https://user-images.githubusercontent.com/55157259/111617086-b30cab80-8808-11eb-8740-3b4ffacfc322.mp4 - Sidebar scroll missing full height ([#21071](https://github.com/RocketChat/Rocket.Chat/pull/21071)) @@ -4190,33 +4116,20 @@ - Chore: Add tests for Meteor methods ([#20901](https://github.com/RocketChat/Rocket.Chat/pull/20901)) - Add end-to-end tests for the following meteor methods - - - - [x] public-settings:get - - - [x] rooms:get - - - [x] subscriptions:get - - - [x] permissions:get - - - [x] loadMissedMessages - - - [x] loadHistory - - - [x] listCustomUserStatus - - - [x] getUserRoles - - - [x] getRoomRoles (called by the API, already covered) - - - [x] getMessages - - - [x] getUsersOfRoom - - - [x] loadNextMessages - + Add end-to-end tests for the following meteor methods + + - [x] public-settings:get + - [x] rooms:get + - [x] subscriptions:get + - [x] permissions:get + - [x] loadMissedMessages + - [x] loadHistory + - [x] listCustomUserStatus + - [x] getUserRoles + - [x] getRoomRoles (called by the API, already covered) + - [x] getMessages + - [x] getUsersOfRoom + - [x] loadNextMessages - [x] getThreadMessages - Chore: Meteor update 2.1 ([#21061](https://github.com/RocketChat/Rocket.Chat/pull/21061)) @@ -4229,10 +4142,8 @@ - Improve: Increase testing coverage ([#21015](https://github.com/RocketChat/Rocket.Chat/pull/21015)) - Add test for - - - settings/raw - + Add test for + - settings/raw - minimongo/comparisons - Improve: NPS survey fetch ([#21263](https://github.com/RocketChat/Rocket.Chat/pull/21263)) @@ -4251,19 +4162,17 @@ - Regression: Add scope to permission checks in Team's endpoints ([#21369](https://github.com/RocketChat/Rocket.Chat/pull/21369)) - - Include scope (team's main room ID) in the permission checks; + - Include scope (team's main room ID) in the permission checks; - Remove the `teamName` parameter from the `members`, `addMembers`, `updateMember` and `removeMembers` methods (since `teamId` will always be defined). - Regression: Add support to filter on `teams.listRooms` endpoint ([#21327](https://github.com/RocketChat/Rocket.Chat/pull/21327)) - - Add support for queries (within the `query` parameter); - + - Add support for queries (within the `query` parameter); - Add support to pagination (`offset` and `count`) when an user doesn't have the permission to get all rooms. - Regression: Add teams support to directory ([#21351](https://github.com/RocketChat/Rocket.Chat/pull/21351)) - - Change `directory.js` to reduce function complexity - + - Change `directory.js` to reduce function complexity - Add `teams` type of item. Directory will return all public teams & private teams the user is part of. - Regression: add view room action on Teams Channels ([#21295](https://github.com/RocketChat/Rocket.Chat/pull/21295)) @@ -4316,19 +4225,18 @@ - Regression: Quick action button missing for Omnichannel On-Hold queue ([#21285](https://github.com/RocketChat/Rocket.Chat/pull/21285)) - - Move the Manual On Hold button to the new Omnichannel Header - ![image](https://user-images.githubusercontent.com/34130764/112291749-6ae10380-8cb6-11eb-94cd-e05efc14b1bf.png) - ![image](https://user-images.githubusercontent.com/34130764/112304146-27d95d00-8cc3-11eb-85db-dde04a110dd1.png) - - + - Move the Manual On Hold button to the new Omnichannel Header + ![image](https://user-images.githubusercontent.com/34130764/112291749-6ae10380-8cb6-11eb-94cd-e05efc14b1bf.png) + ![image](https://user-images.githubusercontent.com/34130764/112304146-27d95d00-8cc3-11eb-85db-dde04a110dd1.png) + - Minor fixes - regression: Remove Breadcrumbs and update Tag component ([#21399](https://github.com/RocketChat/Rocket.Chat/pull/21399)) - Regression: Remove channel action on add channel's modal don't work ([#21356](https://github.com/RocketChat/Rocket.Chat/pull/21356)) - ![removechannel-on-add-existing-modal](https://user-images.githubusercontent.com/27704687/112911017-eda8fa80-90ca-11eb-9c24-47a70be0c314.gif) - + ![removechannel-on-add-existing-modal](https://user-images.githubusercontent.com/27704687/112911017-eda8fa80-90ca-11eb-9c24-47a70be0c314.gif) + ![image](https://user-images.githubusercontent.com/27704687/112911052-02858e00-90cb-11eb-85a2-0ef1f5f9ffd9.png) - Regression: Remove primary color from button in TeamChannels component ([#21293](https://github.com/RocketChat/Rocket.Chat/pull/21293)) @@ -4357,10 +4265,10 @@ - Regression: Unify Contact information displayed on the Room header and Room Info ([#21312](https://github.com/RocketChat/Rocket.Chat/pull/21312) by [@rafaelblink](https://github.com/rafaelblink)) - ![image](https://user-images.githubusercontent.com/34130764/112586659-35592900-8e22-11eb-94be-32bdff7ca883.png) - - ![image](https://user-images.githubusercontent.com/2493803/112913130-788bf400-90cf-11eb-84c6-782b203e100a.png) - + ![image](https://user-images.githubusercontent.com/34130764/112586659-35592900-8e22-11eb-94be-32bdff7ca883.png) + + ![image](https://user-images.githubusercontent.com/2493803/112913130-788bf400-90cf-11eb-84c6-782b203e100a.png) + ![image](https://user-images.githubusercontent.com/2493803/112913146-817cc580-90cf-11eb-87ad-ef79766be2b3.png) - Regression: Unify team actions to add a room to a team ([#21386](https://github.com/RocketChat/Rocket.Chat/pull/21386)) @@ -4369,10 +4277,8 @@ - Regression: Update .invite endpoints to support multiple users at once ([#21328](https://github.com/RocketChat/Rocket.Chat/pull/21328)) - - channels.invite now supports passing an array as a param (either with usernames or userIds) via `usernames` or `userIds` properties. - - - You can still use the endpoint to invite only one user via the old params `userId`, `username` or `user`. - + - channels.invite now supports passing an array as a param (either with usernames or userIds) via `usernames` or `userIds` properties. + - You can still use the endpoint to invite only one user via the old params `userId`, `username` or `user`. - Same changes apply to groups.invite - Regression: user actions in admin ([#21307](https://github.com/RocketChat/Rocket.Chat/pull/21307)) @@ -4507,7 +4413,7 @@ - Close Call contextual bar after starting jitsi call. ([#21004](https://github.com/RocketChat/Rocket.Chat/pull/21004) by [@yash-rajpal](https://github.com/yash-rajpal)) - After jitsi call is started, if the call is started in a new window then we should close contextual tab bar. + After jitsi call is started, if the call is started in a new window then we should close contextual tab bar. So, when 'YES' is pressed on modal, we call handleClose function if openNewWindow is true, as call doesn't starts on tab bar, it starts on new window. ### 🐛 Bug fixes @@ -4517,7 +4423,7 @@ - Stopping Jitsi reload ([#20973](https://github.com/RocketChat/Rocket.Chat/pull/20973) by [@yash-rajpal](https://github.com/yash-rajpal)) - The Function where Jitsi call is started gets called many times due to `room.usernames` dep of useMemo, this dep triggers reloading of this function many times. + The Function where Jitsi call is started gets called many times due to `room.usernames` dep of useMemo, this dep triggers reloading of this function many times. So removing this dep from useMemo dependencies ### 👩‍💻👨‍💻 Contributors 😍 @@ -4545,10 +4451,10 @@ - Cloud Workspace bridge ([#20838](https://github.com/RocketChat/Rocket.Chat/pull/20838)) - Adds the new CloudWorkspace functionality. - - It allows apps to request the access token for the workspace it's installed on, so it can perform actions with other Rocket.Chat services, such as the Omni Gateway. - + Adds the new CloudWorkspace functionality. + + It allows apps to request the access token for the workspace it's installed on, so it can perform actions with other Rocket.Chat services, such as the Omni Gateway. + https://github.com/RocketChat/Rocket.Chat.Apps-engine/pull/382 - Header with Breadcrumbs ([#20609](https://github.com/RocketChat/Rocket.Chat/pull/20609)) @@ -4566,10 +4472,10 @@ - Add symbol to indicate apps' required settings in the UI ([#20447](https://github.com/RocketChat/Rocket.Chat/pull/20447)) - - Apps are able to define **required** settings. These settings should not be left blank by the user and an error will be thrown and shown in the interface if an user attempts to save changes in the app details page leaving any required fields blank; - ![prt_screen_required_app_settings_warning](https://user-images.githubusercontent.com/36537004/106032964-e73cd900-60af-11eb-8eab-c11fd651b593.png) - - - A sign (*) is added to the label of app settings' fields that are required so as to highlight the fields which must not be left blank. + - Apps are able to define **required** settings. These settings should not be left blank by the user and an error will be thrown and shown in the interface if an user attempts to save changes in the app details page leaving any required fields blank; + ![prt_screen_required_app_settings_warning](https://user-images.githubusercontent.com/36537004/106032964-e73cd900-60af-11eb-8eab-c11fd651b593.png) + + - A sign (*) is added to the label of app settings' fields that are required so as to highlight the fields which must not be left blank. ![prt_screen_required_app_settings](https://user-images.githubusercontent.com/36537004/106014879-ae473900-609c-11eb-9b9e-95de7bbf20a5.png) - Add visual validation on users admin forms ([#20308](https://github.com/RocketChat/Rocket.Chat/pull/20308)) @@ -4590,20 +4496,20 @@ - Adds tooltip for sidebar header icons ([#19934](https://github.com/RocketChat/Rocket.Chat/pull/19934) by [@RonLek](https://github.com/RonLek)) - Previously the header icons in the sidebar didn't show a tooltip when hovered over. This PR fixes that. - + Previously the header icons in the sidebar didn't show a tooltip when hovered over. This PR fixes that. + ![Screenshot from 2020-12-22 15-17-41](https://user-images.githubusercontent.com/28918901/102874804-f2756700-4468-11eb-8324-b7f3194e62fe.png) - Better Presentation of Blockquotes ([#20750](https://github.com/RocketChat/Rocket.Chat/pull/20750) by [@aditya-mitra](https://github.com/aditya-mitra)) - Changed the values of `margin-top` and `margin-bottom` for *first* and *last* childs in blockquotes to increase readability. - - ### Before - - ![before](https://user-images.githubusercontent.com/55396651/107858662-3e3a0080-6e5b-11eb-8274-9bd956807235.png) - - ### Now - + Changed the values of `margin-top` and `margin-bottom` for *first* and *last* childs in blockquotes to increase readability. + + ### Before + + ![before](https://user-images.githubusercontent.com/55396651/107858662-3e3a0080-6e5b-11eb-8274-9bd956807235.png) + + ### Now + ![now](https://user-images.githubusercontent.com/55396651/107858471-480f3400-6e5a-11eb-9ccb-3f1be2fed0a4.png) - Change header based on room type ([#20612](https://github.com/RocketChat/Rocket.Chat/pull/20612)) @@ -4624,18 +4530,13 @@ - Replace react-window for react-virtuoso package ([#20392](https://github.com/RocketChat/Rocket.Chat/pull/20392)) - Remove: - - - react-window - - - react-window-infinite-loader - - - simplebar-react - - Include: - - - react-virtuoso - + Remove: + - react-window + - react-window-infinite-loader + - simplebar-react + + Include: + - react-virtuoso - rc-scrollbars - Rewrite Call as React component ([#19778](https://github.com/RocketChat/Rocket.Chat/pull/19778)) @@ -4651,13 +4552,13 @@ - Add debouncing to add users search field. ([#20297](https://github.com/RocketChat/Rocket.Chat/pull/20297) by [@Darshilp326](https://github.com/Darshilp326)) - BEFORE - - https://user-images.githubusercontent.com/55157259/105350722-98a3c080-5c11-11eb-82f3-d9a62a4fa50b.mp4 - - - AFTER - + BEFORE + + https://user-images.githubusercontent.com/55157259/105350722-98a3c080-5c11-11eb-82f3-d9a62a4fa50b.mp4 + + + AFTER + https://user-images.githubusercontent.com/55157259/105350757-a2c5bf00-5c11-11eb-91db-25c0b9e01a28.mp4 - Add tooltips to Thread header buttons ([#20456](https://github.com/RocketChat/Rocket.Chat/pull/20456) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) @@ -4670,8 +4571,8 @@ - Added check for view admin permission page ([#20403](https://github.com/RocketChat/Rocket.Chat/pull/20403) by [@yash-rajpal](https://github.com/yash-rajpal)) - Admin Permission page was visible to all, if you add admin/permissions after the base url. This should not be visible to all user, only people with certain permissions should be able to see this page. - I am also able to see permissions page for open workspace of Rocket chat. + Admin Permission page was visible to all, if you add admin/permissions after the base url. This should not be visible to all user, only people with certain permissions should be able to see this page. + I am also able to see permissions page for open workspace of Rocket chat. ![image](https://user-images.githubusercontent.com/58601732/105829728-bfd00880-5fea-11eb-9121-6c53a752f140.png) - Adding the accidentally deleted tag template, used by other templates ([#20772](https://github.com/RocketChat/Rocket.Chat/pull/20772) by [@yash-rajpal](https://github.com/yash-rajpal)) @@ -4680,8 +4581,8 @@ - Admin cannot clear user details like bio or nickname ([#20785](https://github.com/RocketChat/Rocket.Chat/pull/20785) by [@yash-rajpal](https://github.com/yash-rajpal)) - When the API users.update is called to update user data, it passes data to saveUser function. Here before saving data like bio or nickname we are checking if they are available or not. If data is available then we are saving it, but we are not doing anything when data isn't available. - + When the API users.update is called to update user data, it passes data to saveUser function. Here before saving data like bio or nickname we are checking if they are available or not. If data is available then we are saving it, but we are not doing anything when data isn't available. + So unsetting data if data isn't available to save. Will also fix bio and other fields. :) - Admin Panel pages not visible in Safari ([#20912](https://github.com/RocketChat/Rocket.Chat/pull/20912)) @@ -4698,24 +4599,24 @@ - Blank Personal Access Token Bug ([#20193](https://github.com/RocketChat/Rocket.Chat/pull/20193) by [@RonLek](https://github.com/RonLek)) - Adds error when personal access token is blank thereby disallowing the creation of one. - + Adds error when personal access token is blank thereby disallowing the creation of one. + https://user-images.githubusercontent.com/28918901/104483631-5adde100-55ee-11eb-9938-64146bce127e.mp4 - CAS login failing due to TOTP requirement ([#20840](https://github.com/RocketChat/Rocket.Chat/pull/20840)) - Changed password input field for password access in edit room info. ([#20356](https://github.com/RocketChat/Rocket.Chat/pull/20356) by [@Darshilp326](https://github.com/Darshilp326)) - Password field would be secured with asterisks in edit room info - - https://user-images.githubusercontent.com/55157259/105641758-cad04f00-5eab-11eb-90de-0c91263edd55.mp4 - + Password field would be secured with asterisks in edit room info + + https://user-images.githubusercontent.com/55157259/105641758-cad04f00-5eab-11eb-90de-0c91263edd55.mp4 + . - Channel mentions showing user subscribed channels twice ([#20484](https://github.com/RocketChat/Rocket.Chat/pull/20484) by [@Darshilp326](https://github.com/Darshilp326)) - Channel mention shows user subscribed channels twice. - + Channel mention shows user subscribed channels twice. + https://user-images.githubusercontent.com/55157259/106183033-b353d780-61c5-11eb-8aab-1dbb62b02ff8.mp4 - CORS config not accepting multiple origins ([#20696](https://github.com/RocketChat/Rocket.Chat/pull/20696) by [@g-thome](https://github.com/g-thome)) @@ -4726,26 +4627,26 @@ - Default Attachments - Remove Extra Margin in Field Attachments ([#20618](https://github.com/RocketChat/Rocket.Chat/pull/20618) by [@aditya-mitra](https://github.com/aditya-mitra)) - A large amount of unnecessary margin which existed in the **Field Attachments inside the `DefaultAttachments`** has been fixed. - - ### Earlier - - ![earlier](https://user-images.githubusercontent.com/55396651/107056792-ba4b9d00-67f8-11eb-9153-05281416cddb.png) - - ### Now - + A large amount of unnecessary margin which existed in the **Field Attachments inside the `DefaultAttachments`** has been fixed. + + ### Earlier + + ![earlier](https://user-images.githubusercontent.com/55396651/107056792-ba4b9d00-67f8-11eb-9153-05281416cddb.png) + + ### Now + ![now](https://user-images.githubusercontent.com/55396651/107057196-3219c780-67f9-11eb-84db-e4a0addfc168.png) - Default Attachments - Show Full Attachment.Text with Markdown ([#20606](https://github.com/RocketChat/Rocket.Chat/pull/20606) by [@aditya-mitra](https://github.com/aditya-mitra)) - Removed truncating of text in `Attachment.Text`. - Added `Attachment.Text` to be parsed to markdown by default. - - ### Earlier - ![earlier](https://user-images.githubusercontent.com/55396651/106910781-92d8cf80-6727-11eb-82ec-818df7544ff0.png) - - ### Now - + Removed truncating of text in `Attachment.Text`. + Added `Attachment.Text` to be parsed to markdown by default. + + ### Earlier + ![earlier](https://user-images.githubusercontent.com/55396651/106910781-92d8cf80-6727-11eb-82ec-818df7544ff0.png) + + ### Now + ![now](https://user-images.githubusercontent.com/55396651/106910840-a126eb80-6727-11eb-8bd6-d86383dd9181.png) - Don't ask again not rendering ([#20745](https://github.com/RocketChat/Rocket.Chat/pull/20745)) @@ -4766,21 +4667,21 @@ - Feedback on bulk invite ([#20339](https://github.com/RocketChat/Rocket.Chat/pull/20339) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) - Resolved structure where no response was being received. Changed from callback to async/await. - Added error in case of empty submission, or if no valid emails were found. - + Resolved structure where no response was being received. Changed from callback to async/await. + Added error in case of empty submission, or if no valid emails were found. + https://user-images.githubusercontent.com/38764067/105613964-dfe5a900-5deb-11eb-80f2-21fc8dee57c0.mp4 - Filters are not being applied correctly in Omnichannel Current Chats list ([#20320](https://github.com/RocketChat/Rocket.Chat/pull/20320) by [@rafaelblink](https://github.com/rafaelblink)) - ### Before - ![image](https://user-images.githubusercontent.com/2493803/105537672-082cb500-5cd1-11eb-8f1b-1726ba60420a.png) - - ### After - ![image](https://user-images.githubusercontent.com/2493803/105537773-2d212800-5cd1-11eb-8746-048deb9502d9.png) - - ![image](https://user-images.githubusercontent.com/2493803/106494728-88090b00-6499-11eb-922e-5386107e2389.png) - + ### Before + ![image](https://user-images.githubusercontent.com/2493803/105537672-082cb500-5cd1-11eb-8f1b-1726ba60420a.png) + + ### After + ![image](https://user-images.githubusercontent.com/2493803/105537773-2d212800-5cd1-11eb-8746-048deb9502d9.png) + + ![image](https://user-images.githubusercontent.com/2493803/106494728-88090b00-6499-11eb-922e-5386107e2389.png) + ![image](https://user-images.githubusercontent.com/2493803/106494751-90f9dc80-6499-11eb-901b-5e4dbdc678ba.png) - Fix Empty highlighted words field ([#20329](https://github.com/RocketChat/Rocket.Chat/pull/20329) by [@yash-rajpal](https://github.com/yash-rajpal)) @@ -4809,11 +4710,11 @@ - List of Omnichannel triggers is not listing data ([#20624](https://github.com/RocketChat/Rocket.Chat/pull/20624) by [@rafaelblink](https://github.com/rafaelblink)) - ### Before - ![image](https://user-images.githubusercontent.com/2493803/107095379-7308e080-67e7-11eb-8251-7e7ff891087a.png) - - - ### After + ### Before + ![image](https://user-images.githubusercontent.com/2493803/107095379-7308e080-67e7-11eb-8251-7e7ff891087a.png) + + + ### After ![image](https://user-images.githubusercontent.com/2493803/107095261-3b019d80-67e7-11eb-8425-8612b03ac50a.png) - Livechat bridge permission checkers ([#20653](https://github.com/RocketChat/Rocket.Chat/pull/20653) by [@lolimay](https://github.com/lolimay)) @@ -4836,8 +4737,7 @@ - Missing setting to control when to send the ReplyTo field in email notifications ([#20744](https://github.com/RocketChat/Rocket.Chat/pull/20744)) - - Add a new setting ("Add Reply-To header") in the Email settings' page to control when the Reply-To header is used in e-mail notifications; - + - Add a new setting ("Add Reply-To header") in the Email settings' page to control when the Reply-To header is used in e-mail notifications; - The new setting is turned off (`false` value) by default. - New Integration page was not being displayed ([#20670](https://github.com/RocketChat/Rocket.Chat/pull/20670) by [@yash-rajpal](https://github.com/yash-rajpal)) @@ -4870,15 +4770,15 @@ - Remove duplicate getCommonRoomEvents() event binding for starredMessages ([#20185](https://github.com/RocketChat/Rocket.Chat/pull/20185) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) - The getCommonRoomEvents() returned functions were bound to the starredMessages template twice. This was causing some bugs, as detailed in the Issue mentioned below. + The getCommonRoomEvents() returned functions were bound to the starredMessages template twice. This was causing some bugs, as detailed in the Issue mentioned below. I removed the top events call that only bound the getCommonRoomEvents(). Therefore, only one call for the same is left, which is at the end of the file. Having the events bound just once removes the bugs mentioned. - Remove warning problems from console ([#20800](https://github.com/RocketChat/Rocket.Chat/pull/20800)) - Removed tooltip in kebab menu options. ([#20498](https://github.com/RocketChat/Rocket.Chat/pull/20498) by [@Darshilp326](https://github.com/Darshilp326)) - Removed tooltip as it was not needed. - + Removed tooltip as it was not needed. + https://user-images.githubusercontent.com/55157259/106246146-a53ca000-6233-11eb-9874-cbd1b4331bc0.mp4 - Retry icon comes out of the div ([#20390](https://github.com/RocketChat/Rocket.Chat/pull/20390) by [@im-adithya](https://github.com/im-adithya)) @@ -4893,8 +4793,8 @@ - Room's last message's update date format on IE ([#20680](https://github.com/RocketChat/Rocket.Chat/pull/20680)) - The proposed change fixes a bug when updates the cached records on Internet Explorer and it breaks the sidebar as shown on the screenshot below: - + The proposed change fixes a bug when updates the cached records on Internet Explorer and it breaks the sidebar as shown on the screenshot below: + ![image](https://user-images.githubusercontent.com/27704687/107578007-f2285b00-6bd1-11eb-9250-1e76ae67f9c9.png) - Save user password and email from My Account ([#20737](https://github.com/RocketChat/Rocket.Chat/pull/20737)) @@ -4903,8 +4803,8 @@ - Selected hide system messages would now be viewed in vertical bar. ([#20358](https://github.com/RocketChat/Rocket.Chat/pull/20358) by [@Darshilp326](https://github.com/Darshilp326)) - All selected hide system messages are now in vertical Bar. - + All selected hide system messages are now in vertical Bar. + https://user-images.githubusercontent.com/55157259/105642624-d5411780-5eb0-11eb-8848-93e4b02629cb.mp4 - Selected messages don't get unselected ([#20408](https://github.com/RocketChat/Rocket.Chat/pull/20408) by [@im-adithya](https://github.com/im-adithya)) @@ -4919,22 +4819,14 @@ - Several Slack Importer issues ([#20216](https://github.com/RocketChat/Rocket.Chat/pull/20216)) - - Fix: Slack Importer crashes when importing a large users.json file - - - Fix: Slack importer crashes when messages have invalid mentions - - - Skip listing all users on the preparation screen when the user count is too large. - - - Split avatar download into a separate process. - - - Update room's last message when the import is complete. - - - Prevent invalid or duplicated channel names - - - Improve message error handling. - - - Reduce max allowed BSON size to avoid possible issues in some servers. - + - Fix: Slack Importer crashes when importing a large users.json file + - Fix: Slack importer crashes when messages have invalid mentions + - Skip listing all users on the preparation screen when the user count is too large. + - Split avatar download into a separate process. + - Update room's last message when the import is complete. + - Prevent invalid or duplicated channel names + - Improve message error handling. + - Reduce max allowed BSON size to avoid possible issues in some servers. - Improve handling of very large channel files. - star icon was visible after unstarring a message ([#19645](https://github.com/RocketChat/Rocket.Chat/pull/19645) by [@bhavayAnand9](https://github.com/bhavayAnand9)) @@ -4953,15 +4845,15 @@ - User statuses in admin user info panel ([#20341](https://github.com/RocketChat/Rocket.Chat/pull/20341) by [@RonLek](https://github.com/RonLek)) - Modifies user statuses in admin info panel based on their actual status instead of their `statusConnection`. This enables correct and consistent change in user statuses. - Also, bot users having status as online were classified as offline, with this change they are now correctly classified based on their corresponding statuses. - + Modifies user statuses in admin info panel based on their actual status instead of their `statusConnection`. This enables correct and consistent change in user statuses. + Also, bot users having status as online were classified as offline, with this change they are now correctly classified based on their corresponding statuses. + https://user-images.githubusercontent.com/28918901/105624438-b8bcc500-5e47-11eb-8d1e-3a4180da1304.mp4 - Users autocomplete showing duplicated results ([#20481](https://github.com/RocketChat/Rocket.Chat/pull/20481) by [@Darshilp326](https://github.com/Darshilp326)) - Added new query for outside room users so that room members are not shown twice. - + Added new query for outside room users so that room members are not shown twice. + https://user-images.githubusercontent.com/55157259/106174582-33c10b00-61bb-11eb-9716-377ef7bba34e.mp4
@@ -4982,7 +4874,7 @@ - Chore: Disable Sessions Aggregates tests locally ([#20607](https://github.com/RocketChat/Rocket.Chat/pull/20607)) - Disable Session aggregates tests in local environments + Disable Session aggregates tests in local environments For context, refer to: #20161 - Chore: Improve performance of messages’ watcher ([#20519](https://github.com/RocketChat/Rocket.Chat/pull/20519)) @@ -5191,20 +5083,18 @@ - **ENTERPRISE:** Omnichannel Contact Manager as preferred agent for routing ([#20244](https://github.com/RocketChat/Rocket.Chat/pull/20244)) - If the `Contact-Manager` is assigned to a Visitor, the chat will automatically get transferred to the respective Contact-Manager, provided the Contact-Manager is online. In-case the Contact-Manager is offline, the chat will be transferred to any other online agent. - We have provided a setting to control this auto-assignment feature - ![image](https://user-images.githubusercontent.com/34130764/104880961-8104d780-5986-11eb-9d87-82b99814b028.png) - - Behavior based-on Routing method - - - 1. Auto-selection, Load-Balancing, or External Service (`autoAssignAgent = true`) - This is straightforward, - - if the Contact-manager is online, the chat will be transferred to the Contact-Manger only - - if the Contact-manager is offline, the chat will be transferred to any other online-agent based on the Routing system - - 2. Manual-selection (`autoAssignAgent = false`) - - If the Contact-Manager is online, the chat will appear in the Queue of Contact-Manager **ONLY** + If the `Contact-Manager` is assigned to a Visitor, the chat will automatically get transferred to the respective Contact-Manager, provided the Contact-Manager is online. In-case the Contact-Manager is offline, the chat will be transferred to any other online agent. + We have provided a setting to control this auto-assignment feature + ![image](https://user-images.githubusercontent.com/34130764/104880961-8104d780-5986-11eb-9d87-82b99814b028.png) + + Behavior based-on Routing method + + 1. Auto-selection, Load-Balancing, or External Service (`autoAssignAgent = true`) + This is straightforward, + - if the Contact-manager is online, the chat will be transferred to the Contact-Manger only + - if the Contact-manager is offline, the chat will be transferred to any other online-agent based on the Routing system + 2. Manual-selection (`autoAssignAgent = false`) + - If the Contact-Manager is online, the chat will appear in the Queue of Contact-Manager **ONLY** - If the Contact-Manager is offline, the chat will appear in the Queue of all related Agents/Manager ( like it's done right now ) - Banner system and NPS ([#20221](https://github.com/RocketChat/Rocket.Chat/pull/20221)) @@ -5213,34 +5103,34 @@ - Email Inboxes for Omnichannel ([#20101](https://github.com/RocketChat/Rocket.Chat/pull/20101) by [@rafaelblink](https://github.com/rafaelblink)) - With this new feature, email accounts will receive email messages(threads) which will be transformed into Omnichannel chats. It'll be possible to set up multiple email accounts, test the connection with email server(email provider) and define the behaviour of each account. - - https://user-images.githubusercontent.com/2493803/105430398-242d4980-5c32-11eb-835a-450c94837d23.mp4 - - ### New item on admin menu - - ![image](https://user-images.githubusercontent.com/2493803/105428723-bc293400-5c2e-11eb-8c02-e8d36ea82726.png) - - - ### Send test email tooltip - - ![image](https://user-images.githubusercontent.com/2493803/104366986-eaa16380-54f8-11eb-9ba7-831cfde2319c.png) - - - ### Inbox Info - - ![image](https://user-images.githubusercontent.com/2493803/104366796-ab731280-54f8-11eb-9941-a3cc8eb610e1.png) - - ### SMTP Info - - ![image](https://user-images.githubusercontent.com/2493803/104366868-c47bc380-54f8-11eb-969e-ccc29070957c.png) - - ### IMAP Info - - ![image](https://user-images.githubusercontent.com/2493803/104366897-cd6c9500-54f8-11eb-80c4-97d5b0c002d5.png) - - ### Messages - + With this new feature, email accounts will receive email messages(threads) which will be transformed into Omnichannel chats. It'll be possible to set up multiple email accounts, test the connection with email server(email provider) and define the behaviour of each account. + + https://user-images.githubusercontent.com/2493803/105430398-242d4980-5c32-11eb-835a-450c94837d23.mp4 + + ### New item on admin menu + + ![image](https://user-images.githubusercontent.com/2493803/105428723-bc293400-5c2e-11eb-8c02-e8d36ea82726.png) + + + ### Send test email tooltip + + ![image](https://user-images.githubusercontent.com/2493803/104366986-eaa16380-54f8-11eb-9ba7-831cfde2319c.png) + + + ### Inbox Info + + ![image](https://user-images.githubusercontent.com/2493803/104366796-ab731280-54f8-11eb-9941-a3cc8eb610e1.png) + + ### SMTP Info + + ![image](https://user-images.githubusercontent.com/2493803/104366868-c47bc380-54f8-11eb-969e-ccc29070957c.png) + + ### IMAP Info + + ![image](https://user-images.githubusercontent.com/2493803/104366897-cd6c9500-54f8-11eb-80c4-97d5b0c002d5.png) + + ### Messages + ![image](https://user-images.githubusercontent.com/2493803/105428971-45d90180-5c2f-11eb-992a-022a3df94471.png) - Encrypted Discussions and new Encryption Permissions ([#20201](https://github.com/RocketChat/Rocket.Chat/pull/20201)) @@ -5252,7 +5142,7 @@ - Add extra SAML settings to update room subs and add private room subs. ([#19489](https://github.com/RocketChat/Rocket.Chat/pull/19489) by [@tlskinneriv](https://github.com/tlskinneriv)) - Added a SAML setting to support updating room subscriptions each time a user logs in via SAML. + Added a SAML setting to support updating room subscriptions each time a user logs in via SAML. Added a SAML setting to support including private rooms in SAML updated subscriptions (whether initial or on each logon). - Autofocus on directory ([#20509](https://github.com/RocketChat/Rocket.Chat/pull/20509)) @@ -5279,7 +5169,7 @@ - Tooltip added for Kebab menu on chat header ([#20116](https://github.com/RocketChat/Rocket.Chat/pull/20116) by [@yash-rajpal](https://github.com/yash-rajpal)) - Added the missing Tooltip for kebab menu on chat header. + Added the missing Tooltip for kebab menu on chat header. ![tooltip after](https://user-images.githubusercontent.com/58601732/104031406-b07f4b80-51f2-11eb-87a4-1e8da78a254f.gif) ### 🐛 Bug fixes @@ -5301,7 +5191,7 @@ - Added context check for closing active tabbar for member-list ([#20228](https://github.com/RocketChat/Rocket.Chat/pull/20228) by [@yash-rajpal](https://github.com/yash-rajpal)) - When we click on a username and then click on see user's full profile, a tab gets active and shows us the user's profile, the problem occurs when the tab is still active and we try to see another user's profile. In this case, tabbar gets closed. + When we click on a username and then click on see user's full profile, a tab gets active and shows us the user's profile, the problem occurs when the tab is still active and we try to see another user's profile. In this case, tabbar gets closed. To resolve this, added context check for closing action of active tabbar. - Added Margin between status bullet and status label ([#20199](https://github.com/RocketChat/Rocket.Chat/pull/20199) by [@yash-rajpal](https://github.com/yash-rajpal)) @@ -5310,8 +5200,8 @@ - Added success message on saving notification preference. ([#20220](https://github.com/RocketChat/Rocket.Chat/pull/20220) by [@Darshilp326](https://github.com/Darshilp326)) - Added success message after saving notification preferences. - + Added success message after saving notification preferences. + https://user-images.githubusercontent.com/55157259/104774617-03ca3e80-579d-11eb-8fa4-990b108dd8d9.mp4 - Admin User Info email verified status ([#20110](https://github.com/RocketChat/Rocket.Chat/pull/20110) by [@bdelwood](https://github.com/bdelwood)) @@ -5320,10 +5210,10 @@ - Change header's favorite icon to filled star ([#20174](https://github.com/RocketChat/Rocket.Chat/pull/20174)) - ### Before: - ![image](https://user-images.githubusercontent.com/27704687/104351819-a60bcd00-54e4-11eb-8b43-7d281a6e5dcb.png) - - ### After: + ### Before: + ![image](https://user-images.githubusercontent.com/27704687/104351819-a60bcd00-54e4-11eb-8b43-7d281a6e5dcb.png) + + ### After: ![image](https://user-images.githubusercontent.com/27704687/104351632-67761280-54e4-11eb-87ba-25b940494bb5.png) - Changed success message for adding custom sound. ([#20272](https://github.com/RocketChat/Rocket.Chat/pull/20272) by [@Darshilp326](https://github.com/Darshilp326)) @@ -5332,24 +5222,24 @@ - Changed success message for ignoring member. ([#19996](https://github.com/RocketChat/Rocket.Chat/pull/19996) by [@Darshilp326](https://github.com/Darshilp326)) - Different messages for ignoring/unignoring will be displayed. - + Different messages for ignoring/unignoring will be displayed. + https://user-images.githubusercontent.com/55157259/103310307-4241c880-4a3d-11eb-8c6c-4c9b99d023db.mp4 - Creation of Omnichannel rooms not working correctly through the Apps when the agent parameter is set ([#19997](https://github.com/RocketChat/Rocket.Chat/pull/19997)) - Engagement dashboard graphs labels superposing each other ([#20267](https://github.com/RocketChat/Rocket.Chat/pull/20267)) - Now after a certain breakpoint, the graphs should stack vertically, and overlapping text rotated. - + Now after a certain breakpoint, the graphs should stack vertically, and overlapping text rotated. + ![image](https://user-images.githubusercontent.com/40830821/105098926-93b40500-5a89-11eb-9a56-2fc3b1552914.png) - Fields overflowing page ([#20287](https://github.com/RocketChat/Rocket.Chat/pull/20287)) - ### Before - ![image](https://user-images.githubusercontent.com/40830821/105246952-c1b14c00-5b52-11eb-8671-cff88edf242d.png) - - ### After + ### Before + ![image](https://user-images.githubusercontent.com/40830821/105246952-c1b14c00-5b52-11eb-8671-cff88edf242d.png) + + ### After ![image](https://user-images.githubusercontent.com/40830821/105247125-0a690500-5b53-11eb-9f3c-d6a68108e336.png) - Fix error that occurs on changing archive status of room ([#20098](https://github.com/RocketChat/Rocket.Chat/pull/20098) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) @@ -5366,7 +5256,7 @@ - Livechat.RegisterGuest method removing unset fields ([#20124](https://github.com/RocketChat/Rocket.Chat/pull/20124) by [@rafaelblink](https://github.com/rafaelblink)) - After changes made on https://github.com/RocketChat/Rocket.Chat/pull/19931, the `Livechat.RegisterGuest` method started removing properties from the visitor inappropriately. The properties that did not receive value were removed from the object. + After changes made on https://github.com/RocketChat/Rocket.Chat/pull/19931, the `Livechat.RegisterGuest` method started removing properties from the visitor inappropriately. The properties that did not receive value were removed from the object. Those changes were made to support the new Contact Form, but now the form has its own method to deal with Contact data so those changes are no longer necessary. - Markdown added for Header Room topic ([#20021](https://github.com/RocketChat/Rocket.Chat/pull/20021) by [@yash-rajpal](https://github.com/yash-rajpal)) @@ -5387,18 +5277,18 @@ - Omnichannel - Contact Center form is not validating custom fields properly ([#20196](https://github.com/RocketChat/Rocket.Chat/pull/20196) by [@rafaelblink](https://github.com/rafaelblink)) - The contact form is accepting undefined values in required custom fields when creating or editing contacts, and, the errror message isn't following Rocket.chat design system. - - ### Before - ![image](https://user-images.githubusercontent.com/2493803/104522668-31688980-55dd-11eb-92c5-83f96073edc4.png) - - ### After - - #### New - ![image](https://user-images.githubusercontent.com/2493803/104770494-68f74300-574f-11eb-94a3-c8fd73365308.png) - - - #### Edit + The contact form is accepting undefined values in required custom fields when creating or editing contacts, and, the errror message isn't following Rocket.chat design system. + + ### Before + ![image](https://user-images.githubusercontent.com/2493803/104522668-31688980-55dd-11eb-92c5-83f96073edc4.png) + + ### After + + #### New + ![image](https://user-images.githubusercontent.com/2493803/104770494-68f74300-574f-11eb-94a3-c8fd73365308.png) + + + #### Edit ![image](https://user-images.githubusercontent.com/2493803/104770538-7b717c80-574f-11eb-829f-1ae304103369.png) - Omnichannel Agents unable to take new chats in the queue ([#20022](https://github.com/RocketChat/Rocket.Chat/pull/20022) by [@rafaelblink](https://github.com/rafaelblink)) @@ -5419,15 +5309,15 @@ - Room special name in prompts ([#20277](https://github.com/RocketChat/Rocket.Chat/pull/20277) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) - The "Hide room" and "Leave Room" confirmation prompts use the "name" key from the room info. When the setting " - Allow Special Characters in Room Names" is enabled, the prompts show the normalized names instead of those that contain the special characters. - - Changed the value being used from name to fname, which always has the user-set name. - - Previous: - ![Screenshot from 2021-01-20 15-52-29](https://user-images.githubusercontent.com/38764067/105161642-9b31e780-5b37-11eb-8b0c-ec4b1414c948.png) - - Updated: + The "Hide room" and "Leave Room" confirmation prompts use the "name" key from the room info. When the setting " + Allow Special Characters in Room Names" is enabled, the prompts show the normalized names instead of those that contain the special characters. + + Changed the value being used from name to fname, which always has the user-set name. + + Previous: + ![Screenshot from 2021-01-20 15-52-29](https://user-images.githubusercontent.com/38764067/105161642-9b31e780-5b37-11eb-8b0c-ec4b1414c948.png) + + Updated: ![Screenshot from 2021-01-20 15-50-19](https://user-images.githubusercontent.com/38764067/105161627-966d3380-5b37-11eb-9812-3dd9352b4f95.png) - Room's list showing all rooms with same name ([#20176](https://github.com/RocketChat/Rocket.Chat/pull/20176)) @@ -5438,9 +5328,9 @@ - Saving with blank email in edit user ([#20259](https://github.com/RocketChat/Rocket.Chat/pull/20259) by [@RonLek](https://github.com/RonLek)) - Disallows showing a success popup when email field is made blank in Edit User and instead shows the relevant error popup. - - + Disallows showing a success popup when email field is made blank in Edit User and instead shows the relevant error popup. + + https://user-images.githubusercontent.com/28918901/104960749-dbd81680-59fa-11eb-9c7b-2b257936f894.mp4 - Search list filter ([#19937](https://github.com/RocketChat/Rocket.Chat/pull/19937)) @@ -5487,7 +5377,7 @@ - Add translation of Edit Status in all languages ([#19916](https://github.com/RocketChat/Rocket.Chat/pull/19916) by [@sushant52](https://github.com/sushant52)) - Closes [#19915](https://github.com/RocketChat/Rocket.Chat/issues/19915) + Closes [#19915](https://github.com/RocketChat/Rocket.Chat/issues/19915) The profile options menu is well translated in many languages. However, Edit Status is the only button which is not well translated. With this change, the whole profile options will be properly translated in a lot of languages. - Bump axios from 0.18.0 to 0.18.1 ([#20055](https://github.com/RocketChat/Rocket.Chat/pull/20055) by [@dependabot[bot]](https://github.com/dependabot[bot])) @@ -5522,10 +5412,10 @@ - Regression: Announcement bar not showing properly Markdown content ([#20290](https://github.com/RocketChat/Rocket.Chat/pull/20290)) - **Before**: - ![image](https://user-images.githubusercontent.com/27704687/105273746-a4907380-5b7a-11eb-8121-aff665251c44.png) - - **After**: + **Before**: + ![image](https://user-images.githubusercontent.com/27704687/105273746-a4907380-5b7a-11eb-8121-aff665251c44.png) + + **After**: ![image](https://user-images.githubusercontent.com/27704687/105274050-2e404100-5b7b-11eb-93b2-b6282a7bed95.png) - regression: Announcement link open in new tab ([#20435](https://github.com/RocketChat/Rocket.Chat/pull/20435)) @@ -5540,23 +5430,23 @@ - Regression: Change sort icon ([#20177](https://github.com/RocketChat/Rocket.Chat/pull/20177)) - ### Before - ![image](https://user-images.githubusercontent.com/40830821/104366414-1bcd6400-54f8-11eb-9fc7-c6f13f07a61e.png) - - ### After + ### Before + ![image](https://user-images.githubusercontent.com/40830821/104366414-1bcd6400-54f8-11eb-9fc7-c6f13f07a61e.png) + + ### After ![image](https://user-images.githubusercontent.com/40830821/104366542-4cad9900-54f8-11eb-83ca-acb99899515a.png) - Regression: Custom field labels are not displayed properly on Omnichannel Contact Profile form ([#20393](https://github.com/RocketChat/Rocket.Chat/pull/20393) by [@rafaelblink](https://github.com/rafaelblink)) - ### Before - ![image](https://user-images.githubusercontent.com/2493803/105780399-20116c80-5f4f-11eb-9620-0901472e453b.png) - - ![image](https://user-images.githubusercontent.com/2493803/105780420-2e5f8880-5f4f-11eb-8e93-8115ebc685be.png) - - ### After - - ![image](https://user-images.githubusercontent.com/2493803/105780832-1ccab080-5f50-11eb-8042-188dd0c41904.png) - + ### Before + ![image](https://user-images.githubusercontent.com/2493803/105780399-20116c80-5f4f-11eb-9620-0901472e453b.png) + + ![image](https://user-images.githubusercontent.com/2493803/105780420-2e5f8880-5f4f-11eb-8e93-8115ebc685be.png) + + ### After + + ![image](https://user-images.githubusercontent.com/2493803/105780832-1ccab080-5f50-11eb-8042-188dd0c41904.png) + ![image](https://user-images.githubusercontent.com/2493803/105780911-500d3f80-5f50-11eb-96e0-7df3f179dbd5.png) - Regression: ESLint Warning - explicit-function-return-type ([#20434](https://github.com/RocketChat/Rocket.Chat/pull/20434) by [@aditya-mitra](https://github.com/aditya-mitra)) @@ -5573,8 +5463,8 @@ - Regression: Fixed update room avatar issue. ([#20433](https://github.com/RocketChat/Rocket.Chat/pull/20433) by [@Darshilp326](https://github.com/Darshilp326)) - Users can now update their room avatar without any error. - + Users can now update their room avatar without any error. + https://user-images.githubusercontent.com/55157259/105951602-560d3880-6096-11eb-97a5-b5eb9a28b58d.mp4 - Regression: Info Page Icon style and usage graph breaking ([#20180](https://github.com/RocketChat/Rocket.Chat/pull/20180)) @@ -5591,11 +5481,11 @@ - Regression: Unread superposing announcement. ([#20306](https://github.com/RocketChat/Rocket.Chat/pull/20306)) - ### Before - ![image](https://user-images.githubusercontent.com/40830821/105412619-c2f67d80-5c13-11eb-8204-5932ea880c8a.png) - - - ### After + ### Before + ![image](https://user-images.githubusercontent.com/40830821/105412619-c2f67d80-5c13-11eb-8204-5932ea880c8a.png) + + + ### After ![image](https://user-images.githubusercontent.com/40830821/105411176-d1439a00-5c11-11eb-8d1b-ea27c8485214.png) - Regression: User Dropdown margin ([#20222](https://github.com/RocketChat/Rocket.Chat/pull/20222)) @@ -5883,8 +5773,8 @@ - Hightlights validation on Account Preferences page ([#19902](https://github.com/RocketChat/Rocket.Chat/pull/19902) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) - This PR fixes two issues in the account settings "preferences" panel. - Once set, the "Highlighted Words" setting cannot be reset to an empty string. This was fixed by changing the string validation from checking the length to checking the type of variable. + This PR fixes two issues in the account settings "preferences" panel. + Once set, the "Highlighted Words" setting cannot be reset to an empty string. This was fixed by changing the string validation from checking the length to checking the type of variable. Secondly, it tracks the changes to correctly identify if changes after the last "save changes" action have been made, using an "updates" state variable, instead of just comparing against the initialValue that does not change on clicking "save changes". - Image preview for image URLs on messages ([#19734](https://github.com/RocketChat/Rocket.Chat/pull/19734) by [@g-thome](https://github.com/g-thome)) @@ -5943,14 +5833,10 @@ - Chore: Update Pull Request template ([#19768](https://github.com/RocketChat/Rocket.Chat/pull/19768)) - Improve the template of Pull Requests in order to make it clear reducing duplicated information and removing the visible checklists that were generating noise and misunderstanding with the PR progress. - - - Moved the checklists to inside comments - - - Merge the changelog and proposed changes sections to have a single source of description that goes to the changelog - - - Remove the screenshot section, they can be added inside the description - + Improve the template of Pull Requests in order to make it clear reducing duplicated information and removing the visible checklists that were generating noise and misunderstanding with the PR progress. + - Moved the checklists to inside comments + - Merge the changelog and proposed changes sections to have a single source of description that goes to the changelog + - Remove the screenshot section, they can be added inside the description - Changed the proposed changes title to incentivizing the usage of images and videos - Frontend folder structure ([#19631](https://github.com/RocketChat/Rocket.Chat/pull/19631)) @@ -5985,11 +5871,11 @@ - Regression: Double Scrollbars on tables ([#19980](https://github.com/RocketChat/Rocket.Chat/pull/19980)) - Before: - ![image](https://user-images.githubusercontent.com/40830821/103242719-0ec84680-4936-11eb-87a7-68b6eea8de7b.png) - - - After: + Before: + ![image](https://user-images.githubusercontent.com/40830821/103242719-0ec84680-4936-11eb-87a7-68b6eea8de7b.png) + + + After: ![image](https://user-images.githubusercontent.com/40830821/103242680-ee988780-4935-11eb-99e2-a95de99f78f1.png) - Regression: Failed autolinker and markdown rendering ([#19831](https://github.com/RocketChat/Rocket.Chat/pull/19831)) @@ -6008,7 +5894,7 @@ - Regression: Omnichannel Custom Fields Form no longer working after refactoring ([#19948](https://github.com/RocketChat/Rocket.Chat/pull/19948)) - The Omnichannel `Custom Fields` form is not working anymore after some refactorings on client-side. + The Omnichannel `Custom Fields` form is not working anymore after some refactorings on client-side. When the user clicks on `Custom Field` in the Omnichannel menu, a blank page appears. - Regression: polishing licenses endpoints ([#19981](https://github.com/RocketChat/Rocket.Chat/pull/19981) by [@g-thome](https://github.com/g-thome)) @@ -6207,8 +6093,8 @@ - Bundle Size Client ([#19533](https://github.com/RocketChat/Rocket.Chat/pull/19533)) - temporarily removes some codeblock languages - Moved some libraries to dynamic imports + temporarily removes some codeblock languages + Moved some libraries to dynamic imports Removed some shared code not used on the client side - Forward Omnichannel room to agent in another department ([#19576](https://github.com/RocketChat/Rocket.Chat/pull/19576) by [@mrfigueiredo](https://github.com/mrfigueiredo)) @@ -7289,10 +7175,8 @@ - **2FA:** Password enforcement setting and 2FA protection when saving settings or resetting E2E encryption ([#18640](https://github.com/RocketChat/Rocket.Chat/pull/18640)) - - Increase the 2FA remembering time from 5min to 30min - - - Add new setting to enforce 2FA password fallback (enabled only for new installations) - + - Increase the 2FA remembering time from 5min to 30min + - Add new setting to enforce 2FA password fallback (enabled only for new installations) - Require 2FA to save settings and reset E2E Encryption keys - **Omnichannel:** Allow set other agent status via method `livechat:changeLivechatStatus ` ([#18571](https://github.com/RocketChat/Rocket.Chat/pull/18571)) @@ -7310,7 +7194,7 @@ - 2FA by Email setting showing for the user even when disabled by the admin ([#18473](https://github.com/RocketChat/Rocket.Chat/pull/18473)) - The option to disable/enable the **Two-factor authentication via Email** at `Account > Security > Two Factor Authentication + The option to disable/enable the **Two-factor authentication via Email** at `Account > Security > Two Factor Authentication ` was visible even when the setting **Enable Two Factor Authentication via Email** at `Admin > Accounts > Two Factor Authentication` was disabled leading to misbehavior since the functionality was disabled. - Agents enabledDepartment attribute not set on collection ([#18614](https://github.com/RocketChat/Rocket.Chat/pull/18614) by [@paulobernardoaf](https://github.com/paulobernardoaf)) @@ -7660,16 +7544,13 @@ - Mention autocomplete UI and performance improvements ([#18309](https://github.com/RocketChat/Rocket.Chat/pull/18309)) - * New setting to configure the number of suggestions `Admin > Layout > User Interface > Number of users' autocomplete suggestions` (default 5) - - * The UI shows whenever the user is not a member of the room - - * The UI shows when the suggestion came from the last messages for quick selection/reply - - * The suggestions follow this order: - * The user with the exact username and member of the room - * The user with the exact username but not a member of the room (if allowed to list non-members) - * The users containing the text in username, name or nickname and member of the room + * New setting to configure the number of suggestions `Admin > Layout > User Interface > Number of users' autocomplete suggestions` (default 5) + * The UI shows whenever the user is not a member of the room + * The UI shows when the suggestion came from the last messages for quick selection/reply + * The suggestions follow this order: + * The user with the exact username and member of the room + * The user with the exact username but not a member of the room (if allowed to list non-members) + * The users containing the text in username, name or nickname and member of the room * The users containing the text in username, name or nickname and not a member of the room (if allowed to list non-members) - Message action styles ([#18190](https://github.com/RocketChat/Rocket.Chat/pull/18190)) @@ -8011,10 +7892,10 @@ - Split NOTIFICATIONS_SCHEDULE_DELAY into three separate variables ([#17669](https://github.com/RocketChat/Rocket.Chat/pull/17669) by [@jazztickets](https://github.com/jazztickets)) - Email notification delay can now be customized with the following environment variables: - NOTIFICATIONS_SCHEDULE_DELAY_ONLINE - NOTIFICATIONS_SCHEDULE_DELAY_AWAY - NOTIFICATIONS_SCHEDULE_DELAY_OFFLINE + Email notification delay can now be customized with the following environment variables: + NOTIFICATIONS_SCHEDULE_DELAY_ONLINE + NOTIFICATIONS_SCHEDULE_DELAY_AWAY + NOTIFICATIONS_SCHEDULE_DELAY_OFFLINE Setting the value to -1 disable notifications for that type. - Threads ([#17416](https://github.com/RocketChat/Rocket.Chat/pull/17416)) @@ -8414,11 +8295,11 @@ - **ENTERPRISE:** Omnichannel Last-Chatted Agent Preferred option ([#17666](https://github.com/RocketChat/Rocket.Chat/pull/17666)) - If activated, this feature will store the last agent that assisted each Omnichannel visitor when a conversation is taken. So, when a visitor returns(it works with any entry point, Livechat, Facebook, REST API, and so on) and starts a new chat, the routing system checks: - - 1 - The visitor object for any stored agent that the visitor has previously talked to; - 2 - If a previous agent is not found, the system will try to find a previous conversation of the same visitor. If a room is found, the system will get the previous agent from the room; - + If activated, this feature will store the last agent that assisted each Omnichannel visitor when a conversation is taken. So, when a visitor returns(it works with any entry point, Livechat, Facebook, REST API, and so on) and starts a new chat, the routing system checks: + + 1 - The visitor object for any stored agent that the visitor has previously talked to; + 2 - If a previous agent is not found, the system will try to find a previous conversation of the same visitor. If a room is found, the system will get the previous agent from the room; + After this process, if an agent has been found, the system will check the agent's availability to assist the new chat. If it's not available, then the routing system will get the next available agent in the queue. - **ENTERPRISE:** Support for custom Livechat registration form fields ([#17581](https://github.com/RocketChat/Rocket.Chat/pull/17581)) @@ -8523,12 +8404,9 @@ - Notification sounds ([#17616](https://github.com/RocketChat/Rocket.Chat/pull/17616)) - * Global CDN config was ignored when loading the sound files - - * Upload of custom sounds wasn't getting the file extension correctly - - * Some translations were missing - + * Global CDN config was ignored when loading the sound files + * Upload of custom sounds wasn't getting the file extension correctly + * Some translations were missing * Edit and delete of custom sounds were not working correctly - Omnichannel departments are not saved when the offline channel name is not defined ([#17553](https://github.com/RocketChat/Rocket.Chat/pull/17553)) @@ -8816,19 +8694,14 @@ - Better Push and Email Notification logic ([#17357](https://github.com/RocketChat/Rocket.Chat/pull/17357)) - We are still using the same logic to define which notifications every new message will generate, it takes some servers' settings, users's preferences and subscriptions' settings in consideration to determine who will receive each notification type (desktop, audio, email and mobile push), but now it doesn't check the user's status (online, away, offline) for email and mobile push notifications but send those notifications to a new queue with the following rules: - - - - When the user is online the notification is scheduled to be sent in 120 seconds - - - When the user is away the notification is scheduled to be sent in 120 seconds minus the amount of time he is away - - - When the user is offline the notification is scheduled to be sent right away - - - When the user reads a channel all the notifications for that user are removed (clear queue) - - - When a notification is processed to be sent to a user and there are other scheduled notifications: - - All the scheduled notifications for that user are rescheduled to now + We are still using the same logic to define which notifications every new message will generate, it takes some servers' settings, users's preferences and subscriptions' settings in consideration to determine who will receive each notification type (desktop, audio, email and mobile push), but now it doesn't check the user's status (online, away, offline) for email and mobile push notifications but send those notifications to a new queue with the following rules: + + - When the user is online the notification is scheduled to be sent in 120 seconds + - When the user is away the notification is scheduled to be sent in 120 seconds minus the amount of time he is away + - When the user is offline the notification is scheduled to be sent right away + - When the user reads a channel all the notifications for that user are removed (clear queue) + - When a notification is processed to be sent to a user and there are other scheduled notifications: + - All the scheduled notifications for that user are rescheduled to now - The current notification goes back to the queue to be processed ordered by creation date - Buttons to check/uncheck all users and channels on import ([#17207](https://github.com/RocketChat/Rocket.Chat/pull/17207)) @@ -9191,7 +9064,7 @@ - Translation via MS translate ([#16363](https://github.com/RocketChat/Rocket.Chat/pull/16363) by [@mrsimpson](https://github.com/mrsimpson)) - Adds Microsoft's translation service (https://translator.microsoft.com/) as a provider for translation of messages. + Adds Microsoft's translation service (https://translator.microsoft.com/) as a provider for translation of messages. In addition to implementing the interface (similar to google and DeepL), a small change has been done in order to display the translation provider on the UI. - Two Factor authentication via email ([#15949](https://github.com/RocketChat/Rocket.Chat/pull/15949)) @@ -20730,4 +20603,4 @@ - [@graywolf336](https://github.com/graywolf336) - [@marceloschmidt](https://github.com/marceloschmidt) - [@rodrigok](https://github.com/rodrigok) -- [@sampaiodiego](https://github.com/sampaiodiego) +- [@sampaiodiego](https://github.com/sampaiodiego) \ No newline at end of file diff --git a/app/utils/rocketchat.info b/app/utils/rocketchat.info index 71761e2c92ace..40c1615cfe807 100644 --- a/app/utils/rocketchat.info +++ b/app/utils/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "4.1.0-rc.3" + "version": "4.1.0-rc.4" } diff --git a/package-lock.json b/package-lock.json index 5a3491ca6a712..2609d2871c337 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "Rocket.Chat", - "version": "4.1.0-rc.3", + "version": "4.1.0-rc.4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 93f4583dc345b..83309d28afcfa 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Rocket.Chat", "description": "The Ultimate Open Source WebChat Platform", - "version": "4.1.0-rc.3", + "version": "4.1.0-rc.4", "author": { "name": "Rocket.Chat", "url": "https://rocket.chat/" From 1cd901e085517c075c87f90b73ad8813501bc686 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 27 Oct 2021 23:48:00 -0300 Subject: [PATCH 036/137] Bump version to 4.1.0 --- .docker/Dockerfile.rhel | 2 +- .github/history.json | 15 +++- .snapcraft/resources/prepareRocketChat | 2 +- .snapcraft/snap/snapcraft.yaml | 2 +- HISTORY.md | 118 +++++++------------------ app/utils/rocketchat.info | 2 +- package-lock.json | 2 +- package.json | 2 +- 8 files changed, 51 insertions(+), 94 deletions(-) diff --git a/.docker/Dockerfile.rhel b/.docker/Dockerfile.rhel index b52f652759200..8822de22ef80d 100644 --- a/.docker/Dockerfile.rhel +++ b/.docker/Dockerfile.rhel @@ -1,6 +1,6 @@ FROM registry.access.redhat.com/ubi8/nodejs-12 -ENV RC_VERSION 4.1.0-rc.4 +ENV RC_VERSION 4.1.0 MAINTAINER buildmaster@rocket.chat diff --git a/.github/history.json b/.github/history.json index a5452342705b7..890111d8e9404 100644 --- a/.github/history.json +++ b/.github/history.json @@ -67019,6 +67019,19 @@ ] } ] + }, + "4.1.0": { + "node_version": "12.22.1", + "npm_version": "6.14.1", + "apps_engine_version": "1.28.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [] } } -} +} \ No newline at end of file diff --git a/.snapcraft/resources/prepareRocketChat b/.snapcraft/resources/prepareRocketChat index 31e33dbcfa7ea..92c8f26d454de 100755 --- a/.snapcraft/resources/prepareRocketChat +++ b/.snapcraft/resources/prepareRocketChat @@ -1,6 +1,6 @@ #!/bin/bash -curl -SLf "https://releases.rocket.chat/4.1.0-rc.4/download/" -o rocket.chat.tgz +curl -SLf "https://releases.rocket.chat/4.1.0/download/" -o rocket.chat.tgz tar xf rocket.chat.tgz --strip 1 diff --git a/.snapcraft/snap/snapcraft.yaml b/.snapcraft/snap/snapcraft.yaml index f7d11bb6fa6ab..9c13354e4f6a0 100644 --- a/.snapcraft/snap/snapcraft.yaml +++ b/.snapcraft/snap/snapcraft.yaml @@ -7,7 +7,7 @@ # 5. `snapcraft snap` name: rocketchat-server -version: 4.1.0-rc.4 +version: 4.1.0 summary: Rocket.Chat server description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ confinement: strict diff --git a/HISTORY.md b/HISTORY.md index 8ccee74bcea24..e3aa63d8aa179 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,92 +1,12 @@ -# 4.1.0 (Under Release Candidate Process) +# 4.1.0 +`2021-10-27 · 1 🎉 · 4 🚀 · 25 🐛 · 38 🔍 · 23 👩‍💻👨‍💻` -## 4.1.0-rc.4 -`2021-10-27 · 1 🔍 · 1 👩‍💻👨‍💻` - -
-🔍 Minor changes - - -- Regression: Debounce call based on params on omnichannel queue dispatch ([#23577](https://github.com/RocketChat/Rocket.Chat/pull/23577)) - -
- -### 👩‍💻👨‍💻 Core Team 🤓 - -- [@KevLehman](https://github.com/KevLehman) - -## 4.1.0-rc.3 -`2021-10-27 · 3 🔍 · 3 👩‍💻👨‍💻` - -
-🔍 Minor changes - - -- Bump: fuselage 0.30.1 ([#23391](https://github.com/RocketChat/Rocket.Chat/pull/23391)) - -- Regression: Prevent settings from getting updated ([#23556](https://github.com/RocketChat/Rocket.Chat/pull/23556)) - -- Regression: Routing method not available when called from listeners at startup ([#23568](https://github.com/RocketChat/Rocket.Chat/pull/23568)) - -
- -### 👩‍💻👨‍💻 Core Team 🤓 - -- [@KevLehman](https://github.com/KevLehman) -- [@dougfabris](https://github.com/dougfabris) -- [@sampaiodiego](https://github.com/sampaiodiego) - -## 4.1.0-rc.2 -`2021-10-25 · 1 🐛 · 1 🔍 · 3 👩‍💻👨‍💻` - -### 🐛 Bug fixes - - -- OAuth login not working on mobile app ([#23541](https://github.com/RocketChat/Rocket.Chat/pull/23541)) - -
-🔍 Minor changes - - -- Regression: Mail body contains `undefined` text ([#23552](https://github.com/RocketChat/Rocket.Chat/pull/23552)) - - ### Before - ![image](https://user-images.githubusercontent.com/2263066/138733018-10449892-5c2d-46fb-9355-00e98e0d6c9f.png) - - ### After - ![image](https://user-images.githubusercontent.com/2263066/138733074-a1b88a77-bf64-41c3-a6c3-ac9e1cb63de1.png) - -
- -### 👩‍💻👨‍💻 Core Team 🤓 - -- [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) -- [@sampaiodiego](https://github.com/sampaiodiego) -- [@tassoevan](https://github.com/tassoevan) - -## 4.1.0-rc.1 -`2021-10-21 · 3 🔍 · 2 👩‍💻👨‍💻` - -
-🔍 Minor changes - - -- Regression: Settings order ([#23528](https://github.com/RocketChat/Rocket.Chat/pull/23528)) - -- Regression: Waiting_queue setting not being applied due to missing module key ([#23531](https://github.com/RocketChat/Rocket.Chat/pull/23531)) - -- Regression: watchByRegex without Fibers ([#23529](https://github.com/RocketChat/Rocket.Chat/pull/23529)) - -
- -### 👩‍💻👨‍💻 Core Team 🤓 - -- [@KevLehman](https://github.com/KevLehman) -- [@ggazzo](https://github.com/ggazzo) - -## 4.1.0-rc.0 -`2021-10-20 · 1 🎉 · 4 🚀 · 24 🐛 · 30 🔍 · 23 👩‍💻👨‍💻` +### Engine versions +- Node: `12.22.1` +- NPM: `6.14.1` +- MongoDB: `3.6, 4.0, 4.2, 4.4, 5.0` +- Apps-Engine: `1.28.0` ### 🎉 New features @@ -161,6 +81,8 @@ - MongoDB deprecation link ([#23381](https://github.com/RocketChat/Rocket.Chat/pull/23381)) +- OAuth login not working on mobile app ([#23541](https://github.com/RocketChat/Rocket.Chat/pull/23541)) + - Omni-Webhook's retry mechanism going in infinite loop ([#23394](https://github.com/RocketChat/Rocket.Chat/pull/23394)) - Prevent starting Omni-Queue if Omnichannel is disabled ([#23396](https://github.com/RocketChat/Rocket.Chat/pull/23396)) @@ -204,6 +126,8 @@ - Bump url-parse from 1.4.7 to 1.5.3 ([#23376](https://github.com/RocketChat/Rocket.Chat/pull/23376) by [@dependabot[bot]](https://github.com/dependabot[bot])) +- Bump: fuselage 0.30.1 ([#23391](https://github.com/RocketChat/Rocket.Chat/pull/23391)) + - Chore: clean README ([#23342](https://github.com/RocketChat/Rocket.Chat/pull/23342) by [@AbhJ](https://github.com/AbhJ)) - Chore: Document REST API endpoints (banners) ([#23361](https://github.com/RocketChat/Rocket.Chat/pull/23361)) @@ -282,12 +206,32 @@ - Merge master into develop & Set version to 4.1.0-develop ([#23362](https://github.com/RocketChat/Rocket.Chat/pull/23362)) +- Regression: Debounce call based on params on omnichannel queue dispatch ([#23577](https://github.com/RocketChat/Rocket.Chat/pull/23577)) + - Regression: Fix enterprise setting validation ([#23519](https://github.com/RocketChat/Rocket.Chat/pull/23519)) - Regression: Fix user typings style ([#23511](https://github.com/RocketChat/Rocket.Chat/pull/23511)) +- Regression: Mail body contains `undefined` text ([#23552](https://github.com/RocketChat/Rocket.Chat/pull/23552)) + + ### Before + ![image](https://user-images.githubusercontent.com/2263066/138733018-10449892-5c2d-46fb-9355-00e98e0d6c9f.png) + + ### After + ![image](https://user-images.githubusercontent.com/2263066/138733074-a1b88a77-bf64-41c3-a6c3-ac9e1cb63de1.png) + +- Regression: Prevent settings from getting updated ([#23556](https://github.com/RocketChat/Rocket.Chat/pull/23556)) + - Regression: Prevent Settings Unit Test Error ([#23506](https://github.com/RocketChat/Rocket.Chat/pull/23506)) +- Regression: Routing method not available when called from listeners at startup ([#23568](https://github.com/RocketChat/Rocket.Chat/pull/23568)) + +- Regression: Settings order ([#23528](https://github.com/RocketChat/Rocket.Chat/pull/23528)) + +- Regression: Waiting_queue setting not being applied due to missing module key ([#23531](https://github.com/RocketChat/Rocket.Chat/pull/23531)) + +- Regression: watchByRegex without Fibers ([#23529](https://github.com/RocketChat/Rocket.Chat/pull/23529)) + - Update the community open call link in README ([#23497](https://github.com/RocketChat/Rocket.Chat/pull/23497))
diff --git a/app/utils/rocketchat.info b/app/utils/rocketchat.info index 40c1615cfe807..b3e4de0fd7454 100644 --- a/app/utils/rocketchat.info +++ b/app/utils/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "4.1.0-rc.4" + "version": "4.1.0" } diff --git a/package-lock.json b/package-lock.json index 2609d2871c337..b4f4b752b5aa6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "Rocket.Chat", - "version": "4.1.0-rc.4", + "version": "4.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 83309d28afcfa..8c4109ab850df 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Rocket.Chat", "description": "The Ultimate Open Source WebChat Platform", - "version": "4.1.0-rc.4", + "version": "4.1.0", "author": { "name": "Rocket.Chat", "url": "https://rocket.chat/" From 4c37c5c97474a57e262e2b95a5867dd9d11d2861 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 28 Oct 2021 11:40:30 -0300 Subject: [PATCH 037/137] Bump version to 4.2.0-develop --- .docker/Dockerfile.rhel | 2 +- .snapcraft/resources/prepareRocketChat | 2 +- .snapcraft/snap/snapcraft.yaml | 2 +- app/utils/rocketchat.info | 2 +- package-lock.json | 2 +- package.json | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.docker/Dockerfile.rhel b/.docker/Dockerfile.rhel index 8822de22ef80d..098af5ace225d 100644 --- a/.docker/Dockerfile.rhel +++ b/.docker/Dockerfile.rhel @@ -1,6 +1,6 @@ FROM registry.access.redhat.com/ubi8/nodejs-12 -ENV RC_VERSION 4.1.0 +ENV RC_VERSION 4.2.0-develop MAINTAINER buildmaster@rocket.chat diff --git a/.snapcraft/resources/prepareRocketChat b/.snapcraft/resources/prepareRocketChat index 92c8f26d454de..51dced777dab7 100755 --- a/.snapcraft/resources/prepareRocketChat +++ b/.snapcraft/resources/prepareRocketChat @@ -1,6 +1,6 @@ #!/bin/bash -curl -SLf "https://releases.rocket.chat/4.1.0/download/" -o rocket.chat.tgz +curl -SLf "https://releases.rocket.chat/4.2.0-develop/download/" -o rocket.chat.tgz tar xf rocket.chat.tgz --strip 1 diff --git a/.snapcraft/snap/snapcraft.yaml b/.snapcraft/snap/snapcraft.yaml index 9c13354e4f6a0..bee2a499cbc4b 100644 --- a/.snapcraft/snap/snapcraft.yaml +++ b/.snapcraft/snap/snapcraft.yaml @@ -7,7 +7,7 @@ # 5. `snapcraft snap` name: rocketchat-server -version: 4.1.0 +version: 4.2.0-develop summary: Rocket.Chat server description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ confinement: strict diff --git a/app/utils/rocketchat.info b/app/utils/rocketchat.info index b3e4de0fd7454..643b57be50040 100644 --- a/app/utils/rocketchat.info +++ b/app/utils/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "4.1.0" + "version": "4.2.0-develop" } diff --git a/package-lock.json b/package-lock.json index b4f4b752b5aa6..9fec1aaf51200 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "Rocket.Chat", - "version": "4.1.0", + "version": "4.2.0-develop", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 8c4109ab850df..e7b26de962e22 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Rocket.Chat", "description": "The Ultimate Open Source WebChat Platform", - "version": "4.1.0", + "version": "4.2.0-develop", "author": { "name": "Rocket.Chat", "url": "https://rocket.chat/" From ac84a4120fa76d6d4c0505ea51019343d5e9e7f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrique=20Guimar=C3=A3es=20Ribeiro?= Date: Fri, 29 Oct 2021 09:55:04 -0300 Subject: [PATCH 038/137] [IMPROVE] MKP12 - New UI - Merge Apps and Marketplace Tabs and Content (#23542) * WIP * improve: finished the page merging and routing logic Co-authored-by: Guilherme Gazzo Co-authored-by: dougfabris --- client/views/admin/apps/AppRow.tsx | 1 - client/views/admin/apps/AppsPage.js | 46 ------------ client/views/admin/apps/AppsPage.tsx | 84 ++++++++++++++++++++++ client/views/admin/apps/AppsRoute.js | 17 +++-- client/views/admin/routes.js | 5 ++ client/views/admin/sidebarItems.js | 8 +-- packages/rocketchat-i18n/i18n/en.i18n.json | 1 + 7 files changed, 99 insertions(+), 63 deletions(-) delete mode 100644 client/views/admin/apps/AppsPage.js create mode 100644 client/views/admin/apps/AppsPage.tsx diff --git a/client/views/admin/apps/AppRow.tsx b/client/views/admin/apps/AppRow.tsx index 01104d18689d9..762257cb5c867 100644 --- a/client/views/admin/apps/AppRow.tsx +++ b/client/views/admin/apps/AppRow.tsx @@ -93,7 +93,6 @@ const AppRow: FC = ({ medium, ...props }) => { {current} ))} - ; )} diff --git a/client/views/admin/apps/AppsPage.js b/client/views/admin/apps/AppsPage.js deleted file mode 100644 index 2c4c47b7cdc93..0000000000000 --- a/client/views/admin/apps/AppsPage.js +++ /dev/null @@ -1,46 +0,0 @@ -import { Button, ButtonGroup, Icon } from '@rocket.chat/fuselage'; -import React from 'react'; - -import Page from '../../../components/Page'; -import { useRoute } from '../../../contexts/RouterContext'; -import { useSetting } from '../../../contexts/SettingsContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; -import AppsTable from './AppsTable'; - -function AppsPage() { - const t = useTranslation(); - - const isDevelopmentMode = useSetting('Apps_Framework_Development_Mode'); - const marketplaceRoute = useRoute('admin-marketplace'); - const appsRoute = useRoute('admin-apps'); - - const handleUploadButtonClick = () => { - appsRoute.push({ context: 'install' }); - }; - - const handleViewMarketplaceButtonClick = () => { - marketplaceRoute.push(); - }; - - return ( - - - - {isDevelopmentMode && ( - - )} - - - - - - - - ); -} - -export default AppsPage; diff --git a/client/views/admin/apps/AppsPage.tsx b/client/views/admin/apps/AppsPage.tsx new file mode 100644 index 0000000000000..ab42a902de2c6 --- /dev/null +++ b/client/views/admin/apps/AppsPage.tsx @@ -0,0 +1,84 @@ +import { Button, ButtonGroup, Icon, Skeleton, Tabs } from '@rocket.chat/fuselage'; +import React, { useEffect, useState, ReactElement } from 'react'; + +import Page from '../../../components/Page'; +import { useRoute } from '../../../contexts/RouterContext'; +import { useMethod } from '../../../contexts/ServerContext'; +import { useSetting } from '../../../contexts/SettingsContext'; +import { useTranslation } from '../../../contexts/TranslationContext'; +import AppsTable from './AppsTable'; +import MarketplaceTable from './MarketplaceTable'; + +type AppsPageProps = { + isMarketPlace: boolean; + context: string; +}; + +const AppsPage = ({ isMarketPlace, context }: AppsPageProps): ReactElement => { + const t = useTranslation(); + + const isDevelopmentMode = useSetting('Apps_Framework_Development_Mode'); + const marketplaceRoute = useRoute('admin-marketplace'); + const appsRoute = useRoute('admin-apps'); + const cloudRoute = useRoute('cloud'); + const checkUserLoggedIn = useMethod('cloud:checkUserLoggedIn'); + + const [isLoggedInCloud, setIsLoggedInCloud] = useState(); + + useEffect(() => { + const initialize = async (): Promise => { + setIsLoggedInCloud(await checkUserLoggedIn()); + }; + initialize(); + }, [checkUserLoggedIn]); + + const handleLoginButtonClick = (): void => { + cloudRoute.push(); + }; + + const handleUploadButtonClick = (): void => { + appsRoute.push({ context: 'install' }); + }; + + return ( + + + + {isMarketPlace && !isLoggedInCloud && ( + + )} + {Boolean(isDevelopmentMode) && ( + + )} + + + + marketplaceRoute.push({ context: '' })} + selected={isMarketPlace} + > + {t('Marketplace')} + + marketplaceRoute.push({ context: 'installed' })} + selected={context === 'installed'} + > + {t('Installed')} + + + {context === 'installed' ? : } + + ); +}; + +export default AppsPage; diff --git a/client/views/admin/apps/AppsRoute.js b/client/views/admin/apps/AppsRoute.js index 9c0299c653ddc..6cdeb5f710cef 100644 --- a/client/views/admin/apps/AppsRoute.js +++ b/client/views/admin/apps/AppsRoute.js @@ -3,16 +3,15 @@ import React, { useState, useEffect } from 'react'; import NotAuthorizedPage from '../../../components/NotAuthorizedPage'; import PageSkeleton from '../../../components/PageSkeleton'; import { usePermission } from '../../../contexts/AuthorizationContext'; -import { useRouteParameter, useRoute, useCurrentRoute } from '../../../contexts/RouterContext'; +import { useRouteParameter, useRoute } from '../../../contexts/RouterContext'; import { useMethod } from '../../../contexts/ServerContext'; import AppDetailsPage from './AppDetailsPage'; import AppInstallPage from './AppInstallPage'; import AppLogsPage from './AppLogsPage'; import AppsPage from './AppsPage'; import AppsProvider from './AppsProvider'; -import MarketplacePage from './MarketplacePage'; -function AppsRoute() { +const AppsRoute = () => { const [isLoading, setLoading] = useState(true); const canViewAppsAndMarketplace = usePermission('manage-apps'); const isAppsEngineEnabled = useMethod('apps/is-enabled'); @@ -45,11 +44,10 @@ function AppsRoute() { }; }, [canViewAppsAndMarketplace, isAppsEngineEnabled, appsWhatIsItRoute]); - const [currentRouteName] = useCurrentRoute(); + const context = useRouteParameter('context'); - const isMarketPlace = currentRouteName === 'admin-marketplace'; + const isMarketPlace = !context; - const context = useRouteParameter('context'); const id = useRouteParameter('id'); const version = useRouteParameter('version'); @@ -63,13 +61,14 @@ function AppsRoute() { return ( - {(!context && isMarketPlace && ) || - (!context && !isMarketPlace && ) || + {((!context || context === 'installed') && ( + + )) || (context === 'details' && ) || (context === 'logs' && ) || (context === 'install' && )} ); -} +}; export default AppsRoute; diff --git a/client/views/admin/routes.js b/client/views/admin/routes.js index 815fed54be02c..4d86946c76033 100644 --- a/client/views/admin/routes.js +++ b/client/views/admin/routes.js @@ -26,6 +26,11 @@ registerAdminRoute('/apps/:context?/:id?/:version?', { lazyRouteComponent: () => import('./apps/AppsRoute'), }); +registerAdminRoute('/apps/:context?/:id?/:version?', { + name: 'admin-apps', + lazyRouteComponent: () => import('./apps/AppsRoute'), +}); + registerAdminRoute('/info', { name: 'admin-info', lazyRouteComponent: () => import('./info/InformationRoute'), diff --git a/client/views/admin/sidebarItems.js b/client/views/admin/sidebarItems.js index f8f7a9d7579dc..a9531ca52e44e 100644 --- a/client/views/admin/sidebarItems.js +++ b/client/views/admin/sidebarItems.js @@ -62,16 +62,10 @@ export const { i18nLabel: 'Federation Dashboard', permissionGranted: () => hasRole(Meteor.userId(), 'admin'), }, - { - icon: 'cube', - href: 'admin-apps', - i18nLabel: 'Apps', - permissionGranted: () => hasPermission(['manage-apps']), - }, { icon: 'cube', href: 'admin-marketplace', - i18nLabel: 'Marketplace', + i18nLabel: 'Apps', permissionGranted: () => hasPermission(['manage-apps']), }, { diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index c2e32ce53de47..f378fa709eb42 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -2801,6 +2801,7 @@ "Markdown_Parser": "Markdown Parser", "Markdown_SupportSchemesForLink": "Markdown Support Schemes for Link", "Markdown_SupportSchemesForLink_Description": "Comma-separated list of allowed schemes", + "Marketplace": "Marketplace", "Marketplace_view_marketplace": "View Marketplace", "MAU_value": "MAU __value__", "Max_length_is": "Max length is %s", From c954c5ffece8111bf924bec91e6666b25a66749f Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Fri, 29 Oct 2021 14:40:04 -0300 Subject: [PATCH 039/137] [FIX] Notifications are not being filtered (#23487) * Add migration to update push notification setting's value * Update mobileNotifications preference to pushNotifications --- app/api/server/v1/users.js | 2 +- app/utils/lib/getDefaultSubscriptionPref.js | 6 ++--- .../methods/saveUserPreferences.ts | 2 +- .../PreferencesNotificationsSection.js | 10 +++---- server/methods/saveUserPreferences.js | 10 +++---- server/startup/migrations/index.ts | 1 + server/startup/migrations/v243.ts | 26 +++++++++++++++++++ tests/data/user.js | 2 +- 8 files changed, 43 insertions(+), 16 deletions(-) create mode 100644 server/startup/migrations/v243.ts diff --git a/app/api/server/v1/users.js b/app/api/server/v1/users.js index 714fc5e9265ff..1f3f22e38e6ce 100644 --- a/app/api/server/v1/users.js +++ b/app/api/server/v1/users.js @@ -593,7 +593,7 @@ API.v1.addRoute('users.setPreferences', { authRequired: true }, { unreadAlert: Match.Maybe(Boolean), notificationsSoundVolume: Match.Maybe(Number), desktopNotifications: Match.Maybe(String), - mobileNotifications: Match.Maybe(String), + pushNotifications: Match.Maybe(String), enableAutoAway: Match.Maybe(Boolean), highlights: Match.Maybe(Array), desktopNotificationRequireInteraction: Match.Maybe(Boolean), diff --git a/app/utils/lib/getDefaultSubscriptionPref.js b/app/utils/lib/getDefaultSubscriptionPref.js index 294a7d50a734c..0200cb128e334 100644 --- a/app/utils/lib/getDefaultSubscriptionPref.js +++ b/app/utils/lib/getDefaultSubscriptionPref.js @@ -3,7 +3,7 @@ export const getDefaultSubscriptionPref = (userPref) => { const { desktopNotifications, - mobileNotifications, + pushNotifications, emailNotificationMode, highlights, } = (userPref.settings && userPref.settings.preferences) || {}; @@ -17,8 +17,8 @@ export const getDefaultSubscriptionPref = (userPref) => { subscription.desktopPrefOrigin = 'user'; } - if (mobileNotifications && mobileNotifications !== 'default') { - subscription.mobilePushNotifications = mobileNotifications; + if (pushNotifications && pushNotifications !== 'default') { + subscription.mobilePushNotifications = pushNotifications; subscription.mobilePrefOrigin = 'user'; } diff --git a/client/contexts/ServerContext/methods/saveUserPreferences.ts b/client/contexts/ServerContext/methods/saveUserPreferences.ts index 1f5f67100c97f..7e6b20ad0bf64 100644 --- a/client/contexts/ServerContext/methods/saveUserPreferences.ts +++ b/client/contexts/ServerContext/methods/saveUserPreferences.ts @@ -12,7 +12,7 @@ type UserPreferences = { unreadAlert: boolean; notificationsSoundVolume: number; desktopNotifications: string; - mobileNotifications: string; + pushNotifications: string; enableAutoAway: boolean; highlights: string[]; messageViewMode: number; diff --git a/client/views/account/preferences/PreferencesNotificationsSection.js b/client/views/account/preferences/PreferencesNotificationsSection.js index 615b063d8e3ee..6419f8df5ffe8 100644 --- a/client/views/account/preferences/PreferencesNotificationsSection.js +++ b/client/views/account/preferences/PreferencesNotificationsSection.js @@ -50,7 +50,7 @@ const PreferencesNotificationsSection = ({ onChange, commitRef, ...props }) => { { desktopNotificationRequireInteraction: userDesktopNotificationRequireInteraction, desktopNotifications: userDesktopNotifications, - mobileNotifications: userMobileNotifications, + pushNotifications: userMobileNotifications, emailNotificationMode: userEmailNotificationMode, }, onChange, @@ -59,14 +59,14 @@ const PreferencesNotificationsSection = ({ onChange, commitRef, ...props }) => { const { desktopNotificationRequireInteraction, desktopNotifications, - mobileNotifications, + pushNotifications, emailNotificationMode, } = values; const { handleDesktopNotificationRequireInteraction, handleDesktopNotifications, - handleMobileNotifications, + handlePushNotifications, handleEmailNotificationMode, } = handlers; @@ -171,8 +171,8 @@ const PreferencesNotificationsSection = ({ onChange, commitRef, ...props }) => { {t('Notification_Push_Default_For')} diff --git a/server/methods/saveUserPreferences.js b/server/methods/saveUserPreferences.js index bd062f6064b86..ffed9b7adc6de 100644 --- a/server/methods/saveUserPreferences.js +++ b/server/methods/saveUserPreferences.js @@ -19,7 +19,7 @@ Meteor.methods({ unreadAlert: Match.Optional(Boolean), notificationsSoundVolume: Match.Optional(Number), desktopNotifications: Match.Optional(String), - mobileNotifications: Match.Optional(String), + pushNotifications: Match.Optional(String), enableAutoAway: Match.Optional(Boolean), highlights: Match.Optional([String]), messageViewMode: Match.Optional(Number), @@ -46,7 +46,7 @@ Meteor.methods({ const { desktopNotifications: oldDesktopNotifications, - mobileNotifications: oldMobileNotifications, + pushNotifications: oldMobileNotifications, emailNotificationMode: oldEmailNotifications, } = (user.settings && user.settings.preferences) || {}; @@ -81,11 +81,11 @@ Meteor.methods({ } } - if (settings.mobileNotifications && oldMobileNotifications !== settings.mobileNotifications) { - if (settings.mobileNotifications === 'default') { + if (settings.pushNotifications && oldMobileNotifications !== settings.pushNotifications) { + if (settings.pushNotifications === 'default') { Subscriptions.clearNotificationUserPreferences(user._id, 'mobilePushNotifications', 'mobilePrefOrigin'); } else { - Subscriptions.updateNotificationUserPreferences(user._id, settings.mobileNotifications, 'mobilePushNotifications', 'mobilePrefOrigin'); + Subscriptions.updateNotificationUserPreferences(user._id, settings.pushNotifications, 'mobilePushNotifications', 'mobilePrefOrigin'); } } diff --git a/server/startup/migrations/index.ts b/server/startup/migrations/index.ts index 132a7ad3862d4..37f25773c4e2b 100644 --- a/server/startup/migrations/index.ts +++ b/server/startup/migrations/index.ts @@ -66,4 +66,5 @@ import './v239'; import './v240'; import './v241'; import './v242'; +import './v243'; import './xrun'; diff --git a/server/startup/migrations/v243.ts b/server/startup/migrations/v243.ts new file mode 100644 index 0000000000000..89d3f9f03e707 --- /dev/null +++ b/server/startup/migrations/v243.ts @@ -0,0 +1,26 @@ +import { addMigration } from '../../lib/migrations'; +import { Settings, Users } from '../../../app/models/server'; + +addMigration({ + version: 243, + up() { + const mobileNotificationsSetting = Settings.findOneById('Accounts_Default_User_Preferences_mobileNotifications'); + + Settings.removeById('Accounts_Default_User_Preferences_mobileNotifications'); + if (mobileNotificationsSetting && mobileNotificationsSetting.value) { + Settings.upsert({ + _id: 'Accounts_Default_User_Preferences_pushNotifications', + }, { + $set: { + value: mobileNotificationsSetting.value, + }, + }); + } + + Users.update( + { 'settings.preferences.mobileNotifications': { $exists: 1 } }, + { $rename: { 'settings.preferences.mobileNotifications': 'settings.preferences.pushNotifications' } }, + { multi: true }, + ); + }, +}); diff --git a/tests/data/user.js b/tests/data/user.js index 1a729c10c1d55..7ba380fcd8c41 100644 --- a/tests/data/user.js +++ b/tests/data/user.js @@ -20,7 +20,7 @@ export const preferences = { unreadAlert: true, notificationsSoundVolume: 100, desktopNotifications: 'default', - mobileNotifications: 'default', + pushNotifications: 'default', enableAutoAway: true, highlights: [], desktopNotificationRequireInteraction: false, From ba69feecec6ebd91df6151894147af1118c69f26 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Mon, 8 Nov 2021 22:02:35 -0300 Subject: [PATCH 059/137] Bump version to 4.1.2 --- .docker/Dockerfile.rhel | 2 +- .github/history.json | 69 +- .snapcraft/resources/prepareRocketChat | 2 +- .snapcraft/snap/snapcraft.yaml | 2 +- HISTORY.md | 2125 +++++++++++------------- app/utils/rocketchat.info | 2 +- package-lock.json | 2 +- package.json | 2 +- 8 files changed, 1079 insertions(+), 1127 deletions(-) diff --git a/.docker/Dockerfile.rhel b/.docker/Dockerfile.rhel index f91e8ee83d61c..f8607432f2fd2 100644 --- a/.docker/Dockerfile.rhel +++ b/.docker/Dockerfile.rhel @@ -1,6 +1,6 @@ FROM registry.access.redhat.com/ubi8/nodejs-12 -ENV RC_VERSION 4.1.1 +ENV RC_VERSION 4.1.2 MAINTAINER buildmaster@rocket.chat diff --git a/.github/history.json b/.github/history.json index 206a280bd24e7..5f3b0c6a39508 100644 --- a/.github/history.json +++ b/.github/history.json @@ -67075,6 +67075,73 @@ ] } ] + }, + "3.18.3": { + "node_version": "12.22.1", + "npm_version": "6.14.1", + "apps_engine_version": "1.27.1", + "mongo_versions": [ + "3.4", + "3.6", + "4.0", + "4.2" + ], + "pull_requests": [] + }, + "4.0.6": { + "node_version": "12.22.1", + "npm_version": "6.14.1", + "apps_engine_version": "1.28.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [] + }, + "4.1.2": { + "node_version": "12.22.1", + "npm_version": "6.14.1", + "apps_engine_version": "1.28.1", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "23487", + "title": "[FIX] Notifications are not being filtered", + "userLogin": "matheusbsilva137", + "description": "- Add a migration to update the `Accounts_Default_User_Preferences_pushNotifications` setting's value to the `Accounts_Default_User_Preferences_mobileNotifications` setting's value;\r\n - Remove the `Accounts_Default_User_Preferences_mobileNotifications` setting (replaced by `Accounts_Default_User_Preferences_pushNotifications`);\r\n - Rename 'mobileNotifications' user's preference to 'pushNotifications'.", + "milestone": "4.1.2", + "contributors": [ + "matheusbsilva137" + ] + }, + { + "pr": "23661", + "title": "[FIX] Performance issues when running Omnichannel job queue dispatcher", + "userLogin": "renatobecker", + "milestone": "4.1.2", + "contributors": [ + "renatobecker" + ] + }, + { + "pr": "23587", + "title": "[FIX] Omnichannel status being changed on page refresh", + "userLogin": "KevLehman", + "milestone": "4.1.2", + "contributors": [ + "KevLehman" + ] + } + ] } } -} +} \ No newline at end of file diff --git a/.snapcraft/resources/prepareRocketChat b/.snapcraft/resources/prepareRocketChat index 8326ee76187ec..a5968214af146 100755 --- a/.snapcraft/resources/prepareRocketChat +++ b/.snapcraft/resources/prepareRocketChat @@ -1,6 +1,6 @@ #!/bin/bash -curl -SLf "https://releases.rocket.chat/4.1.1/download/" -o rocket.chat.tgz +curl -SLf "https://releases.rocket.chat/4.1.2/download/" -o rocket.chat.tgz tar xf rocket.chat.tgz --strip 1 diff --git a/.snapcraft/snap/snapcraft.yaml b/.snapcraft/snap/snapcraft.yaml index d9d599fa7ca0f..4bb19f3461ddc 100644 --- a/.snapcraft/snap/snapcraft.yaml +++ b/.snapcraft/snap/snapcraft.yaml @@ -7,7 +7,7 @@ # 5. `snapcraft snap` name: rocketchat-server -version: 4.1.1 +version: 4.1.2 summary: Rocket.Chat server description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ confinement: strict diff --git a/HISTORY.md b/HISTORY.md index 68f00f36e9ae7..7ee96dd1faa2f 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,34 @@ +# 4.1.2 +`2021-11-08 · 3 🐛 · 3 👩‍💻👨‍💻` + +### Engine versions +- Node: `12.22.1` +- NPM: `6.14.1` +- MongoDB: `3.6, 4.0, 4.2, 4.4, 5.0` +- Apps-Engine: `1.28.1` + +### 🐛 Bug fixes + + +- Notifications are not being filtered ([#23487](https://github.com/RocketChat/Rocket.Chat/pull/23487)) + + - Add a migration to update the `Accounts_Default_User_Preferences_pushNotifications` setting's value to the `Accounts_Default_User_Preferences_mobileNotifications` setting's value; + - Remove the `Accounts_Default_User_Preferences_mobileNotifications` setting (replaced by `Accounts_Default_User_Preferences_pushNotifications`); + - Rename 'mobileNotifications' user's preference to 'pushNotifications'. + +- Omnichannel status being changed on page refresh ([#23587](https://github.com/RocketChat/Rocket.Chat/pull/23587)) + +- Performance issues when running Omnichannel job queue dispatcher ([#23661](https://github.com/RocketChat/Rocket.Chat/pull/23661)) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@KevLehman](https://github.com/KevLehman) +- [@matheusbsilva137](https://github.com/matheusbsilva137) +- [@renatobecker](https://github.com/renatobecker) + # 4.1.1 -`2021-11-05 · 4 🐛 · 1 🔍 · 4 👩‍💻👨‍💻` +`2021-11-05 · 4 🐛 · 3 👩‍💻👨‍💻` ### Engine versions - Node: `12.22.1` @@ -21,7 +49,6 @@ - Security Hotfix (https://docs.rocket.chat/guides/security/security-updates) - ### 👩‍💻👨‍💻 Core Team 🤓 - [@d-gubert](https://github.com/d-gubert) @@ -53,19 +80,19 @@ - Make Livechat Instructions setting multi-line ([#23515](https://github.com/RocketChat/Rocket.Chat/pull/23515)) - Since now we're supporting markdown text on this field (via this PR - https://github.com/RocketChat/Rocket.Chat.Livechat/pull/648), it would be nice to make this setting multiline so users can have more space to edit the text + Since now we're supporting markdown text on this field (via this PR - https://github.com/RocketChat/Rocket.Chat.Livechat/pull/648), it would be nice to make this setting multiline so users can have more space to edit the text ![image](https://user-images.githubusercontent.com/34130764/138146712-13e4968b-5312-4d53-b44c-b5699c5e49c1.png) - optimized groups.listAll response time ([#22941](https://github.com/RocketChat/Rocket.Chat/pull/22941)) - groups.listAll endpoint was having performance issues, specially when the total number of groups was high. This happened because the endpoint was loading all objects in memory then using splice to paginate, instead of paginating beforehand. - - Considering 70k groups, this was the performance improvement: - - before - ![image](https://user-images.githubusercontent.com/28611993/129601314-bdf89337-79fa-4446-9f44-95264af4adb3.png) - - after + groups.listAll endpoint was having performance issues, specially when the total number of groups was high. This happened because the endpoint was loading all objects in memory then using splice to paginate, instead of paginating beforehand. + + Considering 70k groups, this was the performance improvement: + + before + ![image](https://user-images.githubusercontent.com/28611993/129601314-bdf89337-79fa-4446-9f44-95264af4adb3.png) + + after ![image](https://user-images.githubusercontent.com/28611993/129601358-5872e166-f923-4c1c-b21d-eb9507365ecf.png) ### 🐛 Bug fixes @@ -73,8 +100,7 @@ - **APPS:** Communication problem when updating and uninstalling apps in cluster ([#23418](https://github.com/RocketChat/Rocket.Chat/pull/23418)) - - Make the hook responsible for receiving app update events inside a cluster fetch the app's package (zip file) in the correct place. - + - Make the hook responsible for receiving app update events inside a cluster fetch the app's package (zip file) in the correct place. - Also shows a warning message on uninstalls inside a cluster. As there are many servers writing to the same place, some race conditions may occur. This prevents problems related to terminating the process in the middle due to errors being thrown and leaving the server in a faulty state. - **ENTERPRISE:** Omnichannel agent is not leaving the room when a forwarded chat is queued ([#23404](https://github.com/RocketChat/Rocket.Chat/pull/23404)) @@ -101,10 +127,10 @@ - Markdown quote message style ([#23462](https://github.com/RocketChat/Rocket.Chat/pull/23462)) - Before: - ![image](https://user-images.githubusercontent.com/17487063/137496669-3abecab4-cf90-45cb-8b1b-d9411a5682dd.png) - - After: + Before: + ![image](https://user-images.githubusercontent.com/17487063/137496669-3abecab4-cf90-45cb-8b1b-d9411a5682dd.png) + + After: ![image](https://user-images.githubusercontent.com/17487063/137496905-fd727f90-f707-4ec6-8139-ba2eb1a2146e.png) - MONGO_OPTIONS being ignored for oplog connection ([#23314](https://github.com/RocketChat/Rocket.Chat/pull/23314) by [@cuonghuunguyen](https://github.com/cuonghuunguyen)) @@ -123,8 +149,8 @@ - Read only description in team creation ([#23213](https://github.com/RocketChat/Rocket.Chat/pull/23213)) - ![image](https://user-images.githubusercontent.com/27704687/133608433-8ca788a3-71a8-4d40-8c40-8156ab03c606.png) - + ![image](https://user-images.githubusercontent.com/27704687/133608433-8ca788a3-71a8-4d40-8c40-8156ab03c606.png) + ![image](https://user-images.githubusercontent.com/27704687/133608400-4cdc7a67-95e5-46c6-8c65-29ab107cd314.png) - resumeToken not working ([#23379](https://github.com/RocketChat/Rocket.Chat/pull/23379)) @@ -133,8 +159,7 @@ - SAML Users' roles being reset to default on login ([#23411](https://github.com/RocketChat/Rocket.Chat/pull/23411)) - - Remove `roles` field update on `insertOrUpdateSAMLUser` function; - + - Remove `roles` field update on `insertOrUpdateSAMLUser` function; - Add SAML `syncRoles` event; - Server crashing when Routing method is not available at start ([#23473](https://github.com/RocketChat/Rocket.Chat/pull/23473)) @@ -215,14 +240,14 @@ - Chore: Startup Time ([#23210](https://github.com/RocketChat/Rocket.Chat/pull/23210)) - The settings logic has been improved as a whole. - - All the logic to get the data from the env var was confusing. - - Setting default values was tricky to understand. - - Every time the server booted, all settings were updated and callbacks were called 2x or more (horrible for environments with multiple instances and generating a turbulent startup). - + The settings logic has been improved as a whole. + + All the logic to get the data from the env var was confusing. + + Setting default values was tricky to understand. + + Every time the server booted, all settings were updated and callbacks were called 2x or more (horrible for environments with multiple instances and generating a turbulent startup). + `Settings.get(......, callback);` was deprecated. We now have better methods for each case. - Chore: Update Apps-Engine version ([#23375](https://github.com/RocketChat/Rocket.Chat/pull/23375)) @@ -245,10 +270,10 @@ - Regression: Mail body contains `undefined` text ([#23552](https://github.com/RocketChat/Rocket.Chat/pull/23552)) - ### Before - ![image](https://user-images.githubusercontent.com/2263066/138733018-10449892-5c2d-46fb-9355-00e98e0d6c9f.png) - - ### After + ### Before + ![image](https://user-images.githubusercontent.com/2263066/138733018-10449892-5c2d-46fb-9355-00e98e0d6c9f.png) + + ### After ![image](https://user-images.githubusercontent.com/2263066/138733074-a1b88a77-bf64-41c3-a6c3-ac9e1cb63de1.png) - Regression: Prevent settings from getting updated ([#23556](https://github.com/RocketChat/Rocket.Chat/pull/23556)) @@ -339,8 +364,7 @@ - SAML Users' roles being reset to default on login ([#23411](https://github.com/RocketChat/Rocket.Chat/pull/23411)) - - Remove `roles` field update on `insertOrUpdateSAMLUser` function; - + - Remove `roles` field update on `insertOrUpdateSAMLUser` function; - Add SAML `syncRoles` event;
@@ -372,8 +396,7 @@ - **APPS:** Communication problem when updating and uninstalling apps in cluster ([#23418](https://github.com/RocketChat/Rocket.Chat/pull/23418)) - - Make the hook responsible for receiving app update events inside a cluster fetch the app's package (zip file) in the correct place. - + - Make the hook responsible for receiving app update events inside a cluster fetch the app's package (zip file) in the correct place. - Also shows a warning message on uninstalls inside a cluster. As there are many servers writing to the same place, some race conditions may occur. This prevents problems related to terminating the process in the middle due to errors being thrown and leaving the server in a faulty state. - Server crashing when Routing method is not available at start ([#23473](https://github.com/RocketChat/Rocket.Chat/pull/23473)) @@ -500,20 +523,18 @@ - **ENTERPRISE:** "Download CSV" button doesn't work in the Engagement Dashboard's Active Users section ([#23013](https://github.com/RocketChat/Rocket.Chat/pull/23013)) - - Fix "Download CSV" button in the Engagement Dashboard's Active Users section; - - - Add column headers to the CSV file downloaded from the Engagement Dashboard's Active Users section; - + - Fix "Download CSV" button in the Engagement Dashboard's Active Users section; + - Add column headers to the CSV file downloaded from the Engagement Dashboard's Active Users section; - Split the data in multiple CSV files. - **ENTERPRISE:** CSV file downloaded in the Engagement Dashboard's New Users section contains undefined data ([#23014](https://github.com/RocketChat/Rocket.Chat/pull/23014)) - - Fix CSV file downloaded in the Engagement Dashboard's New Users section; + - Fix CSV file downloaded in the Engagement Dashboard's New Users section; - Add column headers to the CSV file downloaded from the Engagement Dashboard's New Users section. - **ENTERPRISE:** Missing headers in CSV files downloaded from the Engagement Dashboard ([#23223](https://github.com/RocketChat/Rocket.Chat/pull/23223)) - - Add headers to all CSV files downloaded from the "Messages" and "Channels" tabs from the Engagement Dashboard; + - Add headers to all CSV files downloaded from the "Messages" and "Channels" tabs from the Engagement Dashboard; - Add headers to the CSV file downloaded from the "Users by time of day" section (in the "Users" tab). - LDAP Refactoring ([#23171](https://github.com/RocketChat/Rocket.Chat/pull/23171)) @@ -528,24 +549,17 @@ - Remove deprecated endpoints ([#23162](https://github.com/RocketChat/Rocket.Chat/pull/23162)) - The following REST endpoints were removed: - - - - `/api/v1/emoji-custom` - - - `/api/v1/info` - - - `/api/v1/permissions` - - - `/api/v1/permissions.list` - - The following Real time API Methods were removed: - - - - `getFullUserData` - - - `getServerInfo` - + The following REST endpoints were removed: + + - `/api/v1/emoji-custom` + - `/api/v1/info` + - `/api/v1/permissions` + - `/api/v1/permissions.list` + + The following Real time API Methods were removed: + + - `getFullUserData` + - `getServerInfo` - `livechat:saveOfficeHours` - Remove Google Vision features ([#23160](https://github.com/RocketChat/Rocket.Chat/pull/23160)) @@ -554,8 +568,8 @@ - Remove old migrations up to version 2.4.14 ([#23277](https://github.com/RocketChat/Rocket.Chat/pull/23277)) - To update to version 4.0.0 you'll need to be running at least version 3.0.0, otherwise you might loose some database migrations which might have unexpected effects. - + To update to version 4.0.0 you'll need to be running at least version 3.0.0, otherwise you might loose some database migrations which might have unexpected effects. + This aims to clean up the code, since upgrades jumping 2 major versions are too risky and hard to maintain, we'll keep only migration from that last major (in this case 3.x). - Remove patch info from endpoint /api/info for non-logged in users ([#16050](https://github.com/RocketChat/Rocket.Chat/pull/16050) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) @@ -564,18 +578,18 @@ - Stop sending audio notifications via stream ([#23108](https://github.com/RocketChat/Rocket.Chat/pull/23108)) - Remove audio preferences and make them tied to desktop notification preferences. - + Remove audio preferences and make them tied to desktop notification preferences. + TL;DR: new message sounds will play only if you receive a desktop notification. you'll still be able to chose to not play any sound though - Webhook will fail if user is not part of the channel ([#23310](https://github.com/RocketChat/Rocket.Chat/pull/23310)) - Remove deprecated behavior added by https://github.com/RocketChat/Rocket.Chat/pull/18024 that accepts webhook integrations sending messages even if the user is not part of the channel. - - Starting from 4.0.0 the webhook request will fail with `error-not-allowed` error: - - ``` - {"success":false,"error":"error-not-allowed"} + Remove deprecated behavior added by https://github.com/RocketChat/Rocket.Chat/pull/18024 that accepts webhook integrations sending messages even if the user is not part of the channel. + + Starting from 4.0.0 the webhook request will fail with `error-not-allowed` error: + + ``` + {"success":false,"error":"error-not-allowed"} ``` ### 🎉 New features @@ -593,26 +607,23 @@ - Seats Cap ([#23017](https://github.com/RocketChat/Rocket.Chat/pull/23017) by [@g-thome](https://github.com/g-thome)) - - Adding New Members - - Awareness of seats usage while adding new members - - Seats Cap about to be reached - - Seats Cap reached - - Request more seats - - - Warning Admins - - System telling admins max seats are about to exceed - - System telling admins max seats were exceed - - Metric on Info Page - - Request more seats - - - Warning Members - - Invite link - - Block creating new invite links - - Block existing invite links (feedback on register process) - - Register to Workspaces - - - Emails - - System telling admins max seats are about to exceed + - Adding New Members + - Awareness of seats usage while adding new members + - Seats Cap about to be reached + - Seats Cap reached + - Request more seats + - Warning Admins + - System telling admins max seats are about to exceed + - System telling admins max seats were exceed + - Metric on Info Page + - Request more seats + - Warning Members + - Invite link + - Block creating new invite links + - Block existing invite links (feedback on register process) + - Register to Workspaces + - Emails + - System telling admins max seats are about to exceed - System telling admins max seats were exceed ### 🚀 Improvements @@ -620,10 +631,10 @@ - **APPS:** New storage strategy for Apps-Engine file packages ([#22657](https://github.com/RocketChat/Rocket.Chat/pull/22657)) - This is an enabler for our initiative to support NPM packages in the Apps-Engine. - - Currently, the packages (zip files) for Rocket.Chat Apps are stored as a base64 encoded string in a document in the database, which constrains us due to the size limit of a document in MongoDB (16Mb). - + This is an enabler for our initiative to support NPM packages in the Apps-Engine. + + Currently, the packages (zip files) for Rocket.Chat Apps are stored as a base64 encoded string in a document in the database, which constrains us due to the size limit of a document in MongoDB (16Mb). + When we allow apps to include NPM packages, the size of the App package itself will be potentially _very large_ (I'm looking at you `node_modules`). Thus we'll be changing the strategy to store apps either with GridFS or the host's File System itself. - **APPS:** Return task ids when using the scheduler api ([#23023](https://github.com/RocketChat/Rocket.Chat/pull/23023)) @@ -663,9 +674,9 @@ - "Read Only" and "Allow Reacting" system messages are missing in rooms ([#23037](https://github.com/RocketChat/Rocket.Chat/pull/23037)) - - Add system message to notify changes on the **"Read Only"** setting; - - Add system message to notify changes on the **"Allow Reacting"** setting; - - Fix "Allow Reacting" setting's description (updated from "Only authorized users can write new messages" to "Only authorized users can react to messages"). + - Add system message to notify changes on the **"Read Only"** setting; + - Add system message to notify changes on the **"Allow Reacting"** setting; + - Fix "Allow Reacting" setting's description (updated from "Only authorized users can write new messages" to "Only authorized users can react to messages"). ![system-messages](https://user-images.githubusercontent.com/36537004/130883527-9eb47fcd-c8e5-41fb-af34-5d99bd0a6780.PNG) - Add check before placing chat on-hold to confirm that contact sent last message ([#23053](https://github.com/RocketChat/Rocket.Chat/pull/23053)) @@ -680,9 +691,9 @@ - Inaccurate use of 'Mobile notifications' instead of 'Push notifications' in i18n strings ([#22978](https://github.com/RocketChat/Rocket.Chat/pull/22978)) - - Fix inaccurate use of 'Mobile notifications' (which is misleading in German) by 'Push notifications'; - - Update `'Notification_Mobile_Default_For'` key to `'Notification_Push_Default_For'` (and text to 'Send Push Notifications For' for English Language); - - Update `'Accounts_Default_User_Preferences_mobileNotifications'` key to `'Accounts_Default_User_Preferences_pushNotifications'`; + - Fix inaccurate use of 'Mobile notifications' (which is misleading in German) by 'Push notifications'; + - Update `'Notification_Mobile_Default_For'` key to `'Notification_Push_Default_For'` (and text to 'Send Push Notifications For' for English Language); + - Update `'Accounts_Default_User_Preferences_mobileNotifications'` key to `'Accounts_Default_User_Preferences_pushNotifications'`; - Update `'Mobile_Notifications_Default_Alert'` key to `'Mobile_Push_Notifications_Default_Alert'`; - Logging out from other clients ([#23276](https://github.com/RocketChat/Rocket.Chat/pull/23276)) @@ -691,7 +702,7 @@ - Modals is cutting pixels of the content ([#23243](https://github.com/RocketChat/Rocket.Chat/pull/23243)) - Fuselage Dependency: [543](https://github.com/RocketChat/Rocket.Chat.Fuselage/pull/543) + Fuselage Dependency: [543](https://github.com/RocketChat/Rocket.Chat.Fuselage/pull/543) ![image](https://user-images.githubusercontent.com/27704687/134049227-3cd1deed-34ba-454f-a95e-e99b79a7a7b9.png) - Omnichannel On hold chats being forwarded to offline agents ([#23185](https://github.com/RocketChat/Rocket.Chat/pull/23185)) @@ -700,15 +711,15 @@ - Prevent users to edit an existing role when adding a new one with the same name used before. ([#22407](https://github.com/RocketChat/Rocket.Chat/pull/22407) by [@lucassartor](https://github.com/lucassartor)) - ### before - ![Peek 2021-07-13 16-31](https://user-images.githubusercontent.com/27704687/125513721-953d84f4-1c95-45ca-80e1-b00992b874f6.gif) - - ### after + ### before + ![Peek 2021-07-13 16-31](https://user-images.githubusercontent.com/27704687/125513721-953d84f4-1c95-45ca-80e1-b00992b874f6.gif) + + ### after ![Peek 2021-07-13 16-34](https://user-images.githubusercontent.com/27704687/125514098-91ee8014-51e5-4c62-9027-5538acf57d08.gif) - Remove doubled "Canned Responses" strings ([#23056](https://github.com/RocketChat/Rocket.Chat/pull/23056)) - - Remove doubled canned response setting introduced in #22703 (by setting id change); + - Remove doubled canned response setting introduced in #22703 (by setting id change); - Update "Canned Responses" keys to "Canned_Responses". - Remove margin from quote inside quote ([#21779](https://github.com/RocketChat/Rocket.Chat/pull/21779)) @@ -719,21 +730,16 @@ - Sidebar not closing when clicking in Home or Directory on mobile view ([#23218](https://github.com/RocketChat/Rocket.Chat/pull/23218)) - ### Additional fixed - - - Merge Burger menu components into a single component - - - Show a badge with no-read messages in the Burger Button: - ![image](https://user-images.githubusercontent.com/27704687/133679378-20fea2c0-4ac1-4b4e-886e-45154cc6afea.png) - + ### Additional fixed + - Merge Burger menu components into a single component + - Show a badge with no-read messages in the Burger Button: + ![image](https://user-images.githubusercontent.com/27704687/133679378-20fea2c0-4ac1-4b4e-886e-45154cc6afea.png) - remove useSidebarClose hook - Stop queue when Omnichannel is disabled or the routing method does not support it ([#23261](https://github.com/RocketChat/Rocket.Chat/pull/23261)) - - Add missing key logs - - - Stop queue (and logs) when livechat is disabled or when routing method does not support queue - + - Add missing key logs + - Stop queue (and logs) when livechat is disabled or when routing method does not support queue - Stop ignoring offline bot agents from delegation (previously, if a bot was offline, even with "Assign new conversations to bot agent" enabled, bot will be ignored and chat will be left in limbo (since bot was assigned, but offline). - Toolbox click not working on Safari(iOS) ([#23244](https://github.com/RocketChat/Rocket.Chat/pull/23244)) @@ -840,17 +846,17 @@ - Regression: Blank screen in Jitsi video calls ([#23322](https://github.com/RocketChat/Rocket.Chat/pull/23322)) - - Fix Jitsi calls being disposed even when "Open in new window" setting is disabled; + - Fix Jitsi calls being disposed even when "Open in new window" setting is disabled; - Fix misspelling on `CallJitsWithData.js` file name. - Regression: Create new loggers based on server log level ([#23297](https://github.com/RocketChat/Rocket.Chat/pull/23297)) - Regression: Fix app storage migration ([#23286](https://github.com/RocketChat/Rocket.Chat/pull/23286)) - The previous version of this migration didn't take into consideration apps that were installed prior to [Rocket.Chat@3.8.0](https://github.com/RocketChat/Rocket.Chat/releases/tag/3.8.0), which [removed the typescript compiler from the server](https://github.com/RocketChat/Rocket.Chat/pull/18687) and into the CLI. As a result, the zip files inside each installed app's document in the database had typescript files in them instead of the now required javascript files. - - As the new strategy of source code storage for apps changes the way the app is loaded, those zip files containing the source code are read everytime the app is started (or [in this particular case, updated](https://github.com/RocketChat/Rocket.Chat/pull/23286/files#diff-caf9f7a22478639e58d6514be039140a42ce1ab2d999c3efe5678c38ee36d0ccR43)), and as the zips' contents were wrong, the operation was failing. - + The previous version of this migration didn't take into consideration apps that were installed prior to [Rocket.Chat@3.8.0](https://github.com/RocketChat/Rocket.Chat/releases/tag/3.8.0), which [removed the typescript compiler from the server](https://github.com/RocketChat/Rocket.Chat/pull/18687) and into the CLI. As a result, the zip files inside each installed app's document in the database had typescript files in them instead of the now required javascript files. + + As the new strategy of source code storage for apps changes the way the app is loaded, those zip files containing the source code are read everytime the app is started (or [in this particular case, updated](https://github.com/RocketChat/Rocket.Chat/pull/23286/files#diff-caf9f7a22478639e58d6514be039140a42ce1ab2d999c3efe5678c38ee36d0ccR43)), and as the zips' contents were wrong, the operation was failing. + The fix extract the data from old apps and creates new zip files with the compiled `js` already present. - Regression: Fix Bugsnag not started error ([#23308](https://github.com/RocketChat/Rocket.Chat/pull/23308)) @@ -1027,10 +1033,8 @@ - **ENTERPRISE:** Maximum waiting time for chats in Omnichannel queue ([#22955](https://github.com/RocketChat/Rocket.Chat/pull/22955)) - - Add new settings to support closing chats that have been too long on waiting queue - - - Moved old settings to new "Queue Management" section - + - Add new settings to support closing chats that have been too long on waiting queue + - Moved old settings to new "Queue Management" section - Fix issue when closing a livechat room that caused client to not to know if room was open or not - Banner for the updates regarding authentication services ([#23055](https://github.com/RocketChat/Rocket.Chat/pull/23055) by [@g-thome](https://github.com/g-thome)) @@ -1045,10 +1049,10 @@ - Separate RegEx Settings for Channels and Usernames validation ([#21937](https://github.com/RocketChat/Rocket.Chat/pull/21937) by [@aditya-mitra](https://github.com/aditya-mitra)) - Now, there are 2 separate settings for validating names - One for **channels** and another for **usernames**. - - This change also removes the old `UTF8_Names_Validation` setting and adds 2 new settings `UTF8_User_Names_Validation` and `UTF8_Channel_Names_Validation`. - + Now, there are 2 separate settings for validating names - One for **channels** and another for **usernames**. + + This change also removes the old `UTF8_Names_Validation` setting and adds 2 new settings `UTF8_User_Names_Validation` and `UTF8_Channel_Names_Validation`. + https://user-images.githubusercontent.com/55396651/116969904-af5bb800-acd4-11eb-9fc4-dacac60cb08f.mp4 ### 🚀 Improvements @@ -1064,13 +1068,13 @@ - Rewrite File Upload Modal ([#22750](https://github.com/RocketChat/Rocket.Chat/pull/22750)) - Image preview: - ![image](https://user-images.githubusercontent.com/40830821/127223432-dccd2182-aec0-430f-8d70-03ac88aec791.png) - - Video preview: - ![image](https://user-images.githubusercontent.com/40830821/127225982-f8b21840-0d9c-4aff-a354-16188c7ed66e.png) - - Files larger than 10mb: + Image preview: + ![image](https://user-images.githubusercontent.com/40830821/127223432-dccd2182-aec0-430f-8d70-03ac88aec791.png) + + Video preview: + ![image](https://user-images.githubusercontent.com/40830821/127225982-f8b21840-0d9c-4aff-a354-16188c7ed66e.png) + + Files larger than 10mb: ![image](https://user-images.githubusercontent.com/40830821/127222611-5265040f-a06b-4ec5-b528-89b40e6a9072.png) - Types from currentChatsPage.tsx ([#22967](https://github.com/RocketChat/Rocket.Chat/pull/22967)) @@ -1086,14 +1090,14 @@ - "Users By Time of the Day" chart displays incorrect data for Local Timezone ([#22836](https://github.com/RocketChat/Rocket.Chat/pull/22836)) - - Add local timezone conversion to the "Users By Time of the Day" chart in the Engagement Dashboard; + - Add local timezone conversion to the "Users By Time of the Day" chart in the Engagement Dashboard; - Simplify date creations by using `endOf` and `startOf` methods. - Atlassian Crowd connection not working ([#22996](https://github.com/RocketChat/Rocket.Chat/pull/22996) by [@piotrkochan](https://github.com/piotrkochan)) - Audio recording doesn't stop in direct messages on channel switch ([#22880](https://github.com/RocketChat/Rocket.Chat/pull/22880)) - - Cancel audio recordings on message bar destroy event. + - Cancel audio recordings on message bar destroy event. ![test-22372](https://user-images.githubusercontent.com/36537004/128569780-d83747b0-fb9c-4dc6-9bc5-7ae573e720c8.gif) - Bad words falling if message is empty ([#22930](https://github.com/RocketChat/Rocket.Chat/pull/22930)) @@ -1118,23 +1122,21 @@ - Return transcript/dashboards based on timezone settings ([#22850](https://github.com/RocketChat/Rocket.Chat/pull/22850)) - - Added new setting to manage timezones - - - Applied new setting to omnichannel dashboards (realtime, analytics) [NOTE: Other dashboards aren't using this setting actually) - + - Added new setting to manage timezones + - Applied new setting to omnichannel dashboards (realtime, analytics) [NOTE: Other dashboards aren't using this setting actually) - Change getAnalyticsBetweenDate query to filter out system messages instead of substracting them - Tab margin style ([#22851](https://github.com/RocketChat/Rocket.Chat/pull/22851)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/128103848-2a25ba7e-0e59-4502-9bcd-2569cad9379a.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/128103848-2a25ba7e-0e59-4502-9bcd-2569cad9379a.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/128103633-ec7b93fc-4667-4dc9-bad3-bfffaff3974e.png) - Threads and discussions searches don't display proper results ([#22914](https://github.com/RocketChat/Rocket.Chat/pull/22914)) - - _Fix_ issue in discussions search (which wasn't working after a search with no results was made); + - _Fix_ issue in discussions search (which wasn't working after a search with no results was made); - _Improve_ discussions and threads searches: both searches (`chat.getDiscussions` and `chat.getThreadsList`) are now case insensitive (do NOT differ capital from lower letters) and match incomplete words or terms. - Threads List being requested more than expected ([#22879](https://github.com/RocketChat/Rocket.Chat/pull/22879)) @@ -1169,8 +1171,8 @@ - Chore: Script to start Rocket.Chat in HA mode during development ([#22398](https://github.com/RocketChat/Rocket.Chat/pull/22398)) - Sometimes we need to start Rocket.Chat in High-Availability mode (cluster) during development to test how a feature behaves or hunt down a bug. Currently, this involves a lot of commands with details that might be lost if you haven't done it in a while. - + Sometimes we need to start Rocket.Chat in High-Availability mode (cluster) during development to test how a feature behaves or hunt down a bug. Currently, this involves a lot of commands with details that might be lost if you haven't done it in a while. + This PR intends to provide a really simple way for us to start many instances of Rocket.Chat connected in a cluster. - Chore: Update Livechat widget to 1.9.4 ([#22990](https://github.com/RocketChat/Rocket.Chat/pull/22990)) @@ -1187,13 +1189,13 @@ - Regression: File upload name suggestion ([#22953](https://github.com/RocketChat/Rocket.Chat/pull/22953)) - Before: - ![image](https://user-images.githubusercontent.com/40830821/129774936-ecdbe9a1-5e3f-4a0a-ad1e-6f13eb15c60b.png) - ![image](https://user-images.githubusercontent.com/40830821/129775011-fb0df01d-74e4-41ae-bb47-dcf4cc17735e.png) - - - After: - ![image](https://user-images.githubusercontent.com/40830821/129774877-928a8aa0-c003-4e57-8b33-ea6accc32774.png) + Before: + ![image](https://user-images.githubusercontent.com/40830821/129774936-ecdbe9a1-5e3f-4a0a-ad1e-6f13eb15c60b.png) + ![image](https://user-images.githubusercontent.com/40830821/129775011-fb0df01d-74e4-41ae-bb47-dcf4cc17735e.png) + + + After: + ![image](https://user-images.githubusercontent.com/40830821/129774877-928a8aa0-c003-4e57-8b33-ea6accc32774.png) ![image](https://user-images.githubusercontent.com/40830821/129774972-d67debaf-0ce9-44fb-93cb-d7612dd18edf.png) - Regression: Fix creation of self-DMs ([#23015](https://github.com/RocketChat/Rocket.Chat/pull/23015)) @@ -1261,8 +1263,7 @@ - Fix Auto Selection algorithm on community edition ([#22991](https://github.com/RocketChat/Rocket.Chat/pull/22991)) - - When using the autoselection algo on community editions, all agents were marked as unavailable due to an unapplied filter - + - When using the autoselection algo on community editions, all agents were marked as unavailable due to an unapplied filter - Fixed an issue when both user & system setting to manange EE max number of chats allowed were set to 0
@@ -1302,7 +1303,7 @@ - Apps-Engine's scheduler failing to update run tasks ([#22882](https://github.com/RocketChat/Rocket.Chat/pull/22882)) - [Agenda](https://github.com/agenda/agenda), the library that manages scheduling, depended on setting a job property named `nextRunAt` as `undefined` to signal whether it should be run on schedule or not. [Rocket.Chat's current Mongo driver](https://github.com/RocketChat/Rocket.Chat/pull/22399) ignores `undefined` values when updating documents and this was causing jobs to never stop running as Agenda couldn't clear that property (set them as `undefined`). + [Agenda](https://github.com/agenda/agenda), the library that manages scheduling, depended on setting a job property named `nextRunAt` as `undefined` to signal whether it should be run on schedule or not. [Rocket.Chat's current Mongo driver](https://github.com/RocketChat/Rocket.Chat/pull/22399) ignores `undefined` values when updating documents and this was causing jobs to never stop running as Agenda couldn't clear that property (set them as `undefined`). This updates Rocket.Chat's dependency on Agenda.js to point to [a fork that fixes the problem](https://github.com/RocketChat/agenda/releases/tag/3.1.2). - Close omnichannel conversations when agent is deactivated ([#22917](https://github.com/RocketChat/Rocket.Chat/pull/22917)) @@ -1356,7 +1357,7 @@ - Monitoring Track messages' round trip time ([#22676](https://github.com/RocketChat/Rocket.Chat/pull/22676)) - Track messages' roundtrip time from backend saves time to the time when received back from the oplog allowing track of oplog slowness. + Track messages' roundtrip time from backend saves time to the time when received back from the oplog allowing track of oplog slowness. Prometheus metric: `rocketchat_messages_roundtrip_time` - REST endpoint to remove User from Role ([#20485](https://github.com/RocketChat/Rocket.Chat/pull/20485) by [@Cosnavel](https://github.com/Cosnavel) & [@lucassartor](https://github.com/lucassartor)) @@ -1368,22 +1369,19 @@ - Change message deletion confirmation modal to toast ([#22544](https://github.com/RocketChat/Rocket.Chat/pull/22544)) - Changed a timed modal for a toast message + Changed a timed modal for a toast message ![image](https://user-images.githubusercontent.com/40830821/124192670-0646f900-da9c-11eb-941c-9ae35421f6ef.png) - Configuration for indices in Apps-Engine models ([#22705](https://github.com/RocketChat/Rocket.Chat/pull/22705)) - * Add `appId` field to the data saved by the Scheduler - - * Add `appId` index to `rocketchat_apps_persistence` model - - * Skip "trash collection" when deleting records from `rocketchat_apps_persistence` - - * Add a new setting to control for how long we should keep logs from the apps - - ![image](https://user-images.githubusercontent.com/1810309/126246666-907f9d98-1d84-4dfe-a80a-7dd874d36fa8.png) - - + * Add `appId` field to the data saved by the Scheduler + * Add `appId` index to `rocketchat_apps_persistence` model + * Skip "trash collection" when deleting records from `rocketchat_apps_persistence` + * Add a new setting to control for how long we should keep logs from the apps + + ![image](https://user-images.githubusercontent.com/1810309/126246666-907f9d98-1d84-4dfe-a80a-7dd874d36fa8.png) + + ![image](https://user-images.githubusercontent.com/1810309/126246655-2ce3cb5f-b2f5-456e-a9c4-beccd9b3ef41.png) - Make `shortcut` field of canned responses unique ([#22700](https://github.com/RocketChat/Rocket.Chat/pull/22700)) @@ -1406,38 +1404,37 @@ - Replace remaing discussion creation modals with React modal. ([#22448](https://github.com/RocketChat/Rocket.Chat/pull/22448)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/123840524-cbe72b80-d8e4-11eb-9ddb-23a9f9d90aac.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/123840524-cbe72b80-d8e4-11eb-9ddb-23a9f9d90aac.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/123840219-74e15680-d8e4-11eb-95aa-00a990ffe0e7.png) - Return open room if available for visitors ([#22742](https://github.com/RocketChat/Rocket.Chat/pull/22742)) - Rewrite Enter Encryption Password Modal ([#22456](https://github.com/RocketChat/Rocket.Chat/pull/22456)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/123182889-bbf3c580-d466-11eb-8d4d-9cfc3d224e33.png) - - ### after - ![image](https://user-images.githubusercontent.com/27704687/123182916-cada7800-d466-11eb-96ee-850be190d419.png) - - ### Aditional Improves: - + ### before + ![image](https://user-images.githubusercontent.com/27704687/123182889-bbf3c580-d466-11eb-8d4d-9cfc3d224e33.png) + + ### after + ![image](https://user-images.githubusercontent.com/27704687/123182916-cada7800-d466-11eb-96ee-850be190d419.png) + + ### Aditional Improves: - Added a visual validation in the password field - Rewrite OTR modals ([#22583](https://github.com/RocketChat/Rocket.Chat/pull/22583)) - ![image](https://user-images.githubusercontent.com/40830821/124513267-cb510800-ddb0-11eb-8165-f103029c348f.png) - ![image](https://user-images.githubusercontent.com/40830821/124513354-04897800-ddb1-11eb-96f4-41fe906ca0d7.png) + ![image](https://user-images.githubusercontent.com/40830821/124513267-cb510800-ddb0-11eb-8165-f103029c348f.png) + ![image](https://user-images.githubusercontent.com/40830821/124513354-04897800-ddb1-11eb-96f4-41fe906ca0d7.png) ![image](https://user-images.githubusercontent.com/40830821/124513395-1b2fcf00-ddb1-11eb-83e4-3f8f9b4676ba.png) - Rewrite Save Encryption Password Modal ([#22447](https://github.com/RocketChat/Rocket.Chat/pull/22447)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/122980201-c337a800-d36e-11eb-8e2b-68534cea8e1e.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/122980201-c337a800-d36e-11eb-8e2b-68534cea8e1e.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/122980409-f8dc9100-d36e-11eb-9c15-aff779c84a91.png) - Rewrite sidebar footer as React Component ([#22687](https://github.com/RocketChat/Rocket.Chat/pull/22687)) @@ -1452,12 +1449,12 @@ - Wrong error message when trying to create a blocked username ([#22452](https://github.com/RocketChat/Rocket.Chat/pull/22452) by [@lucassartor](https://github.com/lucassartor)) - When trying to create a user with a blocked username, the UI was showing generic error message that it wasn't very detailed. - - Old error message: - ![image](https://user-images.githubusercontent.com/49413772/123120080-6d203e80-d41a-11eb-8c87-64e34334c856.png) - - New error message: + When trying to create a user with a blocked username, the UI was showing generic error message that it wasn't very detailed. + + Old error message: + ![image](https://user-images.githubusercontent.com/49413772/123120080-6d203e80-d41a-11eb-8c87-64e34334c856.png) + + New error message: ![aaa](https://user-images.githubusercontent.com/49413772/123120251-8c1ed080-d41a-11eb-8dc2-d7484923d851.PNG) ### 🐛 Bug fixes @@ -1465,19 +1462,19 @@ - **ENTERPRISE:** Engagement Dashboard displaying incorrect data about active users ([#22381](https://github.com/RocketChat/Rocket.Chat/pull/22381)) - - Fix sessions' and users' grouping in the Engagement Dashboard API endpoints; - - Fix the data displayed in the charts from the "Active users", "Users by time of day" and "When is the chat busier?" sections of the Engagement Dashboard; + - Fix sessions' and users' grouping in the Engagement Dashboard API endpoints; + - Fix the data displayed in the charts from the "Active users", "Users by time of day" and "When is the chat busier?" sections of the Engagement Dashboard; - Replace label used to describe the amount of Active Users in the License section of the Info page. - **ENTERPRISE:** Make AutoSelect algo take current agent load in consideration ([#22611](https://github.com/RocketChat/Rocket.Chat/pull/22611)) - **ENTERPRISE:** Race condition on Omnichannel visitor abandoned callback ([#22413](https://github.com/RocketChat/Rocket.Chat/pull/22413)) - As you can see [here](https://github.com/RocketChat/Rocket.Chat/blob/857791c39c97b51b5b6fd3718e0c816959a81c3b/ee/app/livechat-enterprise/server/lib/Helper.js#L127) the `predictedVisitorAbandonment` flag is not set if the room object doesn't have `v.lastMessageTs` property. So we need to always make sure the `v.lastMessageTs` is set before this method is called. - - Currently the `v.lastMessageTs` is being set in [this](https://github.com/RocketChat/Rocket.Chat/blob/857791c39c97b51b5b6fd3718e0c816959a81c3b/app/livechat/server/hooks/saveLastVisitorMessageTs.js#L4) (lets call this **hook-1**) hook which has `HIGH` priority - and the `predictedVisitorAbandonment` check is inturn performed in [this](https://github.com/RocketChat/Rocket.Chat/blob/857791c39c97b51b5b6fd3718e0c816959a81c3b/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.js#L5) (let call this **hook-2**) hook which is also `HIGH` priority. - + As you can see [here](https://github.com/RocketChat/Rocket.Chat/blob/857791c39c97b51b5b6fd3718e0c816959a81c3b/ee/app/livechat-enterprise/server/lib/Helper.js#L127) the `predictedVisitorAbandonment` flag is not set if the room object doesn't have `v.lastMessageTs` property. So we need to always make sure the `v.lastMessageTs` is set before this method is called. + + Currently the `v.lastMessageTs` is being set in [this](https://github.com/RocketChat/Rocket.Chat/blob/857791c39c97b51b5b6fd3718e0c816959a81c3b/app/livechat/server/hooks/saveLastVisitorMessageTs.js#L4) (lets call this **hook-1**) hook which has `HIGH` priority + and the `predictedVisitorAbandonment` check is inturn performed in [this](https://github.com/RocketChat/Rocket.Chat/blob/857791c39c97b51b5b6fd3718e0c816959a81c3b/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.js#L5) (let call this **hook-2**) hook which is also `HIGH` priority. + So ideally we'd except the **hook-1** to be called b4 **hook-2**, however currently since both of them are at same priority, there is no way to control which one is executed first. Hence in this PR, I'm making the priority of **hook-2** as `MEDIUM` to keeping the priority of **hook-1** the same as b4, i.e. `HIGH`. This should make sure that the **hook-1** is always executed b4 **hook-2** - Admin page crashing when commit hash is null ([#22057](https://github.com/RocketChat/Rocket.Chat/pull/22057) by [@cprice-kgi](https://github.com/cprice-kgi)) @@ -1486,41 +1483,39 @@ - Blank screen in message auditing DM tab ([#22763](https://github.com/RocketChat/Rocket.Chat/pull/22763)) - The DM tab in message auditing was displaying a blank screen, instead of the actual tab. - + The DM tab in message auditing was displaying a blank screen, instead of the actual tab. + ![image](https://user-images.githubusercontent.com/28611993/127041404-dfca7f6a-2b8b-4c15-9cbd-c6238fac0063.png) - Bugs in AutoCompleteDepartment ([#22414](https://github.com/RocketChat/Rocket.Chat/pull/22414)) - Call button is still displayed when the user doesn't have permission to use it ([#22170](https://github.com/RocketChat/Rocket.Chat/pull/22170)) - - Hide 'Call' buttons from the tab bar for muted users; - + - Hide 'Call' buttons from the tab bar for muted users; - Display an error when a muted user attempts to enter a call using the 'Click to Join!' button. - Can't see full user profile on team's room ([#22355](https://github.com/RocketChat/Rocket.Chat/pull/22355)) - ### before - ![before](https://user-images.githubusercontent.com/27704687/121966860-bbac4980-cd45-11eb-8d48-2b0457110fc7.gif) - - ### after - ![after](https://user-images.githubusercontent.com/27704687/121966870-bea73a00-cd45-11eb-9c89-ec52ac17e20f.gif) - - ### aditional fix :rocket: - + ### before + ![before](https://user-images.githubusercontent.com/27704687/121966860-bbac4980-cd45-11eb-8d48-2b0457110fc7.gif) + + ### after + ![after](https://user-images.githubusercontent.com/27704687/121966870-bea73a00-cd45-11eb-9c89-ec52ac17e20f.gif) + + ### aditional fix :rocket: - unnecessary `TeamsMembers` component removed - Cannot create a discussion from top left sidebar as a user ([#22618](https://github.com/RocketChat/Rocket.Chat/pull/22618) by [@lucassartor](https://github.com/lucassartor)) - When trying to create a discussion using the top left sidebar modal with an role that don't have the `view-other-user-channels ` permission, an empty list would be shown, which is a wrong behavior. - Also, when being able to use this modal, discussions were listed as options, which is also a wrong behavior as there can't be nested discussions. - - This PR looks to fix both these issues. - - **Old behavior:** - ![old](https://user-images.githubusercontent.com/49413772/124960017-3c333280-dff2-11eb-86cd-b2638311517e.png) - - **New behavior:** + When trying to create a discussion using the top left sidebar modal with an role that don't have the `view-other-user-channels ` permission, an empty list would be shown, which is a wrong behavior. + Also, when being able to use this modal, discussions were listed as options, which is also a wrong behavior as there can't be nested discussions. + + This PR looks to fix both these issues. + + **Old behavior:** + ![old](https://user-images.githubusercontent.com/49413772/124960017-3c333280-dff2-11eb-86cd-b2638311517e.png) + + **New behavior:** ![image](https://user-images.githubusercontent.com/49413772/124958882-05a8e800-dff1-11eb-8203-b34a4f1c98a0.png) - Channel is automatically getting added to the first option in move to team feature ([#22670](https://github.com/RocketChat/Rocket.Chat/pull/22670)) @@ -1535,12 +1530,12 @@ - Create discussion modal - cancel button and invite users alignment ([#22718](https://github.com/RocketChat/Rocket.Chat/pull/22718)) - Changes in "open discussion" modal - - > Added cancel button - > Fixed alignment in invite user - - + Changes in "open discussion" modal + + > Added cancel button + > Fixed alignment in invite user + + ![image](https://user-images.githubusercontent.com/28611993/126388304-6ac76574-6924-426e-843d-afd53dc1c874.png) - crush in the getChannelHistory method ([#22667](https://github.com/RocketChat/Rocket.Chat/pull/22667) by [@MaestroArt](https://github.com/MaestroArt)) @@ -1575,29 +1570,27 @@ - Quote message not working for Livechat visitors ([#22586](https://github.com/RocketChat/Rocket.Chat/pull/22586)) - ### Before: - ![image](https://user-images.githubusercontent.com/34130764/124583613-de2b1180-de70-11eb-82aa-18564b317626.png) - ### After: + ### Before: + ![image](https://user-images.githubusercontent.com/34130764/124583613-de2b1180-de70-11eb-82aa-18564b317626.png) + ### After: ![image](https://user-images.githubusercontent.com/34130764/124583775-12063700-de71-11eb-8ab5-b0169fac2d40.png) - Redirect to login after delete own account ([#22499](https://github.com/RocketChat/Rocket.Chat/pull/22499)) - Redirect the user to login after delete own account - - ### Aditional fixes: - - - Visual issue in password input on Delete Own Account Modal - - ### before - ![image](https://user-images.githubusercontent.com/27704687/123711503-f5ea1080-d846-11eb-96aa-8ed638ca665c.png) - - ### after + Redirect the user to login after delete own account + + ### Aditional fixes: + - Visual issue in password input on Delete Own Account Modal + + ### before + ![image](https://user-images.githubusercontent.com/27704687/123711503-f5ea1080-d846-11eb-96aa-8ed638ca665c.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/123711336-b3c0cf00-d846-11eb-9408-a686d8668ba5.png) - Remove stack traces from Meteor errors when debug setting is disabled ([#22699](https://github.com/RocketChat/Rocket.Chat/pull/22699)) - - Fix 'not iterable' errors in the `normalizeMessage` function; - + - Fix 'not iterable' errors in the `normalizeMessage` function; - Remove stack traces from errors thrown by the `jitsi:updateTimeout` (and other `Meteor.Error`s) method. - Rewrite CurrentChats to TS ([#22424](https://github.com/RocketChat/Rocket.Chat/pull/22424)) @@ -1686,16 +1679,15 @@ - Regression: Data in the "Active Users" section is delayed in 1 day ([#22794](https://github.com/RocketChat/Rocket.Chat/pull/22794)) - - Fix 1 day delay in the Engagement Dashboard's "Active Users" section; - - - Downgrade `@nivo/line` version. - **Expected behavior:** + - Fix 1 day delay in the Engagement Dashboard's "Active Users" section; + - Downgrade `@nivo/line` version. + **Expected behavior:** ![active-users-engagement-dashboard](https://user-images.githubusercontent.com/36537004/127372185-390dc42f-bc90-4841-a22b-731f0aafcafe.PNG) - Regression: Data in the "New Users" section is delayed in 1 day ([#22751](https://github.com/RocketChat/Rocket.Chat/pull/22751)) - - Update nivo version (which was causing errors in the bar chart); - - Fix 1 day delay in '7 days' and '30 days' periods; + - Update nivo version (which was causing errors in the bar chart); + - Fix 1 day delay in '7 days' and '30 days' periods; - Update tooltip theme. - Regression: Federation warnings on ci ([#22765](https://github.com/RocketChat/Rocket.Chat/pull/22765) by [@g-thome](https://github.com/g-thome)) @@ -1720,9 +1712,9 @@ - Regression: Fix tooltip style in the "Busiest Chat Times" chart ([#22813](https://github.com/RocketChat/Rocket.Chat/pull/22813)) - - Fix tooltip in the Engagement Dashboard's "Busiest Chat Times" chart (Hours). - - **Expected behavior:** + - Fix tooltip in the Engagement Dashboard's "Busiest Chat Times" chart (Hours). + + **Expected behavior:** ![busiest-times-ed](https://user-images.githubusercontent.com/36537004/127527827-465397ed-f089-4fb7-9ab2-6fa8cea6abdf.PNG) - Regression: Fix users not being able to see the scope of the canned m… ([#22760](https://github.com/RocketChat/Rocket.Chat/pull/22760)) @@ -1739,10 +1731,10 @@ - Regression: Prevent custom status from being visible in sequential messages ([#22733](https://github.com/RocketChat/Rocket.Chat/pull/22733)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/126641946-866dae96-1983-43a5-b689-b24670473ad0.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/126641946-866dae96-1983-43a5-b689-b24670473ad0.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/126641752-3163eb95-1cd4-4d99-a61a-4d06d9e7e13e.png) - Regression: Properly force newline in attachment fields ([#22727](https://github.com/RocketChat/Rocket.Chat/pull/22727)) @@ -1923,32 +1915,30 @@ - Add `teams.convertToChannel` endpoint ([#22188](https://github.com/RocketChat/Rocket.Chat/pull/22188)) - - Add new `teams.converToChannel` endpoint; - - - Update `ConvertToTeam` modal text (since this action can now be reversed); - + - Add new `teams.converToChannel` endpoint; + - Update `ConvertToTeam` modal text (since this action can now be reversed); - Remove corresponding team memberships when a team is deleted or converted to a channel; - Add setting to configure default role for user on manual registration ([#20650](https://github.com/RocketChat/Rocket.Chat/pull/20650) by [@lucassartor](https://github.com/lucassartor)) - Add an `admin` setting to determine the initial `role` for new users who registered manually (through the register form and via API, not using an authentication service), normally all new users are assigned to the `user` role. - - The setting can be found in `Admin`->`Accounts`->`Registration`. - - ![image](https://user-images.githubusercontent.com/49413772/107252603-47b70900-6a14-11eb-9cc6-df76720b7365.png) - The setting initial value is false, so the default behaviour stays the same while creating a new server or upgrading one. - - https://user-images.githubusercontent.com/49413772/107253220-ddeb2f00-6a14-11eb-85b4-f770dbbe4970.mp4 - + Add an `admin` setting to determine the initial `role` for new users who registered manually (through the register form and via API, not using an authentication service), normally all new users are assigned to the `user` role. + + The setting can be found in `Admin`->`Accounts`->`Registration`. + + ![image](https://user-images.githubusercontent.com/49413772/107252603-47b70900-6a14-11eb-9cc6-df76720b7365.png) + The setting initial value is false, so the default behaviour stays the same while creating a new server or upgrading one. + + https://user-images.githubusercontent.com/49413772/107253220-ddeb2f00-6a14-11eb-85b4-f770dbbe4970.mp4 + Video showing an example of the setting being used and creating an new user with the default roles via API. - Content-Security-Policy for inline scripts ([#20724](https://github.com/RocketChat/Rocket.Chat/pull/20724)) - Security policies were applied for inline scripts cases. Due to the libraries and components we use it is not possible to disable inline styles and images as they would break Oembeds and other libraries. - - - basically the inline scripts were moved to a js file - + Security policies were applied for inline scripts cases. Due to the libraries and components we use it is not possible to disable inline styles and images as they would break Oembeds and other libraries. + + + basically the inline scripts were moved to a js file + and besides that some suggars syntax like `addScript` and `addStyle` were added, this way the application already takes care of inserting the elements and providing the content automatically. - Open modals in side effects outside React ([#22247](https://github.com/RocketChat/Rocket.Chat/pull/22247)) @@ -1964,17 +1954,15 @@ - Add BBB and Jitsi to Team ([#22312](https://github.com/RocketChat/Rocket.Chat/pull/22312)) - Added 2 new settings: - - - `Admin > Video Conference > Big Blue Button > Enable for teams` - + Added 2 new settings: + - `Admin > Video Conference > Big Blue Button > Enable for teams` - `Admin > Video Conference > Jitsi > Enable in teams` - Add debouncing to units selects filters ([#22097](https://github.com/RocketChat/Rocket.Chat/pull/22097)) - Add modal to close chats when tags/comments are not required ([#22245](https://github.com/RocketChat/Rocket.Chat/pull/22245) by [@rafaelblink](https://github.com/rafaelblink)) - When neither tags or comments are required to close a livechat, show this modal instead: + When neither tags or comments are required to close a livechat, show this modal instead: ![Screen Shot 2021-05-20 at 7 33 19 PM](https://user-images.githubusercontent.com/20868078/119057741-6af23c80-b9a3-11eb-902f-f8a7458ad11c.png) - Fallback messages on contextual bar ([#22376](https://github.com/RocketChat/Rocket.Chat/pull/22376)) @@ -1997,10 +1985,10 @@ - Remove differentiation between public x private channels in sidebar ([#22160](https://github.com/RocketChat/Rocket.Chat/pull/22160)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/119752184-e7d55880-be72-11eb-9167-be2f305ddb3f.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/119752184-e7d55880-be72-11eb-9167-be2f305ddb3f.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/119752125-c8d6c680-be72-11eb-8444-2e0c7cb1c600.png) - Rewrite create direct modal ([#22209](https://github.com/RocketChat/Rocket.Chat/pull/22209)) @@ -2009,8 +1997,8 @@ - Rewrite Create Discussion Modal (only through sidebar) ([#22224](https://github.com/RocketChat/Rocket.Chat/pull/22224)) - This is only available by creating a new discussion when clicking on the sidebar button. Other places will be implemented afterwards. - + This is only available by creating a new discussion when clicking on the sidebar button. Other places will be implemented afterwards. + ![image](https://user-images.githubusercontent.com/40830821/120556093-6af63180-c3d2-11eb-97ea-63c5423049dc.png) - Send only relevant data via WebSocket ([#22258](https://github.com/RocketChat/Rocket.Chat/pull/22258)) @@ -2024,12 +2012,12 @@ - **EE:** Canned responses can't be deleted ([#22095](https://github.com/RocketChat/Rocket.Chat/pull/22095) by [@rafaelblink](https://github.com/rafaelblink)) - Deletion button has been removed from the edition option. - - ## Before - ![image](https://user-images.githubusercontent.com/2493803/119059416-9f1b2c80-b9a6-11eb-933a-4efa1ac0552a.png) - - ### After + Deletion button has been removed from the edition option. + + ## Before + ![image](https://user-images.githubusercontent.com/2493803/119059416-9f1b2c80-b9a6-11eb-933a-4efa1ac0552a.png) + + ### After ![Rocket Chat (2)](https://user-images.githubusercontent.com/2493803/119172517-72b1ef80-ba3c-11eb-9178-04a12176f312.gif) - **ENTERPRISE:** Omnichannel enterprise permissions being added back to its default roles ([#22322](https://github.com/RocketChat/Rocket.Chat/pull/22322)) @@ -2038,19 +2026,19 @@ - **ENTERPRISE:** Prevent Visitor Abandonment after forwarding chat ([#22243](https://github.com/RocketChat/Rocket.Chat/pull/22243)) - Currently the Visitor Abandonment timer isn't affected when the chat is forwarded. However this is affecting the UX in certain situations like eg: A bot forwarding a chat to an human agent - ![image](https://user-images.githubusercontent.com/34130764/120896383-e4925780-c63e-11eb-937e-ffd7c4836159.png) - + Currently the Visitor Abandonment timer isn't affected when the chat is forwarded. However this is affecting the UX in certain situations like eg: A bot forwarding a chat to an human agent + ![image](https://user-images.githubusercontent.com/34130764/120896383-e4925780-c63e-11eb-937e-ffd7c4836159.png) + To solve this issue, we'll now be stoping the Visitor Abandonment timer once a chat is forwarded. - **IMPROVE:** Prevent creation of duplicated roles and new `roles.update` endpoint ([#22279](https://github.com/RocketChat/Rocket.Chat/pull/22279) by [@lucassartor](https://github.com/lucassartor)) - Currently, the action of updating a role is broken: because roles have their `_id` = `name`, when updating a role there's no way to validate if the user is trying to update or create a new role with a name that already exists - which causes wrong behaviors, such as roles with the same name and not being able to update them. - - To proper fix this, this PR looks to change the creation of roles. Now, roles have a unique `_id` value and there's a endpoint to update roles: `/api/v1/roles.update`. - - Doing so, it's possible to validate on both endpoints (`roles.create` and `roles.update`) to not allow roles with duplicated names. - + Currently, the action of updating a role is broken: because roles have their `_id` = `name`, when updating a role there's no way to validate if the user is trying to update or create a new role with a name that already exists - which causes wrong behaviors, such as roles with the same name and not being able to update them. + + To proper fix this, this PR looks to change the creation of roles. Now, roles have a unique `_id` value and there's a endpoint to update roles: `/api/v1/roles.update`. + + Doing so, it's possible to validate on both endpoints (`roles.create` and `roles.update`) to not allow roles with duplicated names. + **OBS:** The unique id changes only reflect new roles, the standard roles (such as admin and user) still have `_id` = `name`, but new roles now **can't** have the same name as them. - `channels.history`, `groups.history` and `im.history` REST endpoints not respecting hide system message config ([#22364](https://github.com/RocketChat/Rocket.Chat/pull/22364)) @@ -2067,10 +2055,10 @@ - Can't delete file from Room's file list ([#22191](https://github.com/RocketChat/Rocket.Chat/pull/22191)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/120215931-bb239700-c20c-11eb-9494-d4bc017df390.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/120215931-bb239700-c20c-11eb-9494-d4bc017df390.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/120216113-f8882480-c20c-11eb-9afb-b127e66a43da.png) - Cancel button and success toast at Leave Team modal ([#22373](https://github.com/RocketChat/Rocket.Chat/pull/22373)) @@ -2081,10 +2069,10 @@ - Convert and Move team permission ([#22350](https://github.com/RocketChat/Rocket.Chat/pull/22350)) - ### before - https://user-images.githubusercontent.com/45966964/114909360-5c04f100-9e1d-11eb-9363-f308e5d0be68.mp4 - - ### after + ### before + https://user-images.githubusercontent.com/45966964/114909360-5c04f100-9e1d-11eb-9363-f308e5d0be68.mp4 + + ### after https://user-images.githubusercontent.com/45966964/114909388-61fad200-9e1d-11eb-9bbe-114b55954a9f.mp4 - CORS error while interacting with any action button on Livechat ([#22150](https://github.com/RocketChat/Rocket.Chat/pull/22150)) @@ -2103,50 +2091,50 @@ - Members tab visual issues ([#22138](https://github.com/RocketChat/Rocket.Chat/pull/22138)) - ## Before - ![image](https://user-images.githubusercontent.com/27704687/119558283-95fbd800-bd77-11eb-91b4-91821f365bf3.png) - - ## After + ## Before + ![image](https://user-images.githubusercontent.com/27704687/119558283-95fbd800-bd77-11eb-91b4-91821f365bf3.png) + + ## After ![image](https://user-images.githubusercontent.com/27704687/119558120-6947c080-bd77-11eb-8ecb-7fedc07afa82.png) - Memory leak generated by Stream Cast usage ([#22329](https://github.com/RocketChat/Rocket.Chat/pull/22329)) - Stream Cast uses a different approach to broadcast data to the instances, it uses the DDP subscription method that requires a collection on the other side, if no collection exists with the given name `broadcast-stream` it caches in memory waiting for the collection to be set later. The cache is cleared only when a reconnection happens. - + Stream Cast uses a different approach to broadcast data to the instances, it uses the DDP subscription method that requires a collection on the other side, if no collection exists with the given name `broadcast-stream` it caches in memory waiting for the collection to be set later. The cache is cleared only when a reconnection happens. + This PR overrides the function that processes the data for that specific connection, preventing the cache and everything else to be processed since we already have our low-level listener to process the data. - Message box hiding on mobile view (Safari) ([#22212](https://github.com/RocketChat/Rocket.Chat/pull/22212)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/120404256-5b1c1600-c31c-11eb-96e9-860e4132db5f.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/120404256-5b1c1600-c31c-11eb-96e9-860e4132db5f.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/120404406-acc4a080-c31c-11eb-9efb-c2ad88664fda.png) - Missing burger menu on direct messages ([#22211](https://github.com/RocketChat/Rocket.Chat/pull/22211)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/120403671-09bf5700-c31b-11eb-92a1-a2f589bd85fc.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/120403671-09bf5700-c31b-11eb-92a1-a2f589bd85fc.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/120403693-1643af80-c31b-11eb-8027-dbdc4f560647.png) - Missing Throbber while thread list is loading ([#22316](https://github.com/RocketChat/Rocket.Chat/pull/22316)) - ### before - List was starting with no results even if there's results: - - ![image](https://user-images.githubusercontent.com/27704687/121606744-1e8ba100-ca25-11eb-9b31-706fb998d05f.png) - - ### after + ### before + List was starting with no results even if there's results: + + ![image](https://user-images.githubusercontent.com/27704687/121606744-1e8ba100-ca25-11eb-9b31-706fb998d05f.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/121606635-e97f4e80-ca24-11eb-81f7-af8b0cc41c89.png) - Not possible to edit some messages inside threads ([#22325](https://github.com/RocketChat/Rocket.Chat/pull/22325)) - ### Before - ![before](https://user-images.githubusercontent.com/27704687/121755733-4eeb4200-caee-11eb-9d77-1b498c38c478.gif) - - ### After + ### Before + ![before](https://user-images.githubusercontent.com/27704687/121755733-4eeb4200-caee-11eb-9d77-1b498c38c478.gif) + + ### After ![after](https://user-images.githubusercontent.com/27704687/121755736-514d9c00-caee-11eb-9897-78fcead172f2.gif) - Notifications not using user's name ([#22309](https://github.com/RocketChat/Rocket.Chat/pull/22309)) @@ -2173,10 +2161,10 @@ - Sidebar not closing when clicking on a channel ([#22271](https://github.com/RocketChat/Rocket.Chat/pull/22271)) - ### before - ![before](https://user-images.githubusercontent.com/27704687/121074843-c6e20100-c7aa-11eb-88db-76e39b57b064.gif) - - ### after + ### before + ![before](https://user-images.githubusercontent.com/27704687/121074843-c6e20100-c7aa-11eb-88db-76e39b57b064.gif) + + ### after ![after](https://user-images.githubusercontent.com/27704687/121074860-cb0e1e80-c7aa-11eb-9e96-06d75044b763.gif) - Sound notification is not emitted when the Omnichannel chat comes from another department ([#22291](https://github.com/RocketChat/Rocket.Chat/pull/22291)) @@ -2187,9 +2175,9 @@ - Undefined error when forwarding chats to offline department ([#22154](https://github.com/RocketChat/Rocket.Chat/pull/22154) by [@rafaelblink](https://github.com/rafaelblink)) - ![Screen Shot 2021-05-26 at 5 29 17 PM](https://user-images.githubusercontent.com/59577424/119727520-c495b380-be48-11eb-88a2-158017c7ad0a.png) - - Omnichannel agents are facing the error shown above when forwarding chats to offline departments. + ![Screen Shot 2021-05-26 at 5 29 17 PM](https://user-images.githubusercontent.com/59577424/119727520-c495b380-be48-11eb-88a2-158017c7ad0a.png) + + Omnichannel agents are facing the error shown above when forwarding chats to offline departments. The error usually takes place when the routing system algorithm is **Manual Selection**. - Unread bar in channel flash quickly and then disappear ([#22275](https://github.com/RocketChat/Rocket.Chat/pull/22275)) @@ -2220,15 +2208,15 @@ - Chore: Change modals for remove user from team && leave team ([#22141](https://github.com/RocketChat/Rocket.Chat/pull/22141)) - ![image](https://user-images.githubusercontent.com/40830821/119576154-93f14380-bd8e-11eb-8885-f889f2939bf4.png) + ![image](https://user-images.githubusercontent.com/40830821/119576154-93f14380-bd8e-11eb-8885-f889f2939bf4.png) ![image](https://user-images.githubusercontent.com/40830821/119576219-b5eac600-bd8e-11eb-832c-ea7a17a56bdd.png) - Chore: Check PR Title on every submission ([#22140](https://github.com/RocketChat/Rocket.Chat/pull/22140)) - Chore: Enable push gateway only if the server is registered ([#22346](https://github.com/RocketChat/Rocket.Chat/pull/22346) by [@lucassartor](https://github.com/lucassartor)) - Currently, when creating an unregistered server, the default value of the push gateway setting is set to true and is disabled (it can't be changed unless the server is registered). This is a wrong behavior as an unregistered server **can't** use the push gateway. - + Currently, when creating an unregistered server, the default value of the push gateway setting is set to true and is disabled (it can't be changed unless the server is registered). This is a wrong behavior as an unregistered server **can't** use the push gateway. + This PR creates a validation to check if the server is registered when enabling the push gateway. That way, even if the push gateway setting is turned on, but the server is unregistered, the push gateway **won't** work - it will behave like it is off. - Chore: Enforce TypeScript on Storybook ([#22317](https://github.com/RocketChat/Rocket.Chat/pull/22317)) @@ -2245,7 +2233,7 @@ - Chore: Update delete team modal to new design ([#22127](https://github.com/RocketChat/Rocket.Chat/pull/22127)) - Now the modal has only 2 steps (steps 1 and 2 were merged) + Now the modal has only 2 steps (steps 1 and 2 were merged) ![image](https://user-images.githubusercontent.com/40830821/119414580-2e398480-bcc6-11eb-9a47-515568257974.png) - Language update from LingoHub 🤖 on 2021-05-31Z ([#22196](https://github.com/RocketChat/Rocket.Chat/pull/22196)) @@ -2272,10 +2260,10 @@ - Regression: Missing flexDirection on select field ([#22300](https://github.com/RocketChat/Rocket.Chat/pull/22300)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/121425905-532a2a80-c949-11eb-885f-e8ddaf5c8d5c.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/121425905-532a2a80-c949-11eb-885f-e8ddaf5c8d5c.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/121425770-283fd680-c949-11eb-8d94-86886f174599.png) - Regression: RoomProvider using wrong types ([#22370](https://github.com/RocketChat/Rocket.Chat/pull/22370)) @@ -2416,50 +2404,50 @@ - **ENTERPRISE:** Introduce Load Rotation routing algorithm for Omnichannel ([#22090](https://github.com/RocketChat/Rocket.Chat/pull/22090) by [@rafaelblink](https://github.com/rafaelblink)) - This PR introduces a new Auto Chat Distribution (ACD) algorithm for Omnichannel: **Load Rotation**. - The algorithm distributes chats to agents one by one, which means that when a new chat arrives, the agent with the oldest routing assignment time will be selected to serve the chat, regardless of the number of chats in progress each agent has. - + This PR introduces a new Auto Chat Distribution (ACD) algorithm for Omnichannel: **Load Rotation**. + The algorithm distributes chats to agents one by one, which means that when a new chat arrives, the agent with the oldest routing assignment time will be selected to serve the chat, regardless of the number of chats in progress each agent has. + ![Screen Shot 2021-05-20 at 5 17 40 PM](https://user-images.githubusercontent.com/59577424/119043752-c61a3400-b98f-11eb-8543-f3176879af1d.png) - Back button for Omnichannel ([#21647](https://github.com/RocketChat/Rocket.Chat/pull/21647) by [@rafaelblink](https://github.com/rafaelblink)) - New Message Parser ([#21962](https://github.com/RocketChat/Rocket.Chat/pull/21962)) - The objective is to put an end to the confusion that we face having multiple parsers, and the problems that this brings, it is still experimental then users need to choose to use it. - - The benefits are multiple. no more unexpected cases or grammatical collisions (in addition to more flexible nested cases like bold within link labels). - Besides, we no longer render raw html, instead we use components, so the xss attacks are over (the easy ones at least). Without further discoveries and at the fronted, we only reder what is delivered thus improving our performance. + The objective is to put an end to the confusion that we face having multiple parsers, and the problems that this brings, it is still experimental then users need to choose to use it. + + The benefits are multiple. no more unexpected cases or grammatical collisions (in addition to more flexible nested cases like bold within link labels). + Besides, we no longer render raw html, instead we use components, so the xss attacks are over (the easy ones at least). Without further discoveries and at the fronted, we only reder what is delivered thus improving our performance. This can be used in multiple places, (message, alert, sidenav and in the entire mobile application.) - Option to notify failed login attempts to a channel ([#21968](https://github.com/RocketChat/Rocket.Chat/pull/21968)) - Option to prevent users from using Invisible status ([#20084](https://github.com/RocketChat/Rocket.Chat/pull/20084) by [@lucassartor](https://github.com/lucassartor)) - Add an `admin` option to allow/disallow the `Invisible` status option from all users. This option is available in the `Accounts` section. - - ![2021-01-06-11-55-22](https://user-images.githubusercontent.com/49413772/103782988-ebc52300-5016-11eb-8a29-dd540c21e11c.gif) - - If the option is turned off, the `users.setStatus` endpoint is also restricted from users trying to change their status to `Invisible`, throwing the following error: - ```json - { - "success": false, - "error": "Invisible status is disabled [error-not-allowed]", - "stack": "Error: Invisible status is disabled [error-not-allowed]\n at DDPCommon.MethodInvocation. (app/api/server/v1/users.js:425:13)\n at packages/dispatch_run-as-user.js:211:14\n at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1234:12)\n at Object.Meteor.runAsUser (packages/dispatch_run-as-user.js:210:33)\n at Object.post (app/api/server/v1/users.js:415:10)\n at app/api/server/api.js:394:82\n at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1234:12)\n at Object._internalRouteActionHandler [as action] (app/api/server/api.js:394:39)\n at Route.share.Route.Route._callEndpoint (packages/nimble_restivus/lib/route.coffee:150:32)\n at packages/nimble_restivus/lib/route.coffee:59:33\n at packages/simple_json-routes.js:98:9", - "errorType": "error-not-allowed", - "details": { - "method": "users.setStatus" - } - } + Add an `admin` option to allow/disallow the `Invisible` status option from all users. This option is available in the `Accounts` section. + + ![2021-01-06-11-55-22](https://user-images.githubusercontent.com/49413772/103782988-ebc52300-5016-11eb-8a29-dd540c21e11c.gif) + + If the option is turned off, the `users.setStatus` endpoint is also restricted from users trying to change their status to `Invisible`, throwing the following error: + ```json + { + "success": false, + "error": "Invisible status is disabled [error-not-allowed]", + "stack": "Error: Invisible status is disabled [error-not-allowed]\n at DDPCommon.MethodInvocation. (app/api/server/v1/users.js:425:13)\n at packages/dispatch_run-as-user.js:211:14\n at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1234:12)\n at Object.Meteor.runAsUser (packages/dispatch_run-as-user.js:210:33)\n at Object.post (app/api/server/v1/users.js:415:10)\n at app/api/server/api.js:394:82\n at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1234:12)\n at Object._internalRouteActionHandler [as action] (app/api/server/api.js:394:39)\n at Route.share.Route.Route._callEndpoint (packages/nimble_restivus/lib/route.coffee:150:32)\n at packages/nimble_restivus/lib/route.coffee:59:33\n at packages/simple_json-routes.js:98:9", + "errorType": "error-not-allowed", + "details": { + "method": "users.setStatus" + } + } ``` - Paginated and Filtered selects on new/edit unit ([#22052](https://github.com/RocketChat/Rocket.Chat/pull/22052) by [@rafaelblink](https://github.com/rafaelblink)) - REQUIRES https://github.com/RocketChat/Rocket.Chat.Fuselage/pull/447 - - Adds infinite scrolling selects to the units edit/create with the ability to be filtered by text as well - - ![Screen Shot 2021-05-17 at 9 24 19 AM](https://user-images.githubusercontent.com/20868078/118487999-abc32a80-b6f1-11eb-8d58-d031111ea0fb.png) - + REQUIRES https://github.com/RocketChat/Rocket.Chat.Fuselage/pull/447 + + Adds infinite scrolling selects to the units edit/create with the ability to be filtered by text as well + + ![Screen Shot 2021-05-17 at 9 24 19 AM](https://user-images.githubusercontent.com/20868078/118487999-abc32a80-b6f1-11eb-8d58-d031111ea0fb.png) + This Affects the monitors and departments inputs - Remove exif metadata from uploaded files ([#22044](https://github.com/RocketChat/Rocket.Chat/pull/22044)) @@ -2487,17 +2475,13 @@ - Inconsistent and misleading 2FA settings ([#22042](https://github.com/RocketChat/Rocket.Chat/pull/22042) by [@lucassartor](https://github.com/lucassartor)) - Currently, there are some inconsistencies and incorrect behaviors on the 2FA settings, such as: - - - - When disabling the TOTP 2FA, all 2FA are disabled; - - - There are no option to disable only the TOTP 2FA; - - - If 2FA are disabled, the other settings aren't blocked (the e-mail 2FA setting, for example); - - - It lacks some labels to warn the user of some specific 2FA options. - + Currently, there are some inconsistencies and incorrect behaviors on the 2FA settings, such as: + + - When disabling the TOTP 2FA, all 2FA are disabled; + - There are no option to disable only the TOTP 2FA; + - If 2FA are disabled, the other settings aren't blocked (the e-mail 2FA setting, for example); + - It lacks some labels to warn the user of some specific 2FA options. + This PR looks to fix those issues. - LDAP port setting input type to allow only numbers ([#21912](https://github.com/RocketChat/Rocket.Chat/pull/21912) by [@Deepak-learner](https://github.com/Deepak-learner)) @@ -2519,16 +2503,16 @@ - **APPS:** Scheduler duplicating recurrent tasks after server restart ([#21866](https://github.com/RocketChat/Rocket.Chat/pull/21866)) - Reintroduces the old method for creating recurring tasks in the apps' scheduler bridge to ensure tasks won't be duplicated. - - By introducing the [`skipImmediate` property option](https://github.com/RocketChat/Rocket.Chat/pull/21353) at the [`scheduleRecurring`](https://github.com/RocketChat/Rocket.Chat/blob/f8171f464ed8a7487795651767695fb33a1c709e/app/apps/server/bridges/scheduler.js#L119) method, the `every` method from _agenda.js_, which ensured no duplicates were created, was removed in favor of a more manual procedure. The new procedure was not taking into account the management of duplicates and as a result multiple copies of the same task could be created and they would get executed at the same time. - + Reintroduces the old method for creating recurring tasks in the apps' scheduler bridge to ensure tasks won't be duplicated. + + By introducing the [`skipImmediate` property option](https://github.com/RocketChat/Rocket.Chat/pull/21353) at the [`scheduleRecurring`](https://github.com/RocketChat/Rocket.Chat/blob/f8171f464ed8a7487795651767695fb33a1c709e/app/apps/server/bridges/scheduler.js#L119) method, the `every` method from _agenda.js_, which ensured no duplicates were created, was removed in favor of a more manual procedure. The new procedure was not taking into account the management of duplicates and as a result multiple copies of the same task could be created and they would get executed at the same time. + In the case of server restarts, every time this event happened and the app had the `startupSetting` configured to use _recurring tasks_, they would get recreated the same number of times. In the case of a server that restarts frequently (_n_ times), there would be the same (_n_) number of tasks duplicated (and running) in the system. - **ENTERPRISE:** Omnichannel Monitors can't forward chats to departments that they are not supervising ([#22128](https://github.com/RocketChat/Rocket.Chat/pull/22128)) - Currently, Omnichannel Monitors just can't forward chats to a department that is part of a `Business Unit` they're not supervising. This issue is causing critical problems on customer operations since this behaviour is not by design. - The reason this issue is taking place is that, by design, Monitors just have access to departments related to the `Business Units` they're monitoring, but this restriction is designed only for Omnichannel management areas, which means in case the monitor is, also, an agent, they're supposed to be able to forward a chat to any available departments regardless the `Business Units` it's associated with. + Currently, Omnichannel Monitors just can't forward chats to a department that is part of a `Business Unit` they're not supervising. This issue is causing critical problems on customer operations since this behaviour is not by design. + The reason this issue is taking place is that, by design, Monitors just have access to departments related to the `Business Units` they're monitoring, but this restriction is designed only for Omnichannel management areas, which means in case the monitor is, also, an agent, they're supposed to be able to forward a chat to any available departments regardless the `Business Units` it's associated with. So, initially, the restriction was implemented on the `Department Model` and, now, we're implementing the logic properly and introducing a new parameter to department endpoints, so the client will define which type of departments it needs. - **ENTERPRISE:** Omnichannel Monitors can't forward chats to departments that they are not supervising ([#22142](https://github.com/RocketChat/Rocket.Chat/pull/22142)) @@ -2567,18 +2551,18 @@ - Correcting a the wrong Archived label in edit room ([#21717](https://github.com/RocketChat/Rocket.Chat/pull/21717) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - ![image](https://user-images.githubusercontent.com/45966964/116584997-3cd78a80-a918-11eb-81fa-8a7eb5318ae9.png) - + ![image](https://user-images.githubusercontent.com/45966964/116584997-3cd78a80-a918-11eb-81fa-8a7eb5318ae9.png) + A label exists for Archived, and it has not been used. So I replaced it with the existing one. the label 'Archived' does not exist. - Custom OAuth not being completely deleted ([#21637](https://github.com/RocketChat/Rocket.Chat/pull/21637) by [@siva2204](https://github.com/siva2204)) - Directory Table's Sort Function ([#21921](https://github.com/RocketChat/Rocket.Chat/pull/21921)) - ### TableRow Margin Issue: - ![image](https://user-images.githubusercontent.com/27704687/116907348-d6a07f80-ac17-11eb-9411-edfe0906bfe1.png) - - ### Table Sort Action Issue: + ### TableRow Margin Issue: + ![image](https://user-images.githubusercontent.com/27704687/116907348-d6a07f80-ac17-11eb-9411-edfe0906bfe1.png) + + ### Table Sort Action Issue: ![directory](https://user-images.githubusercontent.com/27704687/116907441-f20b8a80-ac17-11eb-8790-bfce19e89a67.gif) - Discussion names showing a random value ([#22172](https://github.com/RocketChat/Rocket.Chat/pull/22172)) @@ -2589,54 +2573,54 @@ - Emails being sent with HTML entities getting escaped multiple times ([#21994](https://github.com/RocketChat/Rocket.Chat/pull/21994) by [@bhavayAnand9](https://github.com/bhavayAnand9)) - fixes an issue where if password contains special HTML character like &, in the email it would end up something like `&amp;` - - - password was going through multiple escapeHTML function calls - `secure&123 => secure&123 => secure&amp;123 + fixes an issue where if password contains special HTML character like &, in the email it would end up something like `&amp;` + + + password was going through multiple escapeHTML function calls + `secure&123 => secure&123 => secure&amp;123 ` - Error when you look at the members list of a room in which you are not a member ([#21952](https://github.com/RocketChat/Rocket.Chat/pull/21952) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - Before, when you look at the members of a room in which you are not a member the app crashed, i corrected this problem. - Indeed, there was a check on each currentSubscription. to see if it was not undefined except on currentSubscription.blocker - + Before, when you look at the members of a room in which you are not a member the app crashed, i corrected this problem. + Indeed, there was a check on each currentSubscription. to see if it was not undefined except on currentSubscription.blocker + https://user-images.githubusercontent.com/45966964/117087470-d3101400-ad4f-11eb-8f44-0ebca830a4d8.mp4 - errors when viewing a room that you're not subscribed to ([#21984](https://github.com/RocketChat/Rocket.Chat/pull/21984)) - Files list will not show deleted files. ([#21732](https://github.com/RocketChat/Rocket.Chat/pull/21732) by [@Darshilp326](https://github.com/Darshilp326)) - When you delete files from the header option, deleted files will not be shown. - + When you delete files from the header option, deleted files will not be shown. + https://user-images.githubusercontent.com/55157259/115730786-38552400-a3a4-11eb-9684-7f510920db66.mp4 - Fixed the fact that when a team was deleted, not all channels were unlinked from the team ([#21942](https://github.com/RocketChat/Rocket.Chat/pull/21942) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - Fixed the fact that when a team was deleted, not all channels were unlinked from the team. Only the first room of the rooms list was unlinked. - - After the fix, there is nos more errors: - - + Fixed the fact that when a team was deleted, not all channels were unlinked from the team. Only the first room of the rooms list was unlinked. + + After the fix, there is nos more errors: + + https://user-images.githubusercontent.com/45966964/117055182-2a47c180-ad1b-11eb-806f-07fb3fa7ec12.mp4 - Fixing Jitsi call ended Issue. ([#21808](https://github.com/RocketChat/Rocket.Chat/pull/21808)) - The new rewrite in react of contextual call component broke the Jitsi "click to join" messages. The issue being after 10 seconds of initiating the call, the message "click to join" always returned "Call Ended" even if the call was still going on. - This was due to the fact that after closing the contextual bar, the react component gets unmounted and we are not able to keep track of ongoing call and increase jitsi room timeout. - - This PR solves this issue by using the setInterval methods on component will unmount. When the call component unmounts, we keep on checking the state of jitsi call and based on conditions increase the jitsi room timeout. After the call is ended all setInterval calls are closed. - + The new rewrite in react of contextual call component broke the Jitsi "click to join" messages. The issue being after 10 seconds of initiating the call, the message "click to join" always returned "Call Ended" even if the call was still going on. + This was due to the fact that after closing the contextual bar, the react component gets unmounted and we are not able to keep track of ongoing call and increase jitsi room timeout. + + This PR solves this issue by using the setInterval methods on component will unmount. When the call component unmounts, we keep on checking the state of jitsi call and based on conditions increase the jitsi room timeout. After the call is ended all setInterval calls are closed. + This PR also removes the implementation of HEARTBEAT events of JitsiBridge. This is because this is no longer needed and all logic is being taken care of by the unmount function. - Handle NPS errors instead of throwing them ([#21945](https://github.com/RocketChat/Rocket.Chat/pull/21945)) - Header Tag Visual Issues ([#21991](https://github.com/RocketChat/Rocket.Chat/pull/21991)) - ### Normal - ![image](https://user-images.githubusercontent.com/27704687/117504793-69635600-af59-11eb-8b79-9d8f631490ee.png) - - ### Hover + ### Normal + ![image](https://user-images.githubusercontent.com/27704687/117504793-69635600-af59-11eb-8b79-9d8f631490ee.png) + + ### Hover ![image](https://user-images.githubusercontent.com/27704687/117504934-97489a80-af59-11eb-87c3-0a62731e9ce3.png) - Horizontal scrollbar not showing on tables ([#21852](https://github.com/RocketChat/Rocket.Chat/pull/21852)) @@ -2645,17 +2629,17 @@ - iFrame size on embedded videos ([#21992](https://github.com/RocketChat/Rocket.Chat/pull/21992)) - ### Before - ![image](https://user-images.githubusercontent.com/27704687/117508802-8bf86d80-af5f-11eb-9eb8-29e55b73eac5.png) - - ### After + ### Before + ![image](https://user-images.githubusercontent.com/27704687/117508802-8bf86d80-af5f-11eb-9eb8-29e55b73eac5.png) + + ### After ![image](https://user-images.githubusercontent.com/27704687/117508870-a4688800-af5f-11eb-9176-7f24de5fc424.png) - Incorrect error message when opening channel in anonymous read ([#22066](https://github.com/RocketChat/Rocket.Chat/pull/22066) by [@lucassartor](https://github.com/lucassartor)) - Every time you open a public channel with threads in it when using anonymous read an `Incorrect User` error will be thrown. - This is an incorrect behaviour as everything that is public should be valid for an anonymous user. - + Every time you open a public channel with threads in it when using anonymous read an `Incorrect User` error will be thrown. + This is an incorrect behaviour as everything that is public should be valid for an anonymous user. + Some files are adapted to that and have already removed this kind of incorrect error, but there are some that need some fix, this PR aims to do that. - Incorrect Team's Info spacing ([#22021](https://github.com/RocketChat/Rocket.Chat/pull/22021)) @@ -2668,21 +2652,19 @@ - Make the FR translation consistent with the 'room' translation + typos ([#21913](https://github.com/RocketChat/Rocket.Chat/pull/21913) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - In the FR translation files, there were two terms that were used to refer to **'room'**: - - - 'salon' (149 times used) - - ![image](https://user-images.githubusercontent.com/45966964/116829860-ac62a980-aba6-11eb-8212-e6f15ed0af82.png) - - - - 'salle' (46 times used) - - ![image](https://user-images.githubusercontent.com/45966964/116829871-be444c80-aba6-11eb-9b42-e213fee6586a.png) - - The problem is that both were used in the same context and sometimes even in the same option list. - However, since 'salon' is a better translation and was also in the majority, I used the translation 'salon' wherever 'salle' was marked. - - For example: + In the FR translation files, there were two terms that were used to refer to **'room'**: + - 'salon' (149 times used) + + ![image](https://user-images.githubusercontent.com/45966964/116829860-ac62a980-aba6-11eb-8212-e6f15ed0af82.png) + + - 'salle' (46 times used) + + ![image](https://user-images.githubusercontent.com/45966964/116829871-be444c80-aba6-11eb-9b42-e213fee6586a.png) + + The problem is that both were used in the same context and sometimes even in the same option list. + However, since 'salon' is a better translation and was also in the majority, I used the translation 'salon' wherever 'salle' was marked. + + For example: ![image](https://user-images.githubusercontent.com/45966964/116830523-1da45b80-abab-11eb-81f8-5225d51cecc6.png) - Maximum 25 channels can be loaded in the teams' channels list ([#21708](https://github.com/RocketChat/Rocket.Chat/pull/21708) by [@Jeanstaquet](https://github.com/Jeanstaquet)) @@ -2697,8 +2679,8 @@ - No warning message is sent when user is removed from a team's main channel ([#21949](https://github.com/RocketChat/Rocket.Chat/pull/21949)) - - Send a warning message to a team's main channel when a user is removed from the team; - - Trigger events while removing a user from a team's main channel; + - Send a warning message to a team's main channel when a user is removed from the team; + - Trigger events while removing a user from a team's main channel; - Fix `usersCount` field in the team's main room when a user is removed from the team (`usersCount` is now decreased by 1). - Not possible accept video call if "Hide right sidebar with click" is enabled ([#22175](https://github.com/RocketChat/Rocket.Chat/pull/22175)) @@ -2719,14 +2701,14 @@ - Prevent the userInfo tab to return 'User not found' each time if a certain member of a DM group has been deleted ([#21970](https://github.com/RocketChat/Rocket.Chat/pull/21970) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - Prevent the userInfo tab to return 'User not found' if a member of a DM group has been deleted. - This happens if the user that has been deleted is the one originally displayed on the userInfo tab in a DM group with >2 users. - + Prevent the userInfo tab to return 'User not found' if a member of a DM group has been deleted. + This happens if the user that has been deleted is the one originally displayed on the userInfo tab in a DM group with >2 users. + https://user-images.githubusercontent.com/45966964/117221081-db785580-ae08-11eb-9b33-2314a99eb037.mp4 - Prune messages not cleaning up unread threads ([#21326](https://github.com/RocketChat/Rocket.Chat/pull/21326) by [@renancleyson-dev](https://github.com/renancleyson-dev)) - Fixes permanent unread messages when admin prune at least two different thread messages in the room that were unread by some user. + Fixes permanent unread messages when admin prune at least two different thread messages in the room that were unread by some user. ![screencapture-localhost-3000-channel-general-thread-2021-03-26-13_17_16](https://user-images.githubusercontent.com/43624243/112678973-62b9cd00-8e4a-11eb-9af9-56f17cc66baf.png) - Redirect on remove user from channel by user profile tab ([#21951](https://github.com/RocketChat/Rocket.Chat/pull/21951)) @@ -2737,8 +2719,8 @@ - Removed fields from User Info for which the user doesn't have permissions. ([#20923](https://github.com/RocketChat/Rocket.Chat/pull/20923) by [@Darshilp326](https://github.com/Darshilp326)) - Removed LastLogin, CreatedAt and Roles for users who don't have permission. - + Removed LastLogin, CreatedAt and Roles for users who don't have permission. + https://user-images.githubusercontent.com/55157259/109381351-f2c62e80-78ff-11eb-9289-e11072bf62f8.mp4 - Replace `query` param by `name`, `username` and `status` on the `teams.members` endpoint ([#21539](https://github.com/RocketChat/Rocket.Chat/pull/21539)) @@ -2751,39 +2733,39 @@ - Unable to edit a 'direct' room setting in the admin due to the room name ([#21636](https://github.com/RocketChat/Rocket.Chat/pull/21636) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - When you are in the admin and want to change a room 'd' setting, it doesn't work because it takes into account the name that is set automatically and therefore tries to save that name. Since the name is not valid and should not be registered, we cannot change the setting for the 'd' room. - I made sure that when you want to change a setting in a 'd' room, that you don't take the name into account - - - https://user-images.githubusercontent.com/45966964/115150919-cd85af00-a06a-11eb-9667-ef3dcfc5adb6.mp4 - - + When you are in the admin and want to change a room 'd' setting, it doesn't work because it takes into account the name that is set automatically and therefore tries to save that name. Since the name is not valid and should not be registered, we cannot change the setting for the 'd' room. + I made sure that when you want to change a setting in a 'd' room, that you don't take the name into account + + + https://user-images.githubusercontent.com/45966964/115150919-cd85af00-a06a-11eb-9667-ef3dcfc5adb6.mp4 + + Behind the scene, the name is not saved - Unable to edit a user who does not have an email via the admin or via the user's profile ([#21626](https://github.com/RocketChat/Rocket.Chat/pull/21626) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - If a user does not have an email address, they cannot change it via their profile or via the admin. I fixed this issue. I have created several profiles and there was one that didn't have an email, I don't know how I did it, I am working on it. I had not modified the db to delete his email, hence the fix - - in admin - - https://user-images.githubusercontent.com/45966964/115112617-9b9b1c80-9f86-11eb-8e3a-950c3c1a1746.mp4 - - - - in the user profile - + If a user does not have an email address, they cannot change it via their profile or via the admin. I fixed this issue. I have created several profiles and there was one that didn't have an email, I don't know how I did it, I am working on it. I had not modified the db to delete his email, hence the fix + + in admin + + https://user-images.githubusercontent.com/45966964/115112617-9b9b1c80-9f86-11eb-8e3a-950c3c1a1746.mp4 + + + + in the user profile + https://user-images.githubusercontent.com/45966964/115112620-a0f86700-9f86-11eb-97b1-56eaba42216b.mp4 - Unable to get channels, sort by most recent message ([#21701](https://github.com/RocketChat/Rocket.Chat/pull/21701) by [@sumukhah](https://github.com/sumukhah)) - Unable to update app manually ([#21215](https://github.com/RocketChat/Rocket.Chat/pull/21215)) - It allows for update of apps using a zip file. - - When installing apps using the zip file, either by url or the file form, if the app was already installed, an error would be thrown stating the condition and forbidding the installation. Now, when sending a zip file of an app that is already installed, the user is presented with the following modal: - - ![2021-04-30-113936_627x235_scrot](https://user-images.githubusercontent.com/733282/116711383-2cbbbb80-a9a9-11eb-8c77-22d6802cb9f5.png) - + It allows for update of apps using a zip file. + + When installing apps using the zip file, either by url or the file form, if the app was already installed, an error would be thrown stating the condition and forbidding the installation. Now, when sending a zip file of an app that is already installed, the user is presented with the following modal: + + ![2021-04-30-113936_627x235_scrot](https://user-images.githubusercontent.com/733282/116711383-2cbbbb80-a9a9-11eb-8c77-22d6802cb9f5.png) + If the app also requires permissions to be reviewed, the modal that handles permission reviews will be shown after this one is accepted. - Unpin message reactivity ([#22029](https://github.com/RocketChat/Rocket.Chat/pull/22029)) @@ -2794,20 +2776,20 @@ - User Impersonation through sendMessage API ([#20391](https://github.com/RocketChat/Rocket.Chat/pull/20391) by [@lucassartor](https://github.com/lucassartor)) - Create a new permission: `message-impersonate`. For new installs only bot role will have the permission and for updating installs the permission will also be given to user role, so it won't break running deployments. - - If a message is being sent with `avatar` or `alias` properties, it validates if the sender has the `message-impersonate` permission, if not, an error is throwed: - ```json - { - "success": false, - "error": "Not enough permission", - "stack": "Error: Not enough permission\n ..." - } + Create a new permission: `message-impersonate`. For new installs only bot role will have the permission and for updating installs the permission will also be given to user role, so it won't break running deployments. + + If a message is being sent with `avatar` or `alias` properties, it validates if the sender has the `message-impersonate` permission, if not, an error is throwed: + ```json + { + "success": false, + "error": "Not enough permission", + "stack": "Error: Not enough permission\n ..." + } ``` - Visibility of burger menu on certain width ([#20736](https://github.com/RocketChat/Rocket.Chat/pull/20736) by [@yash-rajpal](https://github.com/yash-rajpal)) - Burger was not visible on a certain width, specifically between 600 to 780. if width is more than 780px sidebar is shown, if less than 600 then burger icon was shown. But it wasn't shown between 600px to 780 px. + Burger was not visible on a certain width, specifically between 600 to 780. if width is more than 780px sidebar is shown, if less than 600 then burger icon was shown. But it wasn't shown between 600px to 780 px. It was because for showing burger icon we were only checking for `isMobile` which is lenght only less than 600. So i added one more check for condition if length is less than 780 px. - When closing chats a comment is always required ([#21947](https://github.com/RocketChat/Rocket.Chat/pull/21947)) @@ -2822,8 +2804,8 @@ - Wrong icon on "Move to team" option in the channel info actions ([#21944](https://github.com/RocketChat/Rocket.Chat/pull/21944)) - ![image](https://user-images.githubusercontent.com/40830821/117061659-d9bf6c80-acf8-11eb-8e29-be47e702dedd.png) - + ![image](https://user-images.githubusercontent.com/40830821/117061659-d9bf6c80-acf8-11eb-8e29-be47e702dedd.png) + Depends on https://github.com/RocketChat/Rocket.Chat.Fuselage/pull/444
@@ -2840,10 +2822,8 @@ - Add two more test cases to the slash-command test suite ([#21317](https://github.com/RocketChat/Rocket.Chat/pull/21317) by [@EduardoPicolo](https://github.com/EduardoPicolo)) - Added two more test cases to the slash-command test suite: - - - 'should return an error when the command does not exist''; - + Added two more test cases to the slash-command test suite: + - 'should return an error when the command does not exist''; - 'should return an error when no command is provided'; - Bump actions/stale from v3.0.8 to v3.0.18 ([#21877](https://github.com/RocketChat/Rocket.Chat/pull/21877) by [@dependabot[bot]](https://github.com/dependabot[bot])) @@ -2878,9 +2858,9 @@ - i18n: Add missing translation string in account preference ([#21448](https://github.com/RocketChat/Rocket.Chat/pull/21448) by [@sumukhah](https://github.com/sumukhah)) - "Test Desktop Notifications" was missing in translation, Added to the file. - Screenshot 2021-04-05 at 3 58 01 PM - + "Test Desktop Notifications" was missing in translation, Added to the file. + Screenshot 2021-04-05 at 3 58 01 PM + Screenshot 2021-04-05 at 3 58 32 PM - i18n: Correct a typo in German ([#21711](https://github.com/RocketChat/Rocket.Chat/pull/21711) by [@Jeanstaquet](https://github.com/Jeanstaquet)) @@ -2907,10 +2887,10 @@ - Regression: discussions display on sidebar ([#22157](https://github.com/RocketChat/Rocket.Chat/pull/22157)) - ### group by type active - ![image](https://user-images.githubusercontent.com/27704687/119741996-37a92500-be5d-11eb-8b36-4067a7a229f1.png) - - ### group by type inactive + ### group by type active + ![image](https://user-images.githubusercontent.com/27704687/119741996-37a92500-be5d-11eb-8b36-4067a7a229f1.png) + + ### group by type inactive ![image](https://user-images.githubusercontent.com/27704687/119742054-56a7b700-be5d-11eb-8810-e31d4216f573.png) - regression: fix departments with empty ancestors not being returned ([#22068](https://github.com/RocketChat/Rocket.Chat/pull/22068)) @@ -2921,8 +2901,8 @@ - regression: Fix Users list in the Administration ([#22034](https://github.com/RocketChat/Rocket.Chat/pull/22034) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - The app crashed if no custom fields for user profiles have been created by the admin. I fixed this issue. This bug was introduced by a recent commit. - + The app crashed if no custom fields for user profiles have been created by the admin. I fixed this issue. This bug was introduced by a recent commit. + https://user-images.githubusercontent.com/45966964/118210838-5b3a9b80-b46b-11eb-9fe5-5b813848190c.mp4 - Regression: Improve migration 225 ([#22099](https://github.com/RocketChat/Rocket.Chat/pull/22099)) @@ -2939,7 +2919,7 @@ - Regression: not allowed to edit roles due to a new verification ([#22159](https://github.com/RocketChat/Rocket.Chat/pull/22159)) - introduced by https://github.com/RocketChat/Rocket.Chat/pull/21905 + introduced by https://github.com/RocketChat/Rocket.Chat/pull/21905 ![Peek 2021-05-26 22-21](https://user-images.githubusercontent.com/27704687/119750970-b9567e00-be70-11eb-9d52-04c8595950df.gif) - regression: Select Team Modal margin ([#22030](https://github.com/RocketChat/Rocket.Chat/pull/22030)) @@ -2950,10 +2930,10 @@ - Regression: Visual issue on sort list item ([#22158](https://github.com/RocketChat/Rocket.Chat/pull/22158)) - ### before - ![image](https://user-images.githubusercontent.com/27704687/119743703-d84d1400-be60-11eb-97cc-c8256b2c8b07.png) - - ### after + ### before + ![image](https://user-images.githubusercontent.com/27704687/119743703-d84d1400-be60-11eb-97cc-c8256b2c8b07.png) + + ### after ![image](https://user-images.githubusercontent.com/27704687/119743638-b18edd80-be60-11eb-828d-22cc5e1b2f5b.png) - Release 3.14.2 ([#22135](https://github.com/RocketChat/Rocket.Chat/pull/22135)) @@ -3139,12 +3119,12 @@ - Paginated and Filtered selects on new/edit unit ([#22052](https://github.com/RocketChat/Rocket.Chat/pull/22052) by [@rafaelblink](https://github.com/rafaelblink)) - REQUIRES https://github.com/RocketChat/Rocket.Chat.Fuselage/pull/447 - - Adds infinite scrolling selects to the units edit/create with the ability to be filtered by text as well - - ![Screen Shot 2021-05-17 at 9 24 19 AM](https://user-images.githubusercontent.com/20868078/118487999-abc32a80-b6f1-11eb-8d58-d031111ea0fb.png) - + REQUIRES https://github.com/RocketChat/Rocket.Chat.Fuselage/pull/447 + + Adds infinite scrolling selects to the units edit/create with the ability to be filtered by text as well + + ![Screen Shot 2021-05-17 at 9 24 19 AM](https://user-images.githubusercontent.com/20868078/118487999-abc32a80-b6f1-11eb-8d58-d031111ea0fb.png) + This Affects the monitors and departments inputs ### 🚀 Improvements @@ -3220,24 +3200,18 @@ - New set of rules for client code ([#21318](https://github.com/RocketChat/Rocket.Chat/pull/21318)) - This _small_ PR does the following: - - - - Now **React** is the web client's first-class citizen, being **loaded before Blaze**. Thus, `BlazeLayout` calls render templates inside of a React component (`BlazeLayoutWrapper`); - - - Main client startup code, including polyfills, is written in **TypeScript**; - - - At the moment, routes are treated as regular startup code; it's expected that `FlowRouter` will be deprecated in favor of a new routing library; - - - **React** was updated to major version **17**, deprecating the usage of `React` as namespace (e.g. use `memo()` instead of `React.memo()`); - - - The `client/` and `ee/client/` directory are linted with a **custom ESLint configuration** that includes: - - **Prettier**; - - `react-hooks/*` rules for TypeScript files; - - `react/no-multi-comp`, enforcing the rule of **one single React component per module**; - - `react/display-name`, which enforces that **React components must have a name for debugging**; - - `import/named`, avoiding broken named imports. - + This _small_ PR does the following: + + - Now **React** is the web client's first-class citizen, being **loaded before Blaze**. Thus, `BlazeLayout` calls render templates inside of a React component (`BlazeLayoutWrapper`); + - Main client startup code, including polyfills, is written in **TypeScript**; + - At the moment, routes are treated as regular startup code; it's expected that `FlowRouter` will be deprecated in favor of a new routing library; + - **React** was updated to major version **17**, deprecating the usage of `React` as namespace (e.g. use `memo()` instead of `React.memo()`); + - The `client/` and `ee/client/` directory are linted with a **custom ESLint configuration** that includes: + - **Prettier**; + - `react-hooks/*` rules for TypeScript files; + - `react/no-multi-comp`, enforcing the rule of **one single React component per module**; + - `react/display-name`, which enforces that **React components must have a name for debugging**; + - `import/named`, avoiding broken named imports. - A bunch of components were refactored to match the new ESLint rules. - On Hold system messages ([#21360](https://github.com/RocketChat/Rocket.Chat/pull/21360) by [@rafaelblink](https://github.com/rafaelblink)) @@ -3246,15 +3220,12 @@ - Password history ([#21607](https://github.com/RocketChat/Rocket.Chat/pull/21607)) - - Store each user's previously used passwords in a `passwordHistory` field (in the `users` record); - - - Users' previously used passwords are stored in their `passwordHistory` even when the setting is disabled; - - - Add "Password History" setting -- when enabled, it blocks users from reusing their most recent passwords; - - - Convert `comparePassword` file to TypeScript. - - ![Password_Change](https://user-images.githubusercontent.com/36537004/115035168-ac726200-9ea2-11eb-93c6-fc8182ba5f3f.png) + - Store each user's previously used passwords in a `passwordHistory` field (in the `users` record); + - Users' previously used passwords are stored in their `passwordHistory` even when the setting is disabled; + - Add "Password History" setting -- when enabled, it blocks users from reusing their most recent passwords; + - Convert `comparePassword` file to TypeScript. + + ![Password_Change](https://user-images.githubusercontent.com/36537004/115035168-ac726200-9ea2-11eb-93c6-fc8182ba5f3f.png) ![Password_History](https://user-images.githubusercontent.com/36537004/115035175-ad0af880-9ea2-11eb-9f40-94c6327a9854.png) - REST endpoint `teams.update` ([#21134](https://github.com/RocketChat/Rocket.Chat/pull/21134) by [@g-thome](https://github.com/g-thome)) @@ -3272,18 +3243,14 @@ - Add error messages to the creation of channels or usernames containing reserved words ([#21016](https://github.com/RocketChat/Rocket.Chat/pull/21016)) - Display error messages when the user attempts to create or edit users' or channels' names with any of the following words (**case-insensitive**): - - - admin; - - - administrator; - - - system; - - - user. - ![create-channel](https://user-images.githubusercontent.com/36537004/110132223-b421ef80-7da9-11eb-82bc-f0d4e1df967f.png) - ![register-username](https://user-images.githubusercontent.com/36537004/110132234-b71ce000-7da9-11eb-904e-580233625951.png) - ![change-channel](https://user-images.githubusercontent.com/36537004/110143057-96f31e00-7db5-11eb-994a-39ae9e63392e.png) + Display error messages when the user attempts to create or edit users' or channels' names with any of the following words (**case-insensitive**): + - admin; + - administrator; + - system; + - user. + ![create-channel](https://user-images.githubusercontent.com/36537004/110132223-b421ef80-7da9-11eb-82bc-f0d4e1df967f.png) + ![register-username](https://user-images.githubusercontent.com/36537004/110132234-b71ce000-7da9-11eb-904e-580233625951.png) + ![change-channel](https://user-images.githubusercontent.com/36537004/110143057-96f31e00-7db5-11eb-994a-39ae9e63392e.png) ![change-username](https://user-images.githubusercontent.com/36537004/110143065-98244b00-7db5-11eb-9d13-afc5dc9866de.png) - add permission check when adding a channel to a team ([#21689](https://github.com/RocketChat/Rocket.Chat/pull/21689) by [@g-thome](https://github.com/g-thome)) @@ -3308,8 +3275,7 @@ - Resize custom emojis on upload instead of saving at max res ([#21593](https://github.com/RocketChat/Rocket.Chat/pull/21593)) - - Create new MediaService (ideally, should be in charge of all media-related operations) - + - Create new MediaService (ideally, should be in charge of all media-related operations) - Resize emojis to 128x128 ### 🐛 Bug fixes @@ -3329,25 +3295,25 @@ - Allows more than 25 discussions/files to be loaded in the contextualbar ([#21511](https://github.com/RocketChat/Rocket.Chat/pull/21511) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - In some places, you could not load more than 25 threads/discussions/files on the screen when searching the lists in the contextualbar. - Threads & list are numbered for a better view of the solution - - + In some places, you could not load more than 25 threads/discussions/files on the screen when searching the lists in the contextualbar. + Threads & list are numbered for a better view of the solution + + https://user-images.githubusercontent.com/45966964/114222225-93335800-996e-11eb-833f-568e83129aae.mp4 - Allows more than 25 threads to be loaded, fixes #21507 ([#21508](https://github.com/RocketChat/Rocket.Chat/pull/21508) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - Allows to display more than 25 users maximum in the users list ([#21518](https://github.com/RocketChat/Rocket.Chat/pull/21518) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - Now when you scroll to the bottom of the users list, it shows more users. Before the fix, the limit for the query for loadMore was calculated so that no additional users could be loaded. - - Before - - https://user-images.githubusercontent.com/45966964/114249739-baece500-999b-11eb-9bb0-3a5bcee18ad8.mp4 - - After - - + Now when you scroll to the bottom of the users list, it shows more users. Before the fix, the limit for the query for loadMore was calculated so that no additional users could be loaded. + + Before + + https://user-images.githubusercontent.com/45966964/114249739-baece500-999b-11eb-9bb0-3a5bcee18ad8.mp4 + + After + + https://user-images.githubusercontent.com/45966964/114249895-364e9680-999c-11eb-985c-47aedc763488.mp4 - App installation from marketplace not correctly displaying the permissions ([#21470](https://github.com/RocketChat/Rocket.Chat/pull/21470)) @@ -3414,19 +3380,19 @@ - Margins on contextual bar information ([#21457](https://github.com/RocketChat/Rocket.Chat/pull/21457)) - ### Room - **Before** - ![image](https://user-images.githubusercontent.com/27704687/115080812-ba8fa500-9ed9-11eb-9078-3625603bf92b.png) - - **After** - ![image](https://user-images.githubusercontent.com/27704687/115080966-e9a61680-9ed9-11eb-929f-6516c1563e99.png) - - ### Livechat + ### Room + **Before** + ![image](https://user-images.githubusercontent.com/27704687/115080812-ba8fa500-9ed9-11eb-9078-3625603bf92b.png) + + **After** + ![image](https://user-images.githubusercontent.com/27704687/115080966-e9a61680-9ed9-11eb-929f-6516c1563e99.png) + + ### Livechat ![image](https://user-images.githubusercontent.com/27704687/113640101-1859fc80-9651-11eb-88f8-09a899953988.png) - Message Block ordering ([#21464](https://github.com/RocketChat/Rocket.Chat/pull/21464)) - Reactions should come before reply button. + Reactions should come before reply button. ![image](https://user-images.githubusercontent.com/40830821/113748926-6f0e1780-96df-11eb-93a5-ddcfa891413e.png) - Message link null corrupts message rendering ([#21579](https://github.com/RocketChat/Rocket.Chat/pull/21579) by [@g-thome](https://github.com/g-thome)) @@ -3479,19 +3445,15 @@ - Typos/missing elements in the French translation ([#21525](https://github.com/RocketChat/Rocket.Chat/pull/21525) by [@Jeanstaquet](https://github.com/Jeanstaquet)) - - I have corrected some typos in the translation - - - I added a translation for missing words - - - I took the opportunity to correct a mistranslated word - - - Test_Desktop_Notifications was missing in the EN and FR file + - I have corrected some typos in the translation + - I added a translation for missing words + - I took the opportunity to correct a mistranslated word + - Test_Desktop_Notifications was missing in the EN and FR file ![image](https://user-images.githubusercontent.com/45966964/114290186-e7792d80-9a7d-11eb-8164-3b5e72e93703.png) - Updating a message causing URLs to be parsed even within markdown code ([#21489](https://github.com/RocketChat/Rocket.Chat/pull/21489)) - - Fix `updateMessage` to avoid parsing URLs inside markdown - + - Fix `updateMessage` to avoid parsing URLs inside markdown - Honor `parseUrls` property when updating messages - Use async await in TeamChannels delete channel action ([#21534](https://github.com/RocketChat/Rocket.Chat/pull/21534)) @@ -3504,8 +3466,8 @@ - Wrong user in user info ([#21451](https://github.com/RocketChat/Rocket.Chat/pull/21451)) - Fixed some race conditions in admin. - + Fixed some race conditions in admin. + Self DMs used to be created with the userId duplicated. Sometimes rooms can have 2 equal uids, but it's a self DM. Fixed a getter so this isn't a problem anymore.
@@ -3514,30 +3476,22 @@ - Doc: Corrected links to documentation of rocket.chat README.md ([#20478](https://github.com/RocketChat/Rocket.Chat/pull/20478) by [@joshi008](https://github.com/joshi008)) - The link for documentation in the readme was previously https://rocket.chat/docs/ while that was not working and according to the website it was https://docs.rocket.chat/ - The link for deployment methods in readme was corrected from https://rocket.chat/docs/installation/paas-deployments/ to https://docs.rocket.chat/installation/paas-deployments + The link for documentation in the readme was previously https://rocket.chat/docs/ while that was not working and according to the website it was https://docs.rocket.chat/ + The link for deployment methods in readme was corrected from https://rocket.chat/docs/installation/paas-deployments/ to https://docs.rocket.chat/installation/paas-deployments Some more links to the documentations were giving 404 error which hence updated. - [Improve] Remove useless tabbar options from Omnichannel rooms ([#21561](https://github.com/RocketChat/Rocket.Chat/pull/21561) by [@rafaelblink](https://github.com/rafaelblink)) - A React-based replacement for BlazeLayout ([#21527](https://github.com/RocketChat/Rocket.Chat/pull/21527)) - - The Meteor package **`kadira:blaze-layout` was removed**; - - - A **global subscription** for the current application layout (**`appLayout`**) replaces `BlazeLayout` entirely; - - - The **`#react-root` element** is rendered on server-side instead of dynamically injected into the DOM tree; - - - The **"page loading" throbber** is now rendered on the React tree; - - - The **`renderRouteComponent` helper was removed**; - - - Some code run without any criteria on **`main` template** module was moved into **client startup modules**; - - - React portals used to embed Blaze templates have their own subscription (**`blazePortals`**); - - - Some **route components were refactored** to remove a URL path trap originally disabled by `renderRouteComponent`; - + - The Meteor package **`kadira:blaze-layout` was removed**; + - A **global subscription** for the current application layout (**`appLayout`**) replaces `BlazeLayout` entirely; + - The **`#react-root` element** is rendered on server-side instead of dynamically injected into the DOM tree; + - The **"page loading" throbber** is now rendered on the React tree; + - The **`renderRouteComponent` helper was removed**; + - Some code run without any criteria on **`main` template** module was moved into **client startup modules**; + - React portals used to embed Blaze templates have their own subscription (**`blazePortals`**); + - Some **route components were refactored** to remove a URL path trap originally disabled by `renderRouteComponent`; - A new component to embed the DOM nodes generated by **`RoomManager`** was created. - Add ')' after Date and Time in DB migration ([#21519](https://github.com/RocketChat/Rocket.Chat/pull/21519) by [@im-adithya](https://github.com/im-adithya)) @@ -3560,8 +3514,8 @@ - Chore: Meteor update to 2.1.1 ([#21494](https://github.com/RocketChat/Rocket.Chat/pull/21494)) - Basically Node update to version 12.22.1 - + Basically Node update to version 12.22.1 + Meteor change log https://github.com/meteor/meteor/blob/devel/History.md#v211-2021-04-06 - Chore: Remove control character from room model operation ([#21493](https://github.com/RocketChat/Rocket.Chat/pull/21493)) @@ -3570,8 +3524,7 @@ - Fix: Missing module `eventemitter3` for micro services ([#21611](https://github.com/RocketChat/Rocket.Chat/pull/21611)) - - Fix error when running micro services after version 3.12 - + - Fix error when running micro services after version 3.12 - Fix build of docker image version latest for micro services - Language update from LingoHub 🤖 on 2021-04-05Z ([#21446](https://github.com/RocketChat/Rocket.Chat/pull/21446)) @@ -3584,12 +3537,9 @@ - QoL improvements to add channel to team flow ([#21778](https://github.com/RocketChat/Rocket.Chat/pull/21778)) - - Fixed canAccessRoom validation - - - Added e2e tests - - - Removed channels that user cannot add to the team from autocomplete suggestions - + - Fixed canAccessRoom validation + - Added e2e tests + - Removed channels that user cannot add to the team from autocomplete suggestions - Improved error messages - Regression: Bold, italic and strike render (Original markdown) ([#21747](https://github.com/RocketChat/Rocket.Chat/pull/21747)) @@ -3612,10 +3562,10 @@ - Regression: Legacy Banner Position ([#21598](https://github.com/RocketChat/Rocket.Chat/pull/21598)) - ### Before: - ![image](https://user-images.githubusercontent.com/27704687/114961773-dc3c4e00-9e3f-11eb-9a32-e882db3fbfbc.png) - - ### After + ### Before: + ![image](https://user-images.githubusercontent.com/27704687/114961773-dc3c4e00-9e3f-11eb-9a32-e882db3fbfbc.png) + + ### After ![image](https://user-images.githubusercontent.com/27704687/114961673-a6976500-9e3f-11eb-9238-a12870d7db8f.png) - regression: Markdown broken on safari ([#21780](https://github.com/RocketChat/Rocket.Chat/pull/21780)) @@ -3825,62 +3775,56 @@ - **APPS:** New event interfaces for pre/post user leaving a room ([#20917](https://github.com/RocketChat/Rocket.Chat/pull/20917) by [@lucassartor](https://github.com/lucassartor)) - Added events and errors that trigger when a user leaves a room. + Added events and errors that trigger when a user leaves a room. That way it can communicate with the Apps-Engine by the `IPreRoomUserLeave` and `IPostRoomUserLeave` event interfaces. - **Enterprise:** Omnichannel On-Hold Queue ([#20945](https://github.com/RocketChat/Rocket.Chat/pull/20945)) - ### About this feature - This feature has been introduced to deal with Inactive chats. A chat is considered Inactive if an Omnichannel End User (aka Visitor) has not replied back to an agent in some time. These types of inactive chats become very important when an organisation has a limit set for `Max Simultaneous Chats per agent` which is defined by the following setting :point_down: , as more number of Inactive chats would directly affect an agent's productivity. - ![image](https://user-images.githubusercontent.com/34130764/111533003-4d7ad980-878c-11eb-8c1c-2796678a07db.png) - - Before this feature, we only had one option to deal with such Inactive/Abandoned chats - which was to auto close abandoned chats via this setting :point_down: - ![image](https://user-images.githubusercontent.com/34130764/111534353-e65e2480-878d-11eb-82a5-71368064ef45.png) - - however closing a chat isn't a best option for some cases. Let me take an example to explain a scenario - - > An agent is assisting a customer for installing a very huge software which is likely to take more than 20-30 minutes to download. In such scenarios closing a chat isn't the best approach since even after the lengthy download the customer might still need some assist from the agent. - > So basically this chat is going to block the agent's queue until the customer is able to finish his time-consuming download task in which he/she doesn't require any agent's assistance. Due to the `Max Simultaneous Chats per agent` limit, the agent is also not able to use this extra time to help other customer thus affecting his overall productivity. - - **So how does the On-Hold feature solve this problem?** - With the On-Hold feature, an agent is now able to place a chat on-hold. On-Hold chats **don’t count towards the maximum number of concurrent chats** an agent can have. So in our above example, the agent can simply now place the customer on-hold for 20-30 minutes until the customer downloads the software and within this time, the agent can serve other customers - hence increasing the productivity of an agent. - - ---------------------------------------- - ### Working of the new On-Hold feature - - #### How can you place a chat on Hold ? - - A chat can be placed on-hold via 2 means - - 1. Automatically place Abandoned chats On-hold - ![image](https://user-images.githubusercontent.com/34130764/111537074-06431780-8791-11eb-8d23-99f5d9f8ec45.png) - Via this :top: option you can define a timer which will get started when a customer sends a message. If we don't receive any message from the customer within this timer, the timer will get expired and the chat will be considered as Abandoned. - ![image](https://user-images.githubusercontent.com/34130764/111537346-53bf8480-8791-11eb-8dc7-260633b4e98f.png) - The via this :top: setting you can choose to automatically place this abandoned chat On Hold - - 2. Manually place a chat On Hold - As an admin, you can allow an agent to manually place a chat on-hold. To do so, you'll need to turn on this :point_down: setting - ![image](https://user-images.githubusercontent.com/34130764/111537545-97b28980-8791-11eb-86fd-db45b87e9cc1.png) - Now an agent will be able to see a new `On Hold` button within their `Visitor Info Panel` like this :point_down: , provided the agent has sent the last message - ![image](https://user-images.githubusercontent.com/34130764/111537853-f24be580-8791-11eb-9561-d77ba430c625.png) - - #### How can you resume a On Hold chat ? - An On Hold chat can be resumed via 2 means - - - 1. If the Customer sends a message - If the Customer / Omnichannel End User sends a message to the On Hold chat, the On Hold chat will get automatically resumed. - - 2. Manually by agent - An Agent can manually resume the On Hold chat via clicking the `Resume` button in the bottom of a chat room. - ![image](https://user-images.githubusercontent.com/34130764/111538666-f88e9180-8792-11eb-8d14-01453b8e3db0.png) - - #### What would happen if the agent already reached maximum chats, and a On-Hold chat gets resumed ? - Based on how the chat was resumed, there are multiple cases are each case is dealt differently - - - - If an agent manually tries to resume the On Hold chat, he/she will get an error saying `Maximum Simultaneous chat limit reached` - + ### About this feature + This feature has been introduced to deal with Inactive chats. A chat is considered Inactive if an Omnichannel End User (aka Visitor) has not replied back to an agent in some time. These types of inactive chats become very important when an organisation has a limit set for `Max Simultaneous Chats per agent` which is defined by the following setting :point_down: , as more number of Inactive chats would directly affect an agent's productivity. + ![image](https://user-images.githubusercontent.com/34130764/111533003-4d7ad980-878c-11eb-8c1c-2796678a07db.png) + + Before this feature, we only had one option to deal with such Inactive/Abandoned chats - which was to auto close abandoned chats via this setting :point_down: + ![image](https://user-images.githubusercontent.com/34130764/111534353-e65e2480-878d-11eb-82a5-71368064ef45.png) + + however closing a chat isn't a best option for some cases. Let me take an example to explain a scenario + + > An agent is assisting a customer for installing a very huge software which is likely to take more than 20-30 minutes to download. In such scenarios closing a chat isn't the best approach since even after the lengthy download the customer might still need some assist from the agent. + > So basically this chat is going to block the agent's queue until the customer is able to finish his time-consuming download task in which he/she doesn't require any agent's assistance. Due to the `Max Simultaneous Chats per agent` limit, the agent is also not able to use this extra time to help other customer thus affecting his overall productivity. + + **So how does the On-Hold feature solve this problem?** + With the On-Hold feature, an agent is now able to place a chat on-hold. On-Hold chats **don’t count towards the maximum number of concurrent chats** an agent can have. So in our above example, the agent can simply now place the customer on-hold for 20-30 minutes until the customer downloads the software and within this time, the agent can serve other customers - hence increasing the productivity of an agent. + + ---------------------------------------- + ### Working of the new On-Hold feature + + #### How can you place a chat on Hold ? + + A chat can be placed on-hold via 2 means + 1. Automatically place Abandoned chats On-hold + ![image](https://user-images.githubusercontent.com/34130764/111537074-06431780-8791-11eb-8d23-99f5d9f8ec45.png) + Via this :top: option you can define a timer which will get started when a customer sends a message. If we don't receive any message from the customer within this timer, the timer will get expired and the chat will be considered as Abandoned. + ![image](https://user-images.githubusercontent.com/34130764/111537346-53bf8480-8791-11eb-8dc7-260633b4e98f.png) + The via this :top: setting you can choose to automatically place this abandoned chat On Hold + 2. Manually place a chat On Hold + As an admin, you can allow an agent to manually place a chat on-hold. To do so, you'll need to turn on this :point_down: setting + ![image](https://user-images.githubusercontent.com/34130764/111537545-97b28980-8791-11eb-86fd-db45b87e9cc1.png) + Now an agent will be able to see a new `On Hold` button within their `Visitor Info Panel` like this :point_down: , provided the agent has sent the last message + ![image](https://user-images.githubusercontent.com/34130764/111537853-f24be580-8791-11eb-9561-d77ba430c625.png) + + #### How can you resume a On Hold chat ? + An On Hold chat can be resumed via 2 means + + 1. If the Customer sends a message + If the Customer / Omnichannel End User sends a message to the On Hold chat, the On Hold chat will get automatically resumed. + 2. Manually by agent + An Agent can manually resume the On Hold chat via clicking the `Resume` button in the bottom of a chat room. + ![image](https://user-images.githubusercontent.com/34130764/111538666-f88e9180-8792-11eb-8d14-01453b8e3db0.png) + + #### What would happen if the agent already reached maximum chats, and a On-Hold chat gets resumed ? + Based on how the chat was resumed, there are multiple cases are each case is dealt differently + + - If an agent manually tries to resume the On Hold chat, he/she will get an error saying `Maximum Simultaneous chat limit reached` - If a customer replies back on an On Hold chat and the last serving agent has reached maximum capacity, then this customer will be placed on the queue again from where based on the Routing Algorithm selected, the chat will get transferred to any available agent - Ability to hide 'Room topic changed' system messages ([#21062](https://github.com/RocketChat/Rocket.Chat/pull/21062) by [@Tirieru](https://github.com/Tirieru)) @@ -3891,39 +3835,33 @@ - Teams ([#20966](https://github.com/RocketChat/Rocket.Chat/pull/20966) by [@g-thome](https://github.com/g-thome)) - ## Teams - - - - You can easily group your users as Teams on Rocket.Chat. The feature takes the hassle out of managing multiple users one by one and allows you to handle them at the same time efficiently. - - - - - Teams can be public or private and each team can have its own channels, which also can be public or private. - - - It's possible to add existing channels to a Team or create new ones inside a Team. - - - It's possible to invite people outside a Team to join Team's channels. - - - It's possible to convert channels to Teams - - - It's possible to add all team members to a channel at once - - - Team members have roles - - - ![image](https://user-images.githubusercontent.com/70927132/113421955-4f56b680-93a2-11eb-80dc-9b70a3f09b3e.png) - - - - **Quickly onboard new users with Autojoin channels** - - Teams can have Auto-join channels – channels to which the team members are automatically added, so you don’t need to go through the manual process of adding users repetitively - - ![image](https://user-images.githubusercontent.com/70927132/113419284-81194e80-939d-11eb-9fff-aeb05cbc8089.png) - - **Instantly mention multiple members at once** (available in EE) - + ## Teams + + + + You can easily group your users as Teams on Rocket.Chat. The feature takes the hassle out of managing multiple users one by one and allows you to handle them at the same time efficiently. + + + - Teams can be public or private and each team can have its own channels, which also can be public or private. + - It's possible to add existing channels to a Team or create new ones inside a Team. + - It's possible to invite people outside a Team to join Team's channels. + - It's possible to convert channels to Teams + - It's possible to add all team members to a channel at once + - Team members have roles + + + ![image](https://user-images.githubusercontent.com/70927132/113421955-4f56b680-93a2-11eb-80dc-9b70a3f09b3e.png) + + + + **Quickly onboard new users with Autojoin channels** + + Teams can have Auto-join channels – channels to which the team members are automatically added, so you don’t need to go through the manual process of adding users repetitively + + ![image](https://user-images.githubusercontent.com/70927132/113419284-81194e80-939d-11eb-9fff-aeb05cbc8089.png) + + **Instantly mention multiple members at once** (available in EE) + With Teams, you don’t need to remember everyone’s name to communicate with a team quickly. Just mention a Team — @engineers, for instance — and all members will be instantly notified. ### 🚀 Improvements @@ -3933,22 +3871,22 @@ - Added modal-box for preview after recording audio. ([#20370](https://github.com/RocketChat/Rocket.Chat/pull/20370) by [@Darshilp326](https://github.com/Darshilp326)) - A modal box will be displayed so that users can change the filename and add description. - - **Before** - - https://user-images.githubusercontent.com/55157259/105687301-4e2a8880-5f1e-11eb-873d-dc8a880a2fc8.mp4 - - **After** - + A modal box will be displayed so that users can change the filename and add description. + + **Before** + + https://user-images.githubusercontent.com/55157259/105687301-4e2a8880-5f1e-11eb-873d-dc8a880a2fc8.mp4 + + **After** + https://user-images.githubusercontent.com/55157259/105687342-597db400-5f1e-11eb-8b61-8f9d9ebad0c4.mp4 - Adds toast after follow/unfollow messages and following icon for followed messages without threads. ([#20025](https://github.com/RocketChat/Rocket.Chat/pull/20025) by [@RonLek](https://github.com/RonLek)) - There was no alert on following/unfollowing a message previously. Also, it was impossible to make out a followed message with no threads from an unfollowed one. - - This PR would show an alert on following/unfollowing a message and also display a small bell icon (similar to the ones for starred and pinned messages) when a message with no thread is followed. - + There was no alert on following/unfollowing a message previously. Also, it was impossible to make out a followed message with no threads from an unfollowed one. + + This PR would show an alert on following/unfollowing a message and also display a small bell icon (similar to the ones for starred and pinned messages) when a message with no thread is followed. + https://user-images.githubusercontent.com/28918901/103813540-43e73e00-5086-11eb-8592-2877eb650f3e.mp4 - Back to threads list button on threads contextual bar ([#20882](https://github.com/RocketChat/Rocket.Chat/pull/20882)) @@ -3961,12 +3899,12 @@ - Improve Apps permission modal ([#21193](https://github.com/RocketChat/Rocket.Chat/pull/21193) by [@lucassartor](https://github.com/lucassartor)) - Improve the UI of the Apps permission modal when installing an App that requires permissions. - - **New UI:** - ![after](https://user-images.githubusercontent.com/49413772/111685622-e817fe80-8806-11eb-998d-b56623560e74.PNG) - - **Old UI:** + Improve the UI of the Apps permission modal when installing an App that requires permissions. + + **New UI:** + ![after](https://user-images.githubusercontent.com/49413772/111685622-e817fe80-8806-11eb-998d-b56623560e74.PNG) + + **Old UI:** ![before](https://user-images.githubusercontent.com/49413772/111685897-375e2f00-8807-11eb-814e-cb8060dc1830.PNG) - Make debug logs of Apps configurable via Log_Level setting in the Admin panel ([#21000](https://github.com/RocketChat/Rocket.Chat/pull/21000) by [@cuonghuunguyen](https://github.com/cuonghuunguyen)) @@ -3977,15 +3915,15 @@ - Sort Users List In Case Insensitive Manner ([#20790](https://github.com/RocketChat/Rocket.Chat/pull/20790) by [@aditya-mitra](https://github.com/aditya-mitra)) - The users listed in the admin panel were sorted in a case-sensitive manner , where the capitals came first and then the small letters (like - *A B C a b c*). This Change fixes this by sorting the names in a caseinsensitive manner (now - *A a B b C c*). - - ### Before - - ![before](https://user-images.githubusercontent.com/55396651/108189880-3fa74980-7137-11eb-99da-6498707b4bf8.png) - - - ### With This Change - + The users listed in the admin panel were sorted in a case-sensitive manner , where the capitals came first and then the small letters (like - *A B C a b c*). This Change fixes this by sorting the names in a caseinsensitive manner (now - *A a B b C c*). + + ### Before + + ![before](https://user-images.githubusercontent.com/55396651/108189880-3fa74980-7137-11eb-99da-6498707b4bf8.png) + + + ### With This Change + ![after](https://user-images.githubusercontent.com/55396651/108190177-9dd42c80-7137-11eb-8b4e-b7cef4ba512f.png) ### 🐛 Bug fixes @@ -3999,12 +3937,12 @@ - **APPS:** Warn message while installing app in air-gapped environment ([#20992](https://github.com/RocketChat/Rocket.Chat/pull/20992) by [@lucassartor](https://github.com/lucassartor)) - Change **error** message to a **warn** message when uploading a `.zip` file app into a air-gapped environment. - - The **error** message was giving the impression for the user that the app wasn't properly being installed , which it wasn't the case: - ![error](https://user-images.githubusercontent.com/49413772/109855273-d3e4d680-7c36-11eb-824b-ad455d24710c.PNG) - - A more detailed **warn** message can fix that impression for the user: + Change **error** message to a **warn** message when uploading a `.zip` file app into a air-gapped environment. + + The **error** message was giving the impression for the user that the app wasn't properly being installed , which it wasn't the case: + ![error](https://user-images.githubusercontent.com/49413772/109855273-d3e4d680-7c36-11eb-824b-ad455d24710c.PNG) + + A more detailed **warn** message can fix that impression for the user: ![warn](https://user-images.githubusercontent.com/49413772/109855383-f2e36880-7c36-11eb-8d61-c442980bd8fd.PNG) - Add missing `unreads` field to `users.info` REST endpoint ([#20905](https://github.com/RocketChat/Rocket.Chat/pull/20905)) @@ -4019,10 +3957,10 @@ - Correct direction for admin mapview text ([#20897](https://github.com/RocketChat/Rocket.Chat/pull/20897) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) - ![Screenshot from 2021-02-25 02-49-21](https://user-images.githubusercontent.com/38764067/109068512-f8602080-7715-11eb-8e22-d610f9d046d8.png) - ![Screenshot from 2021-02-25 02-49-46](https://user-images.githubusercontent.com/38764067/109068516-fa29e400-7715-11eb-9119-1c79abce278f.png) - ![Screenshot from 2021-02-25 02-49-57](https://user-images.githubusercontent.com/38764067/109068519-fbf3a780-7715-11eb-8b3d-0dc32f898725.png) - + ![Screenshot from 2021-02-25 02-49-21](https://user-images.githubusercontent.com/38764067/109068512-f8602080-7715-11eb-8e22-d610f9d046d8.png) + ![Screenshot from 2021-02-25 02-49-46](https://user-images.githubusercontent.com/38764067/109068516-fa29e400-7715-11eb-9119-1c79abce278f.png) + ![Screenshot from 2021-02-25 02-49-57](https://user-images.githubusercontent.com/38764067/109068519-fbf3a780-7715-11eb-8b3d-0dc32f898725.png) + The text says the share button will be on the left of the messagebox once enabled. However, it actually is on the right. - Correct ignored message CSS ([#20928](https://github.com/RocketChat/Rocket.Chat/pull/20928) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) @@ -4039,13 +3977,13 @@ - Custom emojis to override default ([#20359](https://github.com/RocketChat/Rocket.Chat/pull/20359) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) - Due to the sequence of the imports and how the emojiRenderer prioritizes lists, the custom emojis could not override the emojione emojis. Making two small changes fixed the issue. - - With the custom emoji for `:facepalm:` added, you can check out the result below: - ### Before - ![Screenshot from 2021-01-25 02-20-04](https://user-images.githubusercontent.com/38764067/105643088-dfb0e080-5eb3-11eb-8a00-582c53fbe9a4.png) - - ### After + Due to the sequence of the imports and how the emojiRenderer prioritizes lists, the custom emojis could not override the emojione emojis. Making two small changes fixed the issue. + + With the custom emoji for `:facepalm:` added, you can check out the result below: + ### Before + ![Screenshot from 2021-01-25 02-20-04](https://user-images.githubusercontent.com/38764067/105643088-dfb0e080-5eb3-11eb-8a00-582c53fbe9a4.png) + + ### After ![Screenshot from 2021-01-25 02-18-58](https://user-images.githubusercontent.com/38764067/105643076-cdcf3d80-5eb3-11eb-84b8-5dbc4f1135df.png) - Empty URL in user avatar doesn't show error and enables save ([#20440](https://github.com/RocketChat/Rocket.Chat/pull/20440) by [@im-adithya](https://github.com/im-adithya)) @@ -4058,12 +3996,12 @@ - Fix the search list showing the last channel ([#21160](https://github.com/RocketChat/Rocket.Chat/pull/21160) by [@shrinish123](https://github.com/shrinish123)) - The search list now also properly shows the last channel - Before : - - ![searchlist](https://user-images.githubusercontent.com/56491104/111471487-f3a7ee80-874e-11eb-9c6e-19bbf0731d60.png) - - After : + The search list now also properly shows the last channel + Before : + + ![searchlist](https://user-images.githubusercontent.com/56491104/111471487-f3a7ee80-874e-11eb-9c6e-19bbf0731d60.png) + + After : ![search_final](https://user-images.githubusercontent.com/56491104/111471521-fe628380-874e-11eb-8fa3-d1edb57587e1.png) - Follow thread action on threads list ([#20881](https://github.com/RocketChat/Rocket.Chat/pull/20881)) @@ -4088,13 +4026,13 @@ - Multi Select isn't working in Export Messages ([#21236](https://github.com/RocketChat/Rocket.Chat/pull/21236) by [@PriyaBihani](https://github.com/PriyaBihani)) - While exporting messages, we were not able to select multiple Users like this: - - https://user-images.githubusercontent.com/69837339/111953057-169a2000-8b0c-11eb-94a4-0e1657683f96.mp4 - - Now we can select multiple users: - - + While exporting messages, we were not able to select multiple Users like this: + + https://user-images.githubusercontent.com/69837339/111953057-169a2000-8b0c-11eb-94a4-0e1657683f96.mp4 + + Now we can select multiple users: + + https://user-images.githubusercontent.com/69837339/111953097-274a9600-8b0c-11eb-9177-bec388b042bd.mp4 - New Channel popover not closing ([#21080](https://github.com/RocketChat/Rocket.Chat/pull/21080)) @@ -4103,31 +4041,31 @@ - OEmbedURLWidget - Show Full Embedded Text Description ([#20569](https://github.com/RocketChat/Rocket.Chat/pull/20569) by [@aditya-mitra](https://github.com/aditya-mitra)) - Embeds were cutoff when either _urls had a long description_. - This was handled by removing `overflow:hidden;text-overflow:ellipsis;` from the inline styles in [`oembedUrlWidget.html`](https://github.com/RocketChat/Rocket.Chat/blob/develop/app/oembed/client/oembedUrlWidget.html#L28). - - ### Earlier - - ![earlier](https://user-images.githubusercontent.com/55396651/107110825-00dcde00-6871-11eb-866e-13cabc5b0d05.png) - - ### Now - + Embeds were cutoff when either _urls had a long description_. + This was handled by removing `overflow:hidden;text-overflow:ellipsis;` from the inline styles in [`oembedUrlWidget.html`](https://github.com/RocketChat/Rocket.Chat/blob/develop/app/oembed/client/oembedUrlWidget.html#L28). + + ### Earlier + + ![earlier](https://user-images.githubusercontent.com/55396651/107110825-00dcde00-6871-11eb-866e-13cabc5b0d05.png) + + ### Now + ![now](https://user-images.githubusercontent.com/55396651/107110794-ca06c800-6870-11eb-9b3b-168679936612.png) - Reactions list showing users in reactions option of message action. ([#20753](https://github.com/RocketChat/Rocket.Chat/pull/20753) by [@Darshilp326](https://github.com/Darshilp326)) - Reactions list shows emojis with respected users who have reacted with that emoji. - + Reactions list shows emojis with respected users who have reacted with that emoji. + https://user-images.githubusercontent.com/55157259/107857609-5870e000-6e55-11eb-8137-494a9f71b171.mp4 - Removing truncation from profile ([#20352](https://github.com/RocketChat/Rocket.Chat/pull/20352) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) - Truncating text in profile view was making some information completely inaccessible. Removed it from the user status and the custom fields where if the information is longer, the user would actually want to see all of it. - - ### Before - ![Screenshot from 2021-01-24 20-54-44](https://user-images.githubusercontent.com/38764067/105634935-7e264d00-5e86-11eb-8a6c-9f2a363e0f6c.png) - - ### After + Truncating text in profile view was making some information completely inaccessible. Removed it from the user status and the custom fields where if the information is longer, the user would actually want to see all of it. + + ### Before + ![Screenshot from 2021-01-24 20-54-44](https://user-images.githubusercontent.com/38764067/105634935-7e264d00-5e86-11eb-8a6c-9f2a363e0f6c.png) + + ### After ![Screenshot from 2021-01-24 20-54-06](https://user-images.githubusercontent.com/38764067/105634940-82eb0100-5e86-11eb-8b90-e97a43c5e938.png) - Replace wrong field description on Room Information panel ([#21395](https://github.com/RocketChat/Rocket.Chat/pull/21395) by [@rafaelblink](https://github.com/rafaelblink)) @@ -4138,8 +4076,8 @@ - Set establishing to false if OTR timeouts ([#21183](https://github.com/RocketChat/Rocket.Chat/pull/21183) by [@Darshilp326](https://github.com/Darshilp326)) - Set establishing false if OTR timeouts. - + Set establishing false if OTR timeouts. + https://user-images.githubusercontent.com/55157259/111617086-b30cab80-8808-11eb-8740-3b4ffacfc322.mp4 - Sidebar scroll missing full height ([#21071](https://github.com/RocketChat/Rocket.Chat/pull/21071)) @@ -4178,33 +4116,20 @@ - Chore: Add tests for Meteor methods ([#20901](https://github.com/RocketChat/Rocket.Chat/pull/20901)) - Add end-to-end tests for the following meteor methods - - - - [x] public-settings:get - - - [x] rooms:get - - - [x] subscriptions:get - - - [x] permissions:get - - - [x] loadMissedMessages - - - [x] loadHistory - - - [x] listCustomUserStatus - - - [x] getUserRoles - - - [x] getRoomRoles (called by the API, already covered) - - - [x] getMessages - - - [x] getUsersOfRoom - - - [x] loadNextMessages - + Add end-to-end tests for the following meteor methods + + - [x] public-settings:get + - [x] rooms:get + - [x] subscriptions:get + - [x] permissions:get + - [x] loadMissedMessages + - [x] loadHistory + - [x] listCustomUserStatus + - [x] getUserRoles + - [x] getRoomRoles (called by the API, already covered) + - [x] getMessages + - [x] getUsersOfRoom + - [x] loadNextMessages - [x] getThreadMessages - Chore: Meteor update 2.1 ([#21061](https://github.com/RocketChat/Rocket.Chat/pull/21061)) @@ -4217,10 +4142,8 @@ - Improve: Increase testing coverage ([#21015](https://github.com/RocketChat/Rocket.Chat/pull/21015)) - Add test for - - - settings/raw - + Add test for + - settings/raw - minimongo/comparisons - Improve: NPS survey fetch ([#21263](https://github.com/RocketChat/Rocket.Chat/pull/21263)) @@ -4239,19 +4162,17 @@ - Regression: Add scope to permission checks in Team's endpoints ([#21369](https://github.com/RocketChat/Rocket.Chat/pull/21369)) - - Include scope (team's main room ID) in the permission checks; + - Include scope (team's main room ID) in the permission checks; - Remove the `teamName` parameter from the `members`, `addMembers`, `updateMember` and `removeMembers` methods (since `teamId` will always be defined). - Regression: Add support to filter on `teams.listRooms` endpoint ([#21327](https://github.com/RocketChat/Rocket.Chat/pull/21327)) - - Add support for queries (within the `query` parameter); - + - Add support for queries (within the `query` parameter); - Add support to pagination (`offset` and `count`) when an user doesn't have the permission to get all rooms. - Regression: Add teams support to directory ([#21351](https://github.com/RocketChat/Rocket.Chat/pull/21351)) - - Change `directory.js` to reduce function complexity - + - Change `directory.js` to reduce function complexity - Add `teams` type of item. Directory will return all public teams & private teams the user is part of. - Regression: add view room action on Teams Channels ([#21295](https://github.com/RocketChat/Rocket.Chat/pull/21295)) @@ -4304,19 +4225,18 @@ - Regression: Quick action button missing for Omnichannel On-Hold queue ([#21285](https://github.com/RocketChat/Rocket.Chat/pull/21285)) - - Move the Manual On Hold button to the new Omnichannel Header - ![image](https://user-images.githubusercontent.com/34130764/112291749-6ae10380-8cb6-11eb-94cd-e05efc14b1bf.png) - ![image](https://user-images.githubusercontent.com/34130764/112304146-27d95d00-8cc3-11eb-85db-dde04a110dd1.png) - - + - Move the Manual On Hold button to the new Omnichannel Header + ![image](https://user-images.githubusercontent.com/34130764/112291749-6ae10380-8cb6-11eb-94cd-e05efc14b1bf.png) + ![image](https://user-images.githubusercontent.com/34130764/112304146-27d95d00-8cc3-11eb-85db-dde04a110dd1.png) + - Minor fixes - regression: Remove Breadcrumbs and update Tag component ([#21399](https://github.com/RocketChat/Rocket.Chat/pull/21399)) - Regression: Remove channel action on add channel's modal don't work ([#21356](https://github.com/RocketChat/Rocket.Chat/pull/21356)) - ![removechannel-on-add-existing-modal](https://user-images.githubusercontent.com/27704687/112911017-eda8fa80-90ca-11eb-9c24-47a70be0c314.gif) - + ![removechannel-on-add-existing-modal](https://user-images.githubusercontent.com/27704687/112911017-eda8fa80-90ca-11eb-9c24-47a70be0c314.gif) + ![image](https://user-images.githubusercontent.com/27704687/112911052-02858e00-90cb-11eb-85a2-0ef1f5f9ffd9.png) - Regression: Remove primary color from button in TeamChannels component ([#21293](https://github.com/RocketChat/Rocket.Chat/pull/21293)) @@ -4345,10 +4265,10 @@ - Regression: Unify Contact information displayed on the Room header and Room Info ([#21312](https://github.com/RocketChat/Rocket.Chat/pull/21312) by [@rafaelblink](https://github.com/rafaelblink)) - ![image](https://user-images.githubusercontent.com/34130764/112586659-35592900-8e22-11eb-94be-32bdff7ca883.png) - - ![image](https://user-images.githubusercontent.com/2493803/112913130-788bf400-90cf-11eb-84c6-782b203e100a.png) - + ![image](https://user-images.githubusercontent.com/34130764/112586659-35592900-8e22-11eb-94be-32bdff7ca883.png) + + ![image](https://user-images.githubusercontent.com/2493803/112913130-788bf400-90cf-11eb-84c6-782b203e100a.png) + ![image](https://user-images.githubusercontent.com/2493803/112913146-817cc580-90cf-11eb-87ad-ef79766be2b3.png) - Regression: Unify team actions to add a room to a team ([#21386](https://github.com/RocketChat/Rocket.Chat/pull/21386)) @@ -4357,10 +4277,8 @@ - Regression: Update .invite endpoints to support multiple users at once ([#21328](https://github.com/RocketChat/Rocket.Chat/pull/21328)) - - channels.invite now supports passing an array as a param (either with usernames or userIds) via `usernames` or `userIds` properties. - - - You can still use the endpoint to invite only one user via the old params `userId`, `username` or `user`. - + - channels.invite now supports passing an array as a param (either with usernames or userIds) via `usernames` or `userIds` properties. + - You can still use the endpoint to invite only one user via the old params `userId`, `username` or `user`. - Same changes apply to groups.invite - Regression: user actions in admin ([#21307](https://github.com/RocketChat/Rocket.Chat/pull/21307)) @@ -4495,7 +4413,7 @@ - Close Call contextual bar after starting jitsi call. ([#21004](https://github.com/RocketChat/Rocket.Chat/pull/21004) by [@yash-rajpal](https://github.com/yash-rajpal)) - After jitsi call is started, if the call is started in a new window then we should close contextual tab bar. + After jitsi call is started, if the call is started in a new window then we should close contextual tab bar. So, when 'YES' is pressed on modal, we call handleClose function if openNewWindow is true, as call doesn't starts on tab bar, it starts on new window. ### 🐛 Bug fixes @@ -4505,7 +4423,7 @@ - Stopping Jitsi reload ([#20973](https://github.com/RocketChat/Rocket.Chat/pull/20973) by [@yash-rajpal](https://github.com/yash-rajpal)) - The Function where Jitsi call is started gets called many times due to `room.usernames` dep of useMemo, this dep triggers reloading of this function many times. + The Function where Jitsi call is started gets called many times due to `room.usernames` dep of useMemo, this dep triggers reloading of this function many times. So removing this dep from useMemo dependencies ### 👩‍💻👨‍💻 Contributors 😍 @@ -4533,10 +4451,10 @@ - Cloud Workspace bridge ([#20838](https://github.com/RocketChat/Rocket.Chat/pull/20838)) - Adds the new CloudWorkspace functionality. - - It allows apps to request the access token for the workspace it's installed on, so it can perform actions with other Rocket.Chat services, such as the Omni Gateway. - + Adds the new CloudWorkspace functionality. + + It allows apps to request the access token for the workspace it's installed on, so it can perform actions with other Rocket.Chat services, such as the Omni Gateway. + https://github.com/RocketChat/Rocket.Chat.Apps-engine/pull/382 - Header with Breadcrumbs ([#20609](https://github.com/RocketChat/Rocket.Chat/pull/20609)) @@ -4554,10 +4472,10 @@ - Add symbol to indicate apps' required settings in the UI ([#20447](https://github.com/RocketChat/Rocket.Chat/pull/20447)) - - Apps are able to define **required** settings. These settings should not be left blank by the user and an error will be thrown and shown in the interface if an user attempts to save changes in the app details page leaving any required fields blank; - ![prt_screen_required_app_settings_warning](https://user-images.githubusercontent.com/36537004/106032964-e73cd900-60af-11eb-8eab-c11fd651b593.png) - - - A sign (*) is added to the label of app settings' fields that are required so as to highlight the fields which must not be left blank. + - Apps are able to define **required** settings. These settings should not be left blank by the user and an error will be thrown and shown in the interface if an user attempts to save changes in the app details page leaving any required fields blank; + ![prt_screen_required_app_settings_warning](https://user-images.githubusercontent.com/36537004/106032964-e73cd900-60af-11eb-8eab-c11fd651b593.png) + + - A sign (*) is added to the label of app settings' fields that are required so as to highlight the fields which must not be left blank. ![prt_screen_required_app_settings](https://user-images.githubusercontent.com/36537004/106014879-ae473900-609c-11eb-9b9e-95de7bbf20a5.png) - Add visual validation on users admin forms ([#20308](https://github.com/RocketChat/Rocket.Chat/pull/20308)) @@ -4578,20 +4496,20 @@ - Adds tooltip for sidebar header icons ([#19934](https://github.com/RocketChat/Rocket.Chat/pull/19934) by [@RonLek](https://github.com/RonLek)) - Previously the header icons in the sidebar didn't show a tooltip when hovered over. This PR fixes that. - + Previously the header icons in the sidebar didn't show a tooltip when hovered over. This PR fixes that. + ![Screenshot from 2020-12-22 15-17-41](https://user-images.githubusercontent.com/28918901/102874804-f2756700-4468-11eb-8324-b7f3194e62fe.png) - Better Presentation of Blockquotes ([#20750](https://github.com/RocketChat/Rocket.Chat/pull/20750) by [@aditya-mitra](https://github.com/aditya-mitra)) - Changed the values of `margin-top` and `margin-bottom` for *first* and *last* childs in blockquotes to increase readability. - - ### Before - - ![before](https://user-images.githubusercontent.com/55396651/107858662-3e3a0080-6e5b-11eb-8274-9bd956807235.png) - - ### Now - + Changed the values of `margin-top` and `margin-bottom` for *first* and *last* childs in blockquotes to increase readability. + + ### Before + + ![before](https://user-images.githubusercontent.com/55396651/107858662-3e3a0080-6e5b-11eb-8274-9bd956807235.png) + + ### Now + ![now](https://user-images.githubusercontent.com/55396651/107858471-480f3400-6e5a-11eb-9ccb-3f1be2fed0a4.png) - Change header based on room type ([#20612](https://github.com/RocketChat/Rocket.Chat/pull/20612)) @@ -4612,18 +4530,13 @@ - Replace react-window for react-virtuoso package ([#20392](https://github.com/RocketChat/Rocket.Chat/pull/20392)) - Remove: - - - react-window - - - react-window-infinite-loader - - - simplebar-react - - Include: - - - react-virtuoso - + Remove: + - react-window + - react-window-infinite-loader + - simplebar-react + + Include: + - react-virtuoso - rc-scrollbars - Rewrite Call as React component ([#19778](https://github.com/RocketChat/Rocket.Chat/pull/19778)) @@ -4639,13 +4552,13 @@ - Add debouncing to add users search field. ([#20297](https://github.com/RocketChat/Rocket.Chat/pull/20297) by [@Darshilp326](https://github.com/Darshilp326)) - BEFORE - - https://user-images.githubusercontent.com/55157259/105350722-98a3c080-5c11-11eb-82f3-d9a62a4fa50b.mp4 - - - AFTER - + BEFORE + + https://user-images.githubusercontent.com/55157259/105350722-98a3c080-5c11-11eb-82f3-d9a62a4fa50b.mp4 + + + AFTER + https://user-images.githubusercontent.com/55157259/105350757-a2c5bf00-5c11-11eb-91db-25c0b9e01a28.mp4 - Add tooltips to Thread header buttons ([#20456](https://github.com/RocketChat/Rocket.Chat/pull/20456) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) @@ -4658,8 +4571,8 @@ - Added check for view admin permission page ([#20403](https://github.com/RocketChat/Rocket.Chat/pull/20403) by [@yash-rajpal](https://github.com/yash-rajpal)) - Admin Permission page was visible to all, if you add admin/permissions after the base url. This should not be visible to all user, only people with certain permissions should be able to see this page. - I am also able to see permissions page for open workspace of Rocket chat. + Admin Permission page was visible to all, if you add admin/permissions after the base url. This should not be visible to all user, only people with certain permissions should be able to see this page. + I am also able to see permissions page for open workspace of Rocket chat. ![image](https://user-images.githubusercontent.com/58601732/105829728-bfd00880-5fea-11eb-9121-6c53a752f140.png) - Adding the accidentally deleted tag template, used by other templates ([#20772](https://github.com/RocketChat/Rocket.Chat/pull/20772) by [@yash-rajpal](https://github.com/yash-rajpal)) @@ -4668,8 +4581,8 @@ - Admin cannot clear user details like bio or nickname ([#20785](https://github.com/RocketChat/Rocket.Chat/pull/20785) by [@yash-rajpal](https://github.com/yash-rajpal)) - When the API users.update is called to update user data, it passes data to saveUser function. Here before saving data like bio or nickname we are checking if they are available or not. If data is available then we are saving it, but we are not doing anything when data isn't available. - + When the API users.update is called to update user data, it passes data to saveUser function. Here before saving data like bio or nickname we are checking if they are available or not. If data is available then we are saving it, but we are not doing anything when data isn't available. + So unsetting data if data isn't available to save. Will also fix bio and other fields. :) - Admin Panel pages not visible in Safari ([#20912](https://github.com/RocketChat/Rocket.Chat/pull/20912)) @@ -4686,24 +4599,24 @@ - Blank Personal Access Token Bug ([#20193](https://github.com/RocketChat/Rocket.Chat/pull/20193) by [@RonLek](https://github.com/RonLek)) - Adds error when personal access token is blank thereby disallowing the creation of one. - + Adds error when personal access token is blank thereby disallowing the creation of one. + https://user-images.githubusercontent.com/28918901/104483631-5adde100-55ee-11eb-9938-64146bce127e.mp4 - CAS login failing due to TOTP requirement ([#20840](https://github.com/RocketChat/Rocket.Chat/pull/20840)) - Changed password input field for password access in edit room info. ([#20356](https://github.com/RocketChat/Rocket.Chat/pull/20356) by [@Darshilp326](https://github.com/Darshilp326)) - Password field would be secured with asterisks in edit room info - - https://user-images.githubusercontent.com/55157259/105641758-cad04f00-5eab-11eb-90de-0c91263edd55.mp4 - + Password field would be secured with asterisks in edit room info + + https://user-images.githubusercontent.com/55157259/105641758-cad04f00-5eab-11eb-90de-0c91263edd55.mp4 + . - Channel mentions showing user subscribed channels twice ([#20484](https://github.com/RocketChat/Rocket.Chat/pull/20484) by [@Darshilp326](https://github.com/Darshilp326)) - Channel mention shows user subscribed channels twice. - + Channel mention shows user subscribed channels twice. + https://user-images.githubusercontent.com/55157259/106183033-b353d780-61c5-11eb-8aab-1dbb62b02ff8.mp4 - CORS config not accepting multiple origins ([#20696](https://github.com/RocketChat/Rocket.Chat/pull/20696) by [@g-thome](https://github.com/g-thome)) @@ -4714,26 +4627,26 @@ - Default Attachments - Remove Extra Margin in Field Attachments ([#20618](https://github.com/RocketChat/Rocket.Chat/pull/20618) by [@aditya-mitra](https://github.com/aditya-mitra)) - A large amount of unnecessary margin which existed in the **Field Attachments inside the `DefaultAttachments`** has been fixed. - - ### Earlier - - ![earlier](https://user-images.githubusercontent.com/55396651/107056792-ba4b9d00-67f8-11eb-9153-05281416cddb.png) - - ### Now - + A large amount of unnecessary margin which existed in the **Field Attachments inside the `DefaultAttachments`** has been fixed. + + ### Earlier + + ![earlier](https://user-images.githubusercontent.com/55396651/107056792-ba4b9d00-67f8-11eb-9153-05281416cddb.png) + + ### Now + ![now](https://user-images.githubusercontent.com/55396651/107057196-3219c780-67f9-11eb-84db-e4a0addfc168.png) - Default Attachments - Show Full Attachment.Text with Markdown ([#20606](https://github.com/RocketChat/Rocket.Chat/pull/20606) by [@aditya-mitra](https://github.com/aditya-mitra)) - Removed truncating of text in `Attachment.Text`. - Added `Attachment.Text` to be parsed to markdown by default. - - ### Earlier - ![earlier](https://user-images.githubusercontent.com/55396651/106910781-92d8cf80-6727-11eb-82ec-818df7544ff0.png) - - ### Now - + Removed truncating of text in `Attachment.Text`. + Added `Attachment.Text` to be parsed to markdown by default. + + ### Earlier + ![earlier](https://user-images.githubusercontent.com/55396651/106910781-92d8cf80-6727-11eb-82ec-818df7544ff0.png) + + ### Now + ![now](https://user-images.githubusercontent.com/55396651/106910840-a126eb80-6727-11eb-8bd6-d86383dd9181.png) - Don't ask again not rendering ([#20745](https://github.com/RocketChat/Rocket.Chat/pull/20745)) @@ -4754,21 +4667,21 @@ - Feedback on bulk invite ([#20339](https://github.com/RocketChat/Rocket.Chat/pull/20339) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) - Resolved structure where no response was being received. Changed from callback to async/await. - Added error in case of empty submission, or if no valid emails were found. - + Resolved structure where no response was being received. Changed from callback to async/await. + Added error in case of empty submission, or if no valid emails were found. + https://user-images.githubusercontent.com/38764067/105613964-dfe5a900-5deb-11eb-80f2-21fc8dee57c0.mp4 - Filters are not being applied correctly in Omnichannel Current Chats list ([#20320](https://github.com/RocketChat/Rocket.Chat/pull/20320) by [@rafaelblink](https://github.com/rafaelblink)) - ### Before - ![image](https://user-images.githubusercontent.com/2493803/105537672-082cb500-5cd1-11eb-8f1b-1726ba60420a.png) - - ### After - ![image](https://user-images.githubusercontent.com/2493803/105537773-2d212800-5cd1-11eb-8746-048deb9502d9.png) - - ![image](https://user-images.githubusercontent.com/2493803/106494728-88090b00-6499-11eb-922e-5386107e2389.png) - + ### Before + ![image](https://user-images.githubusercontent.com/2493803/105537672-082cb500-5cd1-11eb-8f1b-1726ba60420a.png) + + ### After + ![image](https://user-images.githubusercontent.com/2493803/105537773-2d212800-5cd1-11eb-8746-048deb9502d9.png) + + ![image](https://user-images.githubusercontent.com/2493803/106494728-88090b00-6499-11eb-922e-5386107e2389.png) + ![image](https://user-images.githubusercontent.com/2493803/106494751-90f9dc80-6499-11eb-901b-5e4dbdc678ba.png) - Fix Empty highlighted words field ([#20329](https://github.com/RocketChat/Rocket.Chat/pull/20329) by [@yash-rajpal](https://github.com/yash-rajpal)) @@ -4797,11 +4710,11 @@ - List of Omnichannel triggers is not listing data ([#20624](https://github.com/RocketChat/Rocket.Chat/pull/20624) by [@rafaelblink](https://github.com/rafaelblink)) - ### Before - ![image](https://user-images.githubusercontent.com/2493803/107095379-7308e080-67e7-11eb-8251-7e7ff891087a.png) - - - ### After + ### Before + ![image](https://user-images.githubusercontent.com/2493803/107095379-7308e080-67e7-11eb-8251-7e7ff891087a.png) + + + ### After ![image](https://user-images.githubusercontent.com/2493803/107095261-3b019d80-67e7-11eb-8425-8612b03ac50a.png) - Livechat bridge permission checkers ([#20653](https://github.com/RocketChat/Rocket.Chat/pull/20653) by [@lolimay](https://github.com/lolimay)) @@ -4824,8 +4737,7 @@ - Missing setting to control when to send the ReplyTo field in email notifications ([#20744](https://github.com/RocketChat/Rocket.Chat/pull/20744)) - - Add a new setting ("Add Reply-To header") in the Email settings' page to control when the Reply-To header is used in e-mail notifications; - + - Add a new setting ("Add Reply-To header") in the Email settings' page to control when the Reply-To header is used in e-mail notifications; - The new setting is turned off (`false` value) by default. - New Integration page was not being displayed ([#20670](https://github.com/RocketChat/Rocket.Chat/pull/20670) by [@yash-rajpal](https://github.com/yash-rajpal)) @@ -4858,15 +4770,15 @@ - Remove duplicate getCommonRoomEvents() event binding for starredMessages ([#20185](https://github.com/RocketChat/Rocket.Chat/pull/20185) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) - The getCommonRoomEvents() returned functions were bound to the starredMessages template twice. This was causing some bugs, as detailed in the Issue mentioned below. + The getCommonRoomEvents() returned functions were bound to the starredMessages template twice. This was causing some bugs, as detailed in the Issue mentioned below. I removed the top events call that only bound the getCommonRoomEvents(). Therefore, only one call for the same is left, which is at the end of the file. Having the events bound just once removes the bugs mentioned. - Remove warning problems from console ([#20800](https://github.com/RocketChat/Rocket.Chat/pull/20800)) - Removed tooltip in kebab menu options. ([#20498](https://github.com/RocketChat/Rocket.Chat/pull/20498) by [@Darshilp326](https://github.com/Darshilp326)) - Removed tooltip as it was not needed. - + Removed tooltip as it was not needed. + https://user-images.githubusercontent.com/55157259/106246146-a53ca000-6233-11eb-9874-cbd1b4331bc0.mp4 - Retry icon comes out of the div ([#20390](https://github.com/RocketChat/Rocket.Chat/pull/20390) by [@im-adithya](https://github.com/im-adithya)) @@ -4881,8 +4793,8 @@ - Room's last message's update date format on IE ([#20680](https://github.com/RocketChat/Rocket.Chat/pull/20680)) - The proposed change fixes a bug when updates the cached records on Internet Explorer and it breaks the sidebar as shown on the screenshot below: - + The proposed change fixes a bug when updates the cached records on Internet Explorer and it breaks the sidebar as shown on the screenshot below: + ![image](https://user-images.githubusercontent.com/27704687/107578007-f2285b00-6bd1-11eb-9250-1e76ae67f9c9.png) - Save user password and email from My Account ([#20737](https://github.com/RocketChat/Rocket.Chat/pull/20737)) @@ -4891,8 +4803,8 @@ - Selected hide system messages would now be viewed in vertical bar. ([#20358](https://github.com/RocketChat/Rocket.Chat/pull/20358) by [@Darshilp326](https://github.com/Darshilp326)) - All selected hide system messages are now in vertical Bar. - + All selected hide system messages are now in vertical Bar. + https://user-images.githubusercontent.com/55157259/105642624-d5411780-5eb0-11eb-8848-93e4b02629cb.mp4 - Selected messages don't get unselected ([#20408](https://github.com/RocketChat/Rocket.Chat/pull/20408) by [@im-adithya](https://github.com/im-adithya)) @@ -4907,22 +4819,14 @@ - Several Slack Importer issues ([#20216](https://github.com/RocketChat/Rocket.Chat/pull/20216)) - - Fix: Slack Importer crashes when importing a large users.json file - - - Fix: Slack importer crashes when messages have invalid mentions - - - Skip listing all users on the preparation screen when the user count is too large. - - - Split avatar download into a separate process. - - - Update room's last message when the import is complete. - - - Prevent invalid or duplicated channel names - - - Improve message error handling. - - - Reduce max allowed BSON size to avoid possible issues in some servers. - + - Fix: Slack Importer crashes when importing a large users.json file + - Fix: Slack importer crashes when messages have invalid mentions + - Skip listing all users on the preparation screen when the user count is too large. + - Split avatar download into a separate process. + - Update room's last message when the import is complete. + - Prevent invalid or duplicated channel names + - Improve message error handling. + - Reduce max allowed BSON size to avoid possible issues in some servers. - Improve handling of very large channel files. - star icon was visible after unstarring a message ([#19645](https://github.com/RocketChat/Rocket.Chat/pull/19645) by [@bhavayAnand9](https://github.com/bhavayAnand9)) @@ -4941,15 +4845,15 @@ - User statuses in admin user info panel ([#20341](https://github.com/RocketChat/Rocket.Chat/pull/20341) by [@RonLek](https://github.com/RonLek)) - Modifies user statuses in admin info panel based on their actual status instead of their `statusConnection`. This enables correct and consistent change in user statuses. - Also, bot users having status as online were classified as offline, with this change they are now correctly classified based on their corresponding statuses. - + Modifies user statuses in admin info panel based on their actual status instead of their `statusConnection`. This enables correct and consistent change in user statuses. + Also, bot users having status as online were classified as offline, with this change they are now correctly classified based on their corresponding statuses. + https://user-images.githubusercontent.com/28918901/105624438-b8bcc500-5e47-11eb-8d1e-3a4180da1304.mp4 - Users autocomplete showing duplicated results ([#20481](https://github.com/RocketChat/Rocket.Chat/pull/20481) by [@Darshilp326](https://github.com/Darshilp326)) - Added new query for outside room users so that room members are not shown twice. - + Added new query for outside room users so that room members are not shown twice. + https://user-images.githubusercontent.com/55157259/106174582-33c10b00-61bb-11eb-9716-377ef7bba34e.mp4
@@ -4970,7 +4874,7 @@ - Chore: Disable Sessions Aggregates tests locally ([#20607](https://github.com/RocketChat/Rocket.Chat/pull/20607)) - Disable Session aggregates tests in local environments + Disable Session aggregates tests in local environments For context, refer to: #20161 - Chore: Improve performance of messages’ watcher ([#20519](https://github.com/RocketChat/Rocket.Chat/pull/20519)) @@ -5179,20 +5083,18 @@ - **ENTERPRISE:** Omnichannel Contact Manager as preferred agent for routing ([#20244](https://github.com/RocketChat/Rocket.Chat/pull/20244)) - If the `Contact-Manager` is assigned to a Visitor, the chat will automatically get transferred to the respective Contact-Manager, provided the Contact-Manager is online. In-case the Contact-Manager is offline, the chat will be transferred to any other online agent. - We have provided a setting to control this auto-assignment feature - ![image](https://user-images.githubusercontent.com/34130764/104880961-8104d780-5986-11eb-9d87-82b99814b028.png) - - Behavior based-on Routing method - - - 1. Auto-selection, Load-Balancing, or External Service (`autoAssignAgent = true`) - This is straightforward, - - if the Contact-manager is online, the chat will be transferred to the Contact-Manger only - - if the Contact-manager is offline, the chat will be transferred to any other online-agent based on the Routing system - - 2. Manual-selection (`autoAssignAgent = false`) - - If the Contact-Manager is online, the chat will appear in the Queue of Contact-Manager **ONLY** + If the `Contact-Manager` is assigned to a Visitor, the chat will automatically get transferred to the respective Contact-Manager, provided the Contact-Manager is online. In-case the Contact-Manager is offline, the chat will be transferred to any other online agent. + We have provided a setting to control this auto-assignment feature + ![image](https://user-images.githubusercontent.com/34130764/104880961-8104d780-5986-11eb-9d87-82b99814b028.png) + + Behavior based-on Routing method + + 1. Auto-selection, Load-Balancing, or External Service (`autoAssignAgent = true`) + This is straightforward, + - if the Contact-manager is online, the chat will be transferred to the Contact-Manger only + - if the Contact-manager is offline, the chat will be transferred to any other online-agent based on the Routing system + 2. Manual-selection (`autoAssignAgent = false`) + - If the Contact-Manager is online, the chat will appear in the Queue of Contact-Manager **ONLY** - If the Contact-Manager is offline, the chat will appear in the Queue of all related Agents/Manager ( like it's done right now ) - Banner system and NPS ([#20221](https://github.com/RocketChat/Rocket.Chat/pull/20221)) @@ -5201,34 +5103,34 @@ - Email Inboxes for Omnichannel ([#20101](https://github.com/RocketChat/Rocket.Chat/pull/20101) by [@rafaelblink](https://github.com/rafaelblink)) - With this new feature, email accounts will receive email messages(threads) which will be transformed into Omnichannel chats. It'll be possible to set up multiple email accounts, test the connection with email server(email provider) and define the behaviour of each account. - - https://user-images.githubusercontent.com/2493803/105430398-242d4980-5c32-11eb-835a-450c94837d23.mp4 - - ### New item on admin menu - - ![image](https://user-images.githubusercontent.com/2493803/105428723-bc293400-5c2e-11eb-8c02-e8d36ea82726.png) - - - ### Send test email tooltip - - ![image](https://user-images.githubusercontent.com/2493803/104366986-eaa16380-54f8-11eb-9ba7-831cfde2319c.png) - - - ### Inbox Info - - ![image](https://user-images.githubusercontent.com/2493803/104366796-ab731280-54f8-11eb-9941-a3cc8eb610e1.png) - - ### SMTP Info - - ![image](https://user-images.githubusercontent.com/2493803/104366868-c47bc380-54f8-11eb-969e-ccc29070957c.png) - - ### IMAP Info - - ![image](https://user-images.githubusercontent.com/2493803/104366897-cd6c9500-54f8-11eb-80c4-97d5b0c002d5.png) - - ### Messages - + With this new feature, email accounts will receive email messages(threads) which will be transformed into Omnichannel chats. It'll be possible to set up multiple email accounts, test the connection with email server(email provider) and define the behaviour of each account. + + https://user-images.githubusercontent.com/2493803/105430398-242d4980-5c32-11eb-835a-450c94837d23.mp4 + + ### New item on admin menu + + ![image](https://user-images.githubusercontent.com/2493803/105428723-bc293400-5c2e-11eb-8c02-e8d36ea82726.png) + + + ### Send test email tooltip + + ![image](https://user-images.githubusercontent.com/2493803/104366986-eaa16380-54f8-11eb-9ba7-831cfde2319c.png) + + + ### Inbox Info + + ![image](https://user-images.githubusercontent.com/2493803/104366796-ab731280-54f8-11eb-9941-a3cc8eb610e1.png) + + ### SMTP Info + + ![image](https://user-images.githubusercontent.com/2493803/104366868-c47bc380-54f8-11eb-969e-ccc29070957c.png) + + ### IMAP Info + + ![image](https://user-images.githubusercontent.com/2493803/104366897-cd6c9500-54f8-11eb-80c4-97d5b0c002d5.png) + + ### Messages + ![image](https://user-images.githubusercontent.com/2493803/105428971-45d90180-5c2f-11eb-992a-022a3df94471.png) - Encrypted Discussions and new Encryption Permissions ([#20201](https://github.com/RocketChat/Rocket.Chat/pull/20201)) @@ -5240,7 +5142,7 @@ - Add extra SAML settings to update room subs and add private room subs. ([#19489](https://github.com/RocketChat/Rocket.Chat/pull/19489) by [@tlskinneriv](https://github.com/tlskinneriv)) - Added a SAML setting to support updating room subscriptions each time a user logs in via SAML. + Added a SAML setting to support updating room subscriptions each time a user logs in via SAML. Added a SAML setting to support including private rooms in SAML updated subscriptions (whether initial or on each logon). - Autofocus on directory ([#20509](https://github.com/RocketChat/Rocket.Chat/pull/20509)) @@ -5267,7 +5169,7 @@ - Tooltip added for Kebab menu on chat header ([#20116](https://github.com/RocketChat/Rocket.Chat/pull/20116) by [@yash-rajpal](https://github.com/yash-rajpal)) - Added the missing Tooltip for kebab menu on chat header. + Added the missing Tooltip for kebab menu on chat header. ![tooltip after](https://user-images.githubusercontent.com/58601732/104031406-b07f4b80-51f2-11eb-87a4-1e8da78a254f.gif) ### 🐛 Bug fixes @@ -5289,7 +5191,7 @@ - Added context check for closing active tabbar for member-list ([#20228](https://github.com/RocketChat/Rocket.Chat/pull/20228) by [@yash-rajpal](https://github.com/yash-rajpal)) - When we click on a username and then click on see user's full profile, a tab gets active and shows us the user's profile, the problem occurs when the tab is still active and we try to see another user's profile. In this case, tabbar gets closed. + When we click on a username and then click on see user's full profile, a tab gets active and shows us the user's profile, the problem occurs when the tab is still active and we try to see another user's profile. In this case, tabbar gets closed. To resolve this, added context check for closing action of active tabbar. - Added Margin between status bullet and status label ([#20199](https://github.com/RocketChat/Rocket.Chat/pull/20199) by [@yash-rajpal](https://github.com/yash-rajpal)) @@ -5298,8 +5200,8 @@ - Added success message on saving notification preference. ([#20220](https://github.com/RocketChat/Rocket.Chat/pull/20220) by [@Darshilp326](https://github.com/Darshilp326)) - Added success message after saving notification preferences. - + Added success message after saving notification preferences. + https://user-images.githubusercontent.com/55157259/104774617-03ca3e80-579d-11eb-8fa4-990b108dd8d9.mp4 - Admin User Info email verified status ([#20110](https://github.com/RocketChat/Rocket.Chat/pull/20110) by [@bdelwood](https://github.com/bdelwood)) @@ -5308,10 +5210,10 @@ - Change header's favorite icon to filled star ([#20174](https://github.com/RocketChat/Rocket.Chat/pull/20174)) - ### Before: - ![image](https://user-images.githubusercontent.com/27704687/104351819-a60bcd00-54e4-11eb-8b43-7d281a6e5dcb.png) - - ### After: + ### Before: + ![image](https://user-images.githubusercontent.com/27704687/104351819-a60bcd00-54e4-11eb-8b43-7d281a6e5dcb.png) + + ### After: ![image](https://user-images.githubusercontent.com/27704687/104351632-67761280-54e4-11eb-87ba-25b940494bb5.png) - Changed success message for adding custom sound. ([#20272](https://github.com/RocketChat/Rocket.Chat/pull/20272) by [@Darshilp326](https://github.com/Darshilp326)) @@ -5320,24 +5222,24 @@ - Changed success message for ignoring member. ([#19996](https://github.com/RocketChat/Rocket.Chat/pull/19996) by [@Darshilp326](https://github.com/Darshilp326)) - Different messages for ignoring/unignoring will be displayed. - + Different messages for ignoring/unignoring will be displayed. + https://user-images.githubusercontent.com/55157259/103310307-4241c880-4a3d-11eb-8c6c-4c9b99d023db.mp4 - Creation of Omnichannel rooms not working correctly through the Apps when the agent parameter is set ([#19997](https://github.com/RocketChat/Rocket.Chat/pull/19997)) - Engagement dashboard graphs labels superposing each other ([#20267](https://github.com/RocketChat/Rocket.Chat/pull/20267)) - Now after a certain breakpoint, the graphs should stack vertically, and overlapping text rotated. - + Now after a certain breakpoint, the graphs should stack vertically, and overlapping text rotated. + ![image](https://user-images.githubusercontent.com/40830821/105098926-93b40500-5a89-11eb-9a56-2fc3b1552914.png) - Fields overflowing page ([#20287](https://github.com/RocketChat/Rocket.Chat/pull/20287)) - ### Before - ![image](https://user-images.githubusercontent.com/40830821/105246952-c1b14c00-5b52-11eb-8671-cff88edf242d.png) - - ### After + ### Before + ![image](https://user-images.githubusercontent.com/40830821/105246952-c1b14c00-5b52-11eb-8671-cff88edf242d.png) + + ### After ![image](https://user-images.githubusercontent.com/40830821/105247125-0a690500-5b53-11eb-9f3c-d6a68108e336.png) - Fix error that occurs on changing archive status of room ([#20098](https://github.com/RocketChat/Rocket.Chat/pull/20098) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) @@ -5354,7 +5256,7 @@ - Livechat.RegisterGuest method removing unset fields ([#20124](https://github.com/RocketChat/Rocket.Chat/pull/20124) by [@rafaelblink](https://github.com/rafaelblink)) - After changes made on https://github.com/RocketChat/Rocket.Chat/pull/19931, the `Livechat.RegisterGuest` method started removing properties from the visitor inappropriately. The properties that did not receive value were removed from the object. + After changes made on https://github.com/RocketChat/Rocket.Chat/pull/19931, the `Livechat.RegisterGuest` method started removing properties from the visitor inappropriately. The properties that did not receive value were removed from the object. Those changes were made to support the new Contact Form, but now the form has its own method to deal with Contact data so those changes are no longer necessary. - Markdown added for Header Room topic ([#20021](https://github.com/RocketChat/Rocket.Chat/pull/20021) by [@yash-rajpal](https://github.com/yash-rajpal)) @@ -5375,18 +5277,18 @@ - Omnichannel - Contact Center form is not validating custom fields properly ([#20196](https://github.com/RocketChat/Rocket.Chat/pull/20196) by [@rafaelblink](https://github.com/rafaelblink)) - The contact form is accepting undefined values in required custom fields when creating or editing contacts, and, the errror message isn't following Rocket.chat design system. - - ### Before - ![image](https://user-images.githubusercontent.com/2493803/104522668-31688980-55dd-11eb-92c5-83f96073edc4.png) - - ### After - - #### New - ![image](https://user-images.githubusercontent.com/2493803/104770494-68f74300-574f-11eb-94a3-c8fd73365308.png) - - - #### Edit + The contact form is accepting undefined values in required custom fields when creating or editing contacts, and, the errror message isn't following Rocket.chat design system. + + ### Before + ![image](https://user-images.githubusercontent.com/2493803/104522668-31688980-55dd-11eb-92c5-83f96073edc4.png) + + ### After + + #### New + ![image](https://user-images.githubusercontent.com/2493803/104770494-68f74300-574f-11eb-94a3-c8fd73365308.png) + + + #### Edit ![image](https://user-images.githubusercontent.com/2493803/104770538-7b717c80-574f-11eb-829f-1ae304103369.png) - Omnichannel Agents unable to take new chats in the queue ([#20022](https://github.com/RocketChat/Rocket.Chat/pull/20022) by [@rafaelblink](https://github.com/rafaelblink)) @@ -5407,15 +5309,15 @@ - Room special name in prompts ([#20277](https://github.com/RocketChat/Rocket.Chat/pull/20277) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) - The "Hide room" and "Leave Room" confirmation prompts use the "name" key from the room info. When the setting " - Allow Special Characters in Room Names" is enabled, the prompts show the normalized names instead of those that contain the special characters. - - Changed the value being used from name to fname, which always has the user-set name. - - Previous: - ![Screenshot from 2021-01-20 15-52-29](https://user-images.githubusercontent.com/38764067/105161642-9b31e780-5b37-11eb-8b0c-ec4b1414c948.png) - - Updated: + The "Hide room" and "Leave Room" confirmation prompts use the "name" key from the room info. When the setting " + Allow Special Characters in Room Names" is enabled, the prompts show the normalized names instead of those that contain the special characters. + + Changed the value being used from name to fname, which always has the user-set name. + + Previous: + ![Screenshot from 2021-01-20 15-52-29](https://user-images.githubusercontent.com/38764067/105161642-9b31e780-5b37-11eb-8b0c-ec4b1414c948.png) + + Updated: ![Screenshot from 2021-01-20 15-50-19](https://user-images.githubusercontent.com/38764067/105161627-966d3380-5b37-11eb-9812-3dd9352b4f95.png) - Room's list showing all rooms with same name ([#20176](https://github.com/RocketChat/Rocket.Chat/pull/20176)) @@ -5426,9 +5328,9 @@ - Saving with blank email in edit user ([#20259](https://github.com/RocketChat/Rocket.Chat/pull/20259) by [@RonLek](https://github.com/RonLek)) - Disallows showing a success popup when email field is made blank in Edit User and instead shows the relevant error popup. - - + Disallows showing a success popup when email field is made blank in Edit User and instead shows the relevant error popup. + + https://user-images.githubusercontent.com/28918901/104960749-dbd81680-59fa-11eb-9c7b-2b257936f894.mp4 - Search list filter ([#19937](https://github.com/RocketChat/Rocket.Chat/pull/19937)) @@ -5475,7 +5377,7 @@ - Add translation of Edit Status in all languages ([#19916](https://github.com/RocketChat/Rocket.Chat/pull/19916) by [@sushant52](https://github.com/sushant52)) - Closes [#19915](https://github.com/RocketChat/Rocket.Chat/issues/19915) + Closes [#19915](https://github.com/RocketChat/Rocket.Chat/issues/19915) The profile options menu is well translated in many languages. However, Edit Status is the only button which is not well translated. With this change, the whole profile options will be properly translated in a lot of languages. - Bump axios from 0.18.0 to 0.18.1 ([#20055](https://github.com/RocketChat/Rocket.Chat/pull/20055) by [@dependabot[bot]](https://github.com/dependabot[bot])) @@ -5510,10 +5412,10 @@ - Regression: Announcement bar not showing properly Markdown content ([#20290](https://github.com/RocketChat/Rocket.Chat/pull/20290)) - **Before**: - ![image](https://user-images.githubusercontent.com/27704687/105273746-a4907380-5b7a-11eb-8121-aff665251c44.png) - - **After**: + **Before**: + ![image](https://user-images.githubusercontent.com/27704687/105273746-a4907380-5b7a-11eb-8121-aff665251c44.png) + + **After**: ![image](https://user-images.githubusercontent.com/27704687/105274050-2e404100-5b7b-11eb-93b2-b6282a7bed95.png) - regression: Announcement link open in new tab ([#20435](https://github.com/RocketChat/Rocket.Chat/pull/20435)) @@ -5528,23 +5430,23 @@ - Regression: Change sort icon ([#20177](https://github.com/RocketChat/Rocket.Chat/pull/20177)) - ### Before - ![image](https://user-images.githubusercontent.com/40830821/104366414-1bcd6400-54f8-11eb-9fc7-c6f13f07a61e.png) - - ### After + ### Before + ![image](https://user-images.githubusercontent.com/40830821/104366414-1bcd6400-54f8-11eb-9fc7-c6f13f07a61e.png) + + ### After ![image](https://user-images.githubusercontent.com/40830821/104366542-4cad9900-54f8-11eb-83ca-acb99899515a.png) - Regression: Custom field labels are not displayed properly on Omnichannel Contact Profile form ([#20393](https://github.com/RocketChat/Rocket.Chat/pull/20393) by [@rafaelblink](https://github.com/rafaelblink)) - ### Before - ![image](https://user-images.githubusercontent.com/2493803/105780399-20116c80-5f4f-11eb-9620-0901472e453b.png) - - ![image](https://user-images.githubusercontent.com/2493803/105780420-2e5f8880-5f4f-11eb-8e93-8115ebc685be.png) - - ### After - - ![image](https://user-images.githubusercontent.com/2493803/105780832-1ccab080-5f50-11eb-8042-188dd0c41904.png) - + ### Before + ![image](https://user-images.githubusercontent.com/2493803/105780399-20116c80-5f4f-11eb-9620-0901472e453b.png) + + ![image](https://user-images.githubusercontent.com/2493803/105780420-2e5f8880-5f4f-11eb-8e93-8115ebc685be.png) + + ### After + + ![image](https://user-images.githubusercontent.com/2493803/105780832-1ccab080-5f50-11eb-8042-188dd0c41904.png) + ![image](https://user-images.githubusercontent.com/2493803/105780911-500d3f80-5f50-11eb-96e0-7df3f179dbd5.png) - Regression: ESLint Warning - explicit-function-return-type ([#20434](https://github.com/RocketChat/Rocket.Chat/pull/20434) by [@aditya-mitra](https://github.com/aditya-mitra)) @@ -5561,8 +5463,8 @@ - Regression: Fixed update room avatar issue. ([#20433](https://github.com/RocketChat/Rocket.Chat/pull/20433) by [@Darshilp326](https://github.com/Darshilp326)) - Users can now update their room avatar without any error. - + Users can now update their room avatar without any error. + https://user-images.githubusercontent.com/55157259/105951602-560d3880-6096-11eb-97a5-b5eb9a28b58d.mp4 - Regression: Info Page Icon style and usage graph breaking ([#20180](https://github.com/RocketChat/Rocket.Chat/pull/20180)) @@ -5579,11 +5481,11 @@ - Regression: Unread superposing announcement. ([#20306](https://github.com/RocketChat/Rocket.Chat/pull/20306)) - ### Before - ![image](https://user-images.githubusercontent.com/40830821/105412619-c2f67d80-5c13-11eb-8204-5932ea880c8a.png) - - - ### After + ### Before + ![image](https://user-images.githubusercontent.com/40830821/105412619-c2f67d80-5c13-11eb-8204-5932ea880c8a.png) + + + ### After ![image](https://user-images.githubusercontent.com/40830821/105411176-d1439a00-5c11-11eb-8d1b-ea27c8485214.png) - Regression: User Dropdown margin ([#20222](https://github.com/RocketChat/Rocket.Chat/pull/20222)) @@ -5871,8 +5773,8 @@ - Hightlights validation on Account Preferences page ([#19902](https://github.com/RocketChat/Rocket.Chat/pull/19902) by [@aKn1ghtOut](https://github.com/aKn1ghtOut)) - This PR fixes two issues in the account settings "preferences" panel. - Once set, the "Highlighted Words" setting cannot be reset to an empty string. This was fixed by changing the string validation from checking the length to checking the type of variable. + This PR fixes two issues in the account settings "preferences" panel. + Once set, the "Highlighted Words" setting cannot be reset to an empty string. This was fixed by changing the string validation from checking the length to checking the type of variable. Secondly, it tracks the changes to correctly identify if changes after the last "save changes" action have been made, using an "updates" state variable, instead of just comparing against the initialValue that does not change on clicking "save changes". - Image preview for image URLs on messages ([#19734](https://github.com/RocketChat/Rocket.Chat/pull/19734) by [@g-thome](https://github.com/g-thome)) @@ -5931,14 +5833,10 @@ - Chore: Update Pull Request template ([#19768](https://github.com/RocketChat/Rocket.Chat/pull/19768)) - Improve the template of Pull Requests in order to make it clear reducing duplicated information and removing the visible checklists that were generating noise and misunderstanding with the PR progress. - - - Moved the checklists to inside comments - - - Merge the changelog and proposed changes sections to have a single source of description that goes to the changelog - - - Remove the screenshot section, they can be added inside the description - + Improve the template of Pull Requests in order to make it clear reducing duplicated information and removing the visible checklists that were generating noise and misunderstanding with the PR progress. + - Moved the checklists to inside comments + - Merge the changelog and proposed changes sections to have a single source of description that goes to the changelog + - Remove the screenshot section, they can be added inside the description - Changed the proposed changes title to incentivizing the usage of images and videos - Frontend folder structure ([#19631](https://github.com/RocketChat/Rocket.Chat/pull/19631)) @@ -5973,11 +5871,11 @@ - Regression: Double Scrollbars on tables ([#19980](https://github.com/RocketChat/Rocket.Chat/pull/19980)) - Before: - ![image](https://user-images.githubusercontent.com/40830821/103242719-0ec84680-4936-11eb-87a7-68b6eea8de7b.png) - - - After: + Before: + ![image](https://user-images.githubusercontent.com/40830821/103242719-0ec84680-4936-11eb-87a7-68b6eea8de7b.png) + + + After: ![image](https://user-images.githubusercontent.com/40830821/103242680-ee988780-4935-11eb-99e2-a95de99f78f1.png) - Regression: Failed autolinker and markdown rendering ([#19831](https://github.com/RocketChat/Rocket.Chat/pull/19831)) @@ -5996,7 +5894,7 @@ - Regression: Omnichannel Custom Fields Form no longer working after refactoring ([#19948](https://github.com/RocketChat/Rocket.Chat/pull/19948)) - The Omnichannel `Custom Fields` form is not working anymore after some refactorings on client-side. + The Omnichannel `Custom Fields` form is not working anymore after some refactorings on client-side. When the user clicks on `Custom Field` in the Omnichannel menu, a blank page appears. - Regression: polishing licenses endpoints ([#19981](https://github.com/RocketChat/Rocket.Chat/pull/19981) by [@g-thome](https://github.com/g-thome)) @@ -6195,8 +6093,8 @@ - Bundle Size Client ([#19533](https://github.com/RocketChat/Rocket.Chat/pull/19533)) - temporarily removes some codeblock languages - Moved some libraries to dynamic imports + temporarily removes some codeblock languages + Moved some libraries to dynamic imports Removed some shared code not used on the client side - Forward Omnichannel room to agent in another department ([#19576](https://github.com/RocketChat/Rocket.Chat/pull/19576) by [@mrfigueiredo](https://github.com/mrfigueiredo)) @@ -6503,7 +6401,7 @@ - [@sampaiodiego](https://github.com/sampaiodiego) # 3.8.0 -`2020-11-14 · 14 🎉 · 4 🚀 · 40 🐛 · 54 🔍 · 30 👩‍💻👨‍💻` +`2020-11-13 · 14 🎉 · 4 🚀 · 40 🐛 · 54 🔍 · 30 👩‍💻👨‍💻` ### Engine versions - Node: `12.18.4` @@ -7277,10 +7175,8 @@ - **2FA:** Password enforcement setting and 2FA protection when saving settings or resetting E2E encryption ([#18640](https://github.com/RocketChat/Rocket.Chat/pull/18640)) - - Increase the 2FA remembering time from 5min to 30min - - - Add new setting to enforce 2FA password fallback (enabled only for new installations) - + - Increase the 2FA remembering time from 5min to 30min + - Add new setting to enforce 2FA password fallback (enabled only for new installations) - Require 2FA to save settings and reset E2E Encryption keys - **Omnichannel:** Allow set other agent status via method `livechat:changeLivechatStatus ` ([#18571](https://github.com/RocketChat/Rocket.Chat/pull/18571)) @@ -7298,7 +7194,7 @@ - 2FA by Email setting showing for the user even when disabled by the admin ([#18473](https://github.com/RocketChat/Rocket.Chat/pull/18473)) - The option to disable/enable the **Two-factor authentication via Email** at `Account > Security > Two Factor Authentication + The option to disable/enable the **Two-factor authentication via Email** at `Account > Security > Two Factor Authentication ` was visible even when the setting **Enable Two Factor Authentication via Email** at `Admin > Accounts > Two Factor Authentication` was disabled leading to misbehavior since the functionality was disabled. - Agents enabledDepartment attribute not set on collection ([#18614](https://github.com/RocketChat/Rocket.Chat/pull/18614) by [@paulobernardoaf](https://github.com/paulobernardoaf)) @@ -7648,16 +7544,13 @@ - Mention autocomplete UI and performance improvements ([#18309](https://github.com/RocketChat/Rocket.Chat/pull/18309)) - * New setting to configure the number of suggestions `Admin > Layout > User Interface > Number of users' autocomplete suggestions` (default 5) - - * The UI shows whenever the user is not a member of the room - - * The UI shows when the suggestion came from the last messages for quick selection/reply - - * The suggestions follow this order: - * The user with the exact username and member of the room - * The user with the exact username but not a member of the room (if allowed to list non-members) - * The users containing the text in username, name or nickname and member of the room + * New setting to configure the number of suggestions `Admin > Layout > User Interface > Number of users' autocomplete suggestions` (default 5) + * The UI shows whenever the user is not a member of the room + * The UI shows when the suggestion came from the last messages for quick selection/reply + * The suggestions follow this order: + * The user with the exact username and member of the room + * The user with the exact username but not a member of the room (if allowed to list non-members) + * The users containing the text in username, name or nickname and member of the room * The users containing the text in username, name or nickname and not a member of the room (if allowed to list non-members) - Message action styles ([#18190](https://github.com/RocketChat/Rocket.Chat/pull/18190)) @@ -7999,10 +7892,10 @@ - Split NOTIFICATIONS_SCHEDULE_DELAY into three separate variables ([#17669](https://github.com/RocketChat/Rocket.Chat/pull/17669) by [@jazztickets](https://github.com/jazztickets)) - Email notification delay can now be customized with the following environment variables: - NOTIFICATIONS_SCHEDULE_DELAY_ONLINE - NOTIFICATIONS_SCHEDULE_DELAY_AWAY - NOTIFICATIONS_SCHEDULE_DELAY_OFFLINE + Email notification delay can now be customized with the following environment variables: + NOTIFICATIONS_SCHEDULE_DELAY_ONLINE + NOTIFICATIONS_SCHEDULE_DELAY_AWAY + NOTIFICATIONS_SCHEDULE_DELAY_OFFLINE Setting the value to -1 disable notifications for that type. - Threads ([#17416](https://github.com/RocketChat/Rocket.Chat/pull/17416)) @@ -8402,11 +8295,11 @@ - **ENTERPRISE:** Omnichannel Last-Chatted Agent Preferred option ([#17666](https://github.com/RocketChat/Rocket.Chat/pull/17666)) - If activated, this feature will store the last agent that assisted each Omnichannel visitor when a conversation is taken. So, when a visitor returns(it works with any entry point, Livechat, Facebook, REST API, and so on) and starts a new chat, the routing system checks: - - 1 - The visitor object for any stored agent that the visitor has previously talked to; - 2 - If a previous agent is not found, the system will try to find a previous conversation of the same visitor. If a room is found, the system will get the previous agent from the room; - + If activated, this feature will store the last agent that assisted each Omnichannel visitor when a conversation is taken. So, when a visitor returns(it works with any entry point, Livechat, Facebook, REST API, and so on) and starts a new chat, the routing system checks: + + 1 - The visitor object for any stored agent that the visitor has previously talked to; + 2 - If a previous agent is not found, the system will try to find a previous conversation of the same visitor. If a room is found, the system will get the previous agent from the room; + After this process, if an agent has been found, the system will check the agent's availability to assist the new chat. If it's not available, then the routing system will get the next available agent in the queue. - **ENTERPRISE:** Support for custom Livechat registration form fields ([#17581](https://github.com/RocketChat/Rocket.Chat/pull/17581)) @@ -8511,12 +8404,9 @@ - Notification sounds ([#17616](https://github.com/RocketChat/Rocket.Chat/pull/17616)) - * Global CDN config was ignored when loading the sound files - - * Upload of custom sounds wasn't getting the file extension correctly - - * Some translations were missing - + * Global CDN config was ignored when loading the sound files + * Upload of custom sounds wasn't getting the file extension correctly + * Some translations were missing * Edit and delete of custom sounds were not working correctly - Omnichannel departments are not saved when the offline channel name is not defined ([#17553](https://github.com/RocketChat/Rocket.Chat/pull/17553)) @@ -8804,19 +8694,14 @@ - Better Push and Email Notification logic ([#17357](https://github.com/RocketChat/Rocket.Chat/pull/17357)) - We are still using the same logic to define which notifications every new message will generate, it takes some servers' settings, users's preferences and subscriptions' settings in consideration to determine who will receive each notification type (desktop, audio, email and mobile push), but now it doesn't check the user's status (online, away, offline) for email and mobile push notifications but send those notifications to a new queue with the following rules: - - - - When the user is online the notification is scheduled to be sent in 120 seconds - - - When the user is away the notification is scheduled to be sent in 120 seconds minus the amount of time he is away - - - When the user is offline the notification is scheduled to be sent right away - - - When the user reads a channel all the notifications for that user are removed (clear queue) - - - When a notification is processed to be sent to a user and there are other scheduled notifications: - - All the scheduled notifications for that user are rescheduled to now + We are still using the same logic to define which notifications every new message will generate, it takes some servers' settings, users's preferences and subscriptions' settings in consideration to determine who will receive each notification type (desktop, audio, email and mobile push), but now it doesn't check the user's status (online, away, offline) for email and mobile push notifications but send those notifications to a new queue with the following rules: + + - When the user is online the notification is scheduled to be sent in 120 seconds + - When the user is away the notification is scheduled to be sent in 120 seconds minus the amount of time he is away + - When the user is offline the notification is scheduled to be sent right away + - When the user reads a channel all the notifications for that user are removed (clear queue) + - When a notification is processed to be sent to a user and there are other scheduled notifications: + - All the scheduled notifications for that user are rescheduled to now - The current notification goes back to the queue to be processed ordered by creation date - Buttons to check/uncheck all users and channels on import ([#17207](https://github.com/RocketChat/Rocket.Chat/pull/17207)) @@ -9179,7 +9064,7 @@ - Translation via MS translate ([#16363](https://github.com/RocketChat/Rocket.Chat/pull/16363) by [@mrsimpson](https://github.com/mrsimpson)) - Adds Microsoft's translation service (https://translator.microsoft.com/) as a provider for translation of messages. + Adds Microsoft's translation service (https://translator.microsoft.com/) as a provider for translation of messages. In addition to implementing the interface (similar to google and DeepL), a small change has been done in order to display the translation provider on the UI. - Two Factor authentication via email ([#15949](https://github.com/RocketChat/Rocket.Chat/pull/15949)) @@ -20718,4 +20603,4 @@ - [@graywolf336](https://github.com/graywolf336) - [@marceloschmidt](https://github.com/marceloschmidt) - [@rodrigok](https://github.com/rodrigok) -- [@sampaiodiego](https://github.com/sampaiodiego) +- [@sampaiodiego](https://github.com/sampaiodiego) \ No newline at end of file diff --git a/app/utils/rocketchat.info b/app/utils/rocketchat.info index e0d8e5906fd71..d767f42754843 100644 --- a/app/utils/rocketchat.info +++ b/app/utils/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "4.1.1" + "version": "4.1.2" } diff --git a/package-lock.json b/package-lock.json index 01bb93f30fec6..e83cf8126066e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "Rocket.Chat", - "version": "4.1.1", + "version": "4.1.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d0240cf5e5941..ac32ba44327eb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Rocket.Chat", "description": "The Ultimate Open Source WebChat Platform", - "version": "4.1.1", + "version": "4.1.2", "author": { "name": "Rocket.Chat", "url": "https://rocket.chat/" From 0eba26876e85871211a18ec3adb5ac0073782763 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Felchar?= <11652381+cauefcr@users.noreply.github.com> Date: Tue, 9 Nov 2021 17:29:10 -0300 Subject: [PATCH 060/137] [NEW] Permissions for interacting with Omnichannel Contact Center (#23389) * added new permissions and backend api checks for said permission * client-side validation of the permission for showing the contact center button * logo update * Added permissions to the correct place, as well as english description for the permission * Update packages/rocketchat-i18n/i18n/en.i18n.json Co-authored-by: Renato Becker * changed name of the permission and it's description * Made the permission work with the new settings code * Removing uneeded migration * Added back migrations to create permission * fixing translations * Update packages/rocketchat-i18n/i18n/en.i18n.json Co-authored-by: Renato Becker Co-authored-by: Caue Felchar Co-authored-by: Renato Becker --- app/authorization/server/functions/upsertPermissions.js | 1 + client/sidebar/sections/Omnichannel.js | 5 ++++- .../omnichannel/directory/OmnichannelDirectoryPage.js | 7 +++++++ packages/rocketchat-i18n/i18n/en.i18n.json | 2 ++ packages/rocketchat-i18n/i18n/pt-BR.i18n.json | 2 ++ server/startup/migrations/index.ts | 1 + server/startup/migrations/v244.ts | 9 +++++++++ 7 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 server/startup/migrations/v244.ts diff --git a/app/authorization/server/functions/upsertPermissions.js b/app/authorization/server/functions/upsertPermissions.js index 76e97fcef4800..a2e78302ad402 100644 --- a/app/authorization/server/functions/upsertPermissions.js +++ b/app/authorization/server/functions/upsertPermissions.js @@ -94,6 +94,7 @@ export const upsertPermissions = () => { { _id: 'create-invite-links', roles: ['admin', 'owner', 'moderator'] }, { _id: 'view-l-room', roles: ['livechat-manager', 'livechat-monitor', 'livechat-agent', 'admin'] }, { _id: 'view-livechat-manager', roles: ['livechat-manager', 'livechat-monitor', 'admin'] }, + { _id: 'view-omnichannel-contact-center', roles: ['livechat-manager', 'livechat-agent', 'livechat-monitor', 'admin'] }, { _id: 'edit-omnichannel-contact', roles: ['livechat-manager', 'livechat-agent', 'admin'] }, { _id: 'view-livechat-rooms', roles: ['livechat-manager', 'livechat-monitor', 'admin'] }, { _id: 'close-livechat-room', roles: ['livechat-manager', 'livechat-monitor', 'livechat-agent', 'admin'] }, diff --git a/client/sidebar/sections/Omnichannel.js b/client/sidebar/sections/Omnichannel.js index 3a18ac5b6347f..c829b44c11699 100644 --- a/client/sidebar/sections/Omnichannel.js +++ b/client/sidebar/sections/Omnichannel.js @@ -2,6 +2,7 @@ import { Sidebar } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import React, { memo } from 'react'; +import { hasPermission } from '../../../app/authorization/client'; import { useOmnichannelShowQueueLink, useOmnichannelAgentAvailable, @@ -49,7 +50,9 @@ const OmnichannelSection = (props) => { )} - + {hasPermission(['view-omnichannel-contact-center']) && ( + + )} ); diff --git a/client/views/omnichannel/directory/OmnichannelDirectoryPage.js b/client/views/omnichannel/directory/OmnichannelDirectoryPage.js index 4f19bbf53901c..3a0cec01e44a9 100644 --- a/client/views/omnichannel/directory/OmnichannelDirectoryPage.js +++ b/client/views/omnichannel/directory/OmnichannelDirectoryPage.js @@ -1,7 +1,9 @@ import { Tabs } from '@rocket.chat/fuselage'; import React, { useEffect, useCallback, useState } from 'react'; +import NotAuthorizedPage from '../../../components/NotAuthorizedPage'; import Page from '../../../components/Page'; +import { usePermission } from '../../../contexts/AuthorizationContext'; import { useCurrentRoute, useRoute, useRouteParameter } from '../../../contexts/RouterContext'; import { useTranslation } from '../../../contexts/TranslationContext'; import ContextualBar from './ContextualBar'; @@ -14,6 +16,7 @@ const OmnichannelDirectoryPage = () => { const [routeName] = useCurrentRoute(); const tab = useRouteParameter('page'); const directoryRoute = useRoute('omnichannel-directory'); + const canViewDirectory = usePermission('view-omnichannel-contact-center'); useEffect(() => { if (routeName !== 'omnichannel-directory') { @@ -32,6 +35,10 @@ const OmnichannelDirectoryPage = () => { const t = useTranslation(); + if (!canViewDirectory) { + return ; + } + return ( diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 3e16b0be3cb6c..91a0a21421532 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -4531,6 +4531,8 @@ "Videos": "Videos", "View_All": "View All Members", "View_channels": "View Channels", + "view-omnichannel-contact-center": "View Omnichannel Contact Center", + "view-omnichannel-contact-center_description": "Permission to view and interact with the Omnichannel Contact Center", "View_Logs": "View Logs", "View_mode": "View Mode", "View_original": "View Original", diff --git a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json index 69e66f6ea937a..f34ce0b737cd2 100644 --- a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json +++ b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json @@ -4525,6 +4525,8 @@ "Videos": "Vídeos", "View_All": "Ver Todos Membros", "View_channels": "Ver Canais", + "view-omnichannel-contact-center": "Ver o Centro de Contatos do Omnichannel", + "view-omnichannel-contact-center_description": "Permissão para ver e interagir com o Centro de Contatos do Omnichannel", "View_Logs": "Ver Logs", "View_mode": "Modo de visualização", "View_original": "Visualizar original", diff --git a/server/startup/migrations/index.ts b/server/startup/migrations/index.ts index 37f25773c4e2b..dfeec238d17a9 100644 --- a/server/startup/migrations/index.ts +++ b/server/startup/migrations/index.ts @@ -67,4 +67,5 @@ import './v240'; import './v241'; import './v242'; import './v243'; +import './v244'; import './xrun'; diff --git a/server/startup/migrations/v244.ts b/server/startup/migrations/v244.ts new file mode 100644 index 0000000000000..8ec31f2485e19 --- /dev/null +++ b/server/startup/migrations/v244.ts @@ -0,0 +1,9 @@ +import { addMigration } from '../../lib/migrations'; +import { Permissions } from '../../../app/models/server'; + +addMigration({ + version: 244, + up() { + Permissions.create('view-omnichannel-contact-center', ['livechat-manager', 'livechat-agent', 'livechat-monitor', 'admin']); + }, +}); From f50609df0b5a57375c019d1e19c7b0c345847f62 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Wed, 10 Nov 2021 18:28:39 -0300 Subject: [PATCH 061/137] Chore: Convert Fiber models to async Step 1 (#23633) Co-authored-by: Diego Sampaio Co-authored-by: Guilherme Gazzo --- .../lib/{integrations.js => integrations.ts} | 14 +- app/api/server/lib/webdav.js | 14 - app/api/server/lib/webdav.ts | 16 + app/api/server/v1/channels.js | 22 +- app/api/server/v1/email-inbox.js | 6 +- app/api/server/v1/emoji-custom.js | 10 +- app/api/server/v1/groups.js | 22 +- app/api/server/v1/im.js | 9 +- app/api/server/v1/instances.ts | 4 +- app/api/server/v1/integrations.js | 45 +- app/api/server/v1/invites.js | 10 +- .../v1/{permissions.js => permissions.ts} | 24 +- app/api/server/v1/{roles.js => roles.ts} | 48 +- .../server/v1/{settings.js => settings.ts} | 97 ++++- app/apps/server/bridges/internal.ts | 5 +- app/apps/server/bridges/settings.ts | 11 +- app/apps/server/communication/rest.js | 19 +- app/apps/server/converters/settings.js | 6 +- app/apps/server/converters/uploads.js | 4 +- app/apps/server/cron.js | 17 +- .../server/lib/restrictLoginAttempts.ts | 17 +- app/authentication/server/startup/index.js | 7 +- app/authorization/client/hasPermission.ts | 16 +- .../{addUserRoles.js => addUserRoles.ts} | 16 +- .../server/functions/getRoles.js | 3 - .../server/functions/getRoles.ts | 4 + .../server/functions/getUsersInRole.js | 3 - .../server/functions/getUsersInRole.ts | 14 + .../server/functions/removeUserFromRoles.js | 5 +- ...ertPermissions.js => upsertPermissions.ts} | 61 +-- ...issionToRole.js => addPermissionToRole.ts} | 14 +- .../{addUserToRole.js => addUserToRole.ts} | 9 +- .../methods/{deleteRole.js => deleteRole.ts} | 14 +- ...mission.js => removeRoleFromPermission.ts} | 11 +- .../server/methods/removeUserFromRole.js | 8 +- .../methods/{saveRole.js => saveRole.ts} | 6 +- .../server/streamer/permissions/index.js | 25 -- .../server/streamer/permissions/index.ts | 28 ++ app/autotranslate/server/permissions.js | 11 - app/autotranslate/server/permissions.ts | 9 + app/cas/server/cas_server.js | 12 +- .../server/functions/saveRoomName.js | 9 +- .../server/methods/saveRoomSettings.js | 14 +- .../server/functions/buildRegistrationData.js | 7 +- .../functions/startRegisterWorkspace.js | 7 +- app/cloud/server/functions/syncWorkspace.js | 14 +- app/cloud/server/methods.js | 8 +- app/crowd/server/crowd.js | 2 +- .../server/methods/deleteCustomSound.js | 8 +- .../server/methods/insertOrUpdateSound.js | 12 +- .../server/methods/listCustomSounds.js | 6 +- .../server/{permissions.js => permissions.ts} | 3 +- .../server/methods/deleteEmojiCustom.js | 8 +- .../server/methods/insertOrUpdateEmoji.js | 26 +- .../server/methods/listEmojiCustom.js | 6 +- app/federation/server/endpoints/dispatch.js | 19 +- .../server/endpoints/requestFromLatest.js | 6 +- app/federation/server/endpoints/uploads.js | 4 +- app/federation/server/functions/addUser.js | 9 +- app/federation/server/functions/dashboard.js | 15 +- app/federation/server/functions/helpers.js | 69 --- app/federation/server/functions/helpers.ts | 77 ++++ app/federation/server/handler/index.js | 26 +- .../server/hooks/afterCreateDirectRoom.js | 2 +- .../server/hooks/afterCreateRoom.js | 2 +- app/federation/server/lib/crypt.js | 16 +- app/federation/server/lib/dns.js | 8 +- app/federation/server/lib/http.js | 8 +- app/federation/server/startup/generateKeys.js | 10 +- app/federation/server/startup/settings.ts | 30 +- app/file-upload/server/lib/FileUpload.js | 32 +- app/file-upload/server/lib/requests.js | 6 +- .../server/methods/getS3FileUrl.js | 6 +- .../server/methods/sendFileMessage.ts | 5 +- app/integrations/server/api/api.js | 12 +- app/integrations/server/lib/triggerHandler.js | 9 +- ...nHistory.js => clearIntegrationHistory.ts} | 15 +- .../incoming/addIncomingIntegration.js | 13 +- ...ration.js => deleteIncomingIntegration.ts} | 15 +- .../incoming/updateIncomingIntegration.js | 21 +- .../outgoing/addOutgoingIntegration.js | 11 +- ...ration.js => deleteOutgoingIntegration.ts} | 17 +- ...ration.js => replayOutgoingIntegration.ts} | 15 +- .../outgoing/updateOutgoingIntegration.js | 19 +- .../server/functions/findOrCreateInvite.js | 9 +- app/invites/server/functions/listInvites.js | 8 +- app/invites/server/functions/removeInvite.js | 8 +- .../server/functions/useInviteToken.js | 9 +- .../server/functions/validateInviteToken.js | 7 +- app/lib/server/functions/deleteMessage.ts | 11 +- app/lib/server/functions/deleteUser.js | 13 +- .../functions/relinquishRoomOwnerships.js | 14 +- app/lib/server/functions/setRoomAvatar.js | 7 +- .../server/functions/setUserActiveStatus.js | 2 +- app/lib/server/functions/setUsername.js | 5 +- app/lib/server/lib/getRoomRoles.js | 5 +- app/lib/server/methods/deleteMessage.js | 2 +- .../server/methods/deleteUserOwnAccount.js | 4 +- .../methods/{leaveRoom.js => leaveRoom.ts} | 12 +- ...OAuthService.js => refreshOAuthService.ts} | 8 +- app/lib/server/methods/removeOAuthService.js | 51 --- app/lib/server/methods/removeOAuthService.ts | 54 +++ app/lib/server/methods/saveSetting.js | 8 +- app/lib/server/methods/saveSettings.js | 18 +- app/livechat/server/api/lib/livechat.js | 7 +- app/livechat/server/api/v1/config.js | 2 +- app/livechat/server/api/v1/message.js | 8 +- app/livechat/server/api/v1/room.js | 2 +- app/livechat/server/api/v1/videoCall.js | 2 +- .../server/hooks/saveAnalyticsData.js | 2 +- app/livechat/server/hooks/sendToCRM.js | 2 +- app/livechat/server/hooks/sendToFacebook.js | 2 +- app/livechat/server/lib/Livechat.js | 4 +- app/livechat/server/sendMessageBySMS.js | 2 +- .../LivechatAgentActivityMonitor.js | 10 +- app/meteor-accounts-saml/server/lib/SAML.ts | 19 +- .../server/loginHandler.ts | 2 +- app/metrics/server/lib/collectMetrics.js | 4 +- app/models/server/index.js | 49 --- app/models/server/models/Analytics.js | 11 - app/models/server/models/Avatars.js | 92 ---- app/models/server/models/CredentialTokens.js | 32 -- app/models/server/models/CustomSounds.js | 56 --- app/models/server/models/CustomUserStatus.js | 71 --- app/models/server/models/EmailInbox.js | 27 -- app/models/server/models/EmojiCustom.js | 91 ---- app/models/server/models/ExportOperations.js | 108 ----- .../server/models/FederationDNSCache.js | 13 - app/models/server/models/FederationKeys.js | 58 --- app/models/server/models/FederationServers.js | 26 -- app/models/server/models/InstanceStatus.js | 7 - .../server/models/IntegrationHistory.js | 43 -- app/models/server/models/Integrations.js | 31 -- app/models/server/models/Invites.js | 51 --- app/models/server/models/NotificationQueue.js | 14 - app/models/server/models/OAuthApps.js | 9 - app/models/server/models/OEmbedCache.js | 39 -- app/models/server/models/Permissions.js | 49 --- app/models/server/models/ReadReceipts.js | 25 -- app/models/server/models/Reports.js | 23 - app/models/server/models/Roles.js | 134 ------ app/models/server/models/Rooms.js | 2 +- app/models/server/models/ServerEvents.ts | 11 - app/models/server/models/Sessions.mocks.js | 16 - app/models/server/models/SmarshHistory.js | 9 - app/models/server/models/Statistics.js | 28 -- app/models/server/models/Uploads.js | 146 ------- app/models/server/models/UserDataFiles.js | 44 -- app/models/server/models/UsersSessions.js | 7 - app/models/server/models/WebdavAccounts.js | 27 -- app/models/server/models/_BaseDb.js | 190 ++++---- app/models/server/raw/Analytics.js | 151 ------- app/models/server/raw/Analytics.ts | 146 +++++++ app/models/server/raw/Avatars.ts | 73 ++++ app/models/server/raw/BaseRaw.ts | 108 ++++- app/models/server/raw/CredentialTokens.ts | 29 ++ app/models/server/raw/CustomSounds.js | 5 - app/models/server/raw/CustomSounds.ts | 44 ++ app/models/server/raw/CustomUserStatus.js | 5 - app/models/server/raw/CustomUserStatus.ts | 59 +++ app/models/server/raw/EmailInbox.ts | 6 +- app/models/server/raw/EmailMessageHistory.ts | 15 +- app/models/server/raw/EmojiCustom.js | 5 - app/models/server/raw/EmojiCustom.ts | 79 ++++ app/models/server/raw/ExportOperations.ts | 68 +++ app/models/server/raw/FederationKeys.ts | 65 +++ app/models/server/raw/FederationServers.ts | 29 ++ app/models/server/raw/IntegrationHistory.ts | 10 +- app/models/server/raw/Integrations.js | 14 - app/models/server/raw/Integrations.ts | 36 ++ app/models/server/raw/Invites.ts | 27 ++ app/models/server/raw/NotificationQueue.ts | 41 +- app/models/server/raw/OAuthApps.js | 14 - app/models/server/raw/OAuthApps.ts | 11 + app/models/server/raw/OEmbedCache.ts | 31 ++ app/models/server/raw/Permissions.ts | 31 ++ app/models/server/raw/ReadReceipts.ts | 15 + app/models/server/raw/Reports.ts | 15 + app/models/server/raw/Roles.js | 31 -- app/models/server/raw/Roles.ts | 200 +++++++++ app/models/server/raw/ServerEvents.ts | 32 +- app/models/server/raw/Sessions.js | 285 ------------ .../server/{models => raw}/Sessions.tests.js | 2 - .../{models/Sessions.js => raw/Sessions.ts} | 411 +++++++++++++++--- app/models/server/raw/Settings.ts | 117 ++++- app/models/server/raw/SmarshHistory.ts | 8 + app/models/server/raw/Statistics.js | 14 - app/models/server/raw/Statistics.ts | 21 + app/models/server/raw/Subscriptions.ts | 87 +++- app/models/server/raw/Uploads.ts | 116 +++++ app/models/server/raw/UserDataFiles.ts | 29 ++ app/models/server/raw/Users.js | 45 ++ app/models/server/raw/UsersSessions.ts | 14 +- app/models/server/raw/WebdavAccounts.js | 8 - app/models/server/raw/WebdavAccounts.ts | 46 ++ app/models/server/raw/_Users.d.ts | 14 + app/models/server/raw/index.ts | 190 ++++---- .../server/admin/methods/addOAuthApp.js | 7 +- .../server/admin/methods/deleteOAuthApp.js | 8 +- .../server/admin/methods/updateOAuthApp.js | 11 +- .../server/oauth/default-services.js | 17 - .../server/oauth/default-services.ts | 20 + .../server/oauth/oauth2-server.js | 7 +- app/oembed/server/server.js | 23 +- app/reactions/server/setReaction.js | 5 +- .../server/functions/generateEml.js | 7 +- .../server/functions/sendEmail.js | 12 +- app/statistics/server/lib/SAUMonitor.js | 90 ++-- app/statistics/server/lib/statistics.js | 39 +- .../server/cronProcessDownloads.js | 27 +- .../server/exportDownload.js | 6 +- .../server/methods/deleteCustomUserStatus.js | 8 +- .../methods/insertOrUpdateUserStatus.js | 14 +- .../server/methods/listCustomUserStatus.js | 6 +- .../functions/normalizeMessageFileUpload.js | 6 +- .../server/functions/checkVersionUpdate.js | 8 +- app/webdav/client/actionButton.js | 2 +- app/webdav/client/selectWebdavAccount.js | 2 +- .../client/startup/messageBoxActions.js | 2 +- app/webdav/server/methods/addWebdavAccount.js | 12 +- .../server/methods/getFileFromWebdav.js | 4 +- .../server/methods/getWebdavFileList.js | 4 +- .../server/methods/getWebdavFilePreview.js | 4 +- .../server/methods/removeWebdavAccount.js | 6 +- .../server/methods/uploadFileToWebdav.ts | 4 +- definition/Federation.ts | 5 + definition/IAnalytic.ts | 33 ++ definition/IAvatar.ts | 13 + definition/ICredentialToken.ts | 10 + definition/ICustomSound.ts | 6 + definition/ICustomUserStatus.ts | 6 + definition/IEmojiCustom.ts | 7 + definition/IExportOperation.ts | 16 + definition/IIntegration.ts | 8 +- definition/IIntegrationHistory.ts | 20 +- definition/IInvite.ts | 11 + definition/ILivechatBusinessHour.ts | 1 + definition/IOAuthApps.ts | 14 + definition/IOEmbedCache.ts | 6 + definition/IReport.ts | 9 + definition/IRoom.ts | 4 + definition/IServerEvent.ts | 2 +- definition/ISession.ts | 29 ++ definition/ISetting.ts | 11 +- definition/ISmarshHistory.ts | 6 + definition/IStatistic.ts | 24 + definition/IUpload.ts | 12 + definition/IUser.ts | 9 +- definition/IUserDataFile.ts | 13 + definition/IWebdavAccount.ts | 9 + definition/externals/meteor/meteor.d.ts | 1 + ee/app/auditing/server/{index.js => index.ts} | 8 +- .../server/resetEnterprisePermissions.js | 6 - .../server/resetEnterprisePermissions.ts | 7 + .../server/{permissions.js => permissions.ts} | 2 +- .../server/lib/messages.js | 30 +- .../engagement-dashboard/server/lib/users.js | 19 +- .../livechat-enterprise/server/permissions.js | 22 - .../livechat-enterprise/server/permissions.ts | 21 + ee/server/lib/ldap/Manager.ts | 10 +- ee/server/lib/oauth/Manager.ts | 5 +- .../services/ddp-streamer/streams/index.ts | 5 +- ee/server/services/stream-hub/StreamHub.ts | 6 +- .../server/api/methods/getReadReceipts.js | 2 +- .../server/lib/ReadReceipt.js | 21 +- package-lock.json | 9 + package.json | 1 + server/cron/statistics.js | 4 +- server/database/readSecondaryPreferred.ts | 4 +- server/features/EmailInbox/EmailInbox.ts | 2 +- .../EmailInbox/EmailInbox_Outgoing.ts | 9 +- server/lib/channelExport.ts | 6 +- server/lib/migrations.ts | 2 +- server/lib/{pushConfig.js => pushConfig.ts} | 32 +- server/lib/sendMessagesToAdmins.js | 42 +- server/methods/OEmbedCacheCleanup.js | 6 +- server/methods/afterVerifyEmail.js | 29 -- server/methods/afterVerifyEmail.ts | 39 ++ server/methods/browseChannels.js | 6 +- server/methods/createDirectMessage.js | 2 +- server/methods/deleteFileMessage.js | 2 +- server/methods/deleteUser.js | 4 +- server/methods/registerUser.js | 4 +- ...{removeRoomOwner.js => removeRoomOwner.ts} | 14 +- ...eUserFromRoom.js => removeUserFromRoom.ts} | 12 +- server/methods/reportMessage.js | 9 +- server/methods/requestDataDownload.js | 16 +- server/modules/watchers/watchers.module.ts | 4 +- .../settings/{index.js => index.ts} | 29 +- server/routes/avatar/room.js | 11 +- server/routes/avatar/user.js | 7 +- server/sdk/types/ITeamService.ts | 2 + server/services/analytics/service.ts | 7 +- server/services/authorization/service.ts | 13 +- server/services/nps/notification.ts | 4 +- server/services/team/service.ts | 6 +- server/startup/initialData.js | 26 +- server/startup/instance.js | 4 - .../startup/migrations/{v174.js => v174.ts} | 6 +- server/startup/migrations/v175.js | 17 +- server/startup/migrations/v176.js | 12 - server/startup/migrations/v176.ts | 9 + .../startup/migrations/{v177.js => v177.ts} | 6 +- server/startup/migrations/v178.js | 12 - server/startup/migrations/v178.ts | 9 + server/startup/migrations/v182.js | 4 +- server/startup/migrations/v183.js | 15 +- .../startup/migrations/{v184.js => v184.ts} | 6 +- server/startup/migrations/v185.js | 19 - server/startup/migrations/v185.ts | 18 + server/startup/migrations/v187.js | 15 +- .../startup/migrations/{v188.js => v188.ts} | 5 +- server/startup/migrations/v189.js | 11 - server/startup/migrations/v189.ts | 11 + server/startup/migrations/v190.js | 2 +- server/startup/migrations/v191.js | 9 - server/startup/migrations/v191.ts | 9 + server/startup/migrations/v194.js | 20 +- .../startup/migrations/{v195.js => v195.ts} | 39 +- .../startup/migrations/{v198.js => v198.ts} | 24 +- server/startup/migrations/v201.js | 16 +- server/startup/migrations/v202.js | 8 +- server/startup/migrations/v203.js | 10 - server/startup/migrations/v203.ts | 10 + server/startup/migrations/v205.js | 6 +- server/startup/migrations/v207.js | 9 - server/startup/migrations/v207.ts | 10 + server/startup/migrations/v208.js | 16 +- server/startup/migrations/v214.js | 11 - server/startup/migrations/v214.ts | 12 + .../startup/migrations/{v215.js => v215.ts} | 10 +- server/startup/migrations/v216.js | 65 +-- server/startup/migrations/v217.js | 12 - server/startup/migrations/v217.ts | 13 + server/startup/migrations/v218.js | 9 - server/startup/migrations/v218.ts | 10 + .../startup/migrations/{v219.js => v219.ts} | 11 +- .../startup/migrations/{v220.js => v220.ts} | 10 +- server/startup/migrations/v223.js | 11 - server/startup/migrations/v223.ts | 11 + server/startup/migrations/v224.js | 11 - server/startup/migrations/v224.ts | 9 + .../startup/migrations/{v225.js => v225.ts} | 23 +- server/startup/migrations/v226.js | 9 - server/startup/migrations/v226.ts | 9 + server/startup/migrations/v228.js | 11 - server/startup/migrations/v228.ts | 9 + .../startup/migrations/{v229.js => v229.ts} | 17 +- server/startup/migrations/v230.ts | 10 +- server/startup/migrations/v231.ts | 6 +- server/startup/migrations/v233.ts | 4 +- server/startup/migrations/v234.ts | 4 +- server/startup/migrations/v235.ts | 7 +- server/startup/migrations/v236.ts | 13 +- server/startup/migrations/v237.ts | 24 +- server/startup/migrations/v240.ts | 4 +- server/startup/migrations/v242.ts | 13 +- server/startup/migrations/v243.ts | 13 +- server/startup/migrations/v244.ts | 4 +- server/startup/presence.js | 17 +- server/stream/streamBroadcast.js | 20 +- 361 files changed, 4201 insertions(+), 3748 deletions(-) rename app/api/server/lib/{integrations.js => integrations.ts} (65%) delete mode 100644 app/api/server/lib/webdav.js create mode 100644 app/api/server/lib/webdav.ts rename app/api/server/v1/{permissions.js => permissions.ts} (74%) rename app/api/server/v1/{roles.js => roles.ts} (76%) rename app/api/server/v1/{settings.js => settings.ts} (54%) rename app/authorization/server/functions/{addUserRoles.js => addUserRoles.ts} (54%) delete mode 100644 app/authorization/server/functions/getRoles.js create mode 100644 app/authorization/server/functions/getRoles.ts delete mode 100644 app/authorization/server/functions/getUsersInRole.js create mode 100644 app/authorization/server/functions/getUsersInRole.ts rename app/authorization/server/functions/{upsertPermissions.js => upsertPermissions.ts} (88%) rename app/authorization/server/methods/{addPermissionToRole.js => addPermissionToRole.ts} (72%) rename app/authorization/server/methods/{addUserToRole.js => addUserToRole.ts} (84%) rename app/authorization/server/methods/{deleteRole.js => deleteRole.ts} (67%) rename app/authorization/server/methods/{removeRoleFromPermission.js => removeRoleFromPermission.ts} (70%) rename app/authorization/server/methods/{saveRole.js => saveRole.ts} (80%) delete mode 100644 app/authorization/server/streamer/permissions/index.js create mode 100644 app/authorization/server/streamer/permissions/index.ts delete mode 100644 app/autotranslate/server/permissions.js create mode 100644 app/autotranslate/server/permissions.ts rename app/discussion/server/{permissions.js => permissions.ts} (87%) delete mode 100644 app/federation/server/functions/helpers.js create mode 100644 app/federation/server/functions/helpers.ts rename app/integrations/server/methods/{clearIntegrationHistory.js => clearIntegrationHistory.ts} (70%) rename app/integrations/server/methods/incoming/{deleteIncomingIntegration.js => deleteIncomingIntegration.ts} (56%) rename app/integrations/server/methods/outgoing/{deleteOutgoingIntegration.js => deleteOutgoingIntegration.ts} (63%) rename app/integrations/server/methods/outgoing/{replayOutgoingIntegration.js => replayOutgoingIntegration.ts} (69%) rename app/lib/server/methods/{leaveRoom.js => leaveRoom.ts} (76%) rename app/lib/server/methods/{refreshOAuthService.js => refreshOAuthService.ts} (66%) delete mode 100644 app/lib/server/methods/removeOAuthService.js create mode 100644 app/lib/server/methods/removeOAuthService.ts delete mode 100644 app/models/server/models/Analytics.js delete mode 100644 app/models/server/models/Avatars.js delete mode 100644 app/models/server/models/CredentialTokens.js delete mode 100644 app/models/server/models/CustomSounds.js delete mode 100644 app/models/server/models/CustomUserStatus.js delete mode 100644 app/models/server/models/EmailInbox.js delete mode 100644 app/models/server/models/EmojiCustom.js delete mode 100644 app/models/server/models/ExportOperations.js delete mode 100644 app/models/server/models/FederationDNSCache.js delete mode 100644 app/models/server/models/FederationKeys.js delete mode 100644 app/models/server/models/FederationServers.js delete mode 100644 app/models/server/models/InstanceStatus.js delete mode 100644 app/models/server/models/IntegrationHistory.js delete mode 100644 app/models/server/models/Integrations.js delete mode 100644 app/models/server/models/Invites.js delete mode 100644 app/models/server/models/NotificationQueue.js delete mode 100644 app/models/server/models/OAuthApps.js delete mode 100644 app/models/server/models/OEmbedCache.js delete mode 100644 app/models/server/models/Permissions.js delete mode 100644 app/models/server/models/ReadReceipts.js delete mode 100644 app/models/server/models/Reports.js delete mode 100644 app/models/server/models/Roles.js delete mode 100644 app/models/server/models/ServerEvents.ts delete mode 100644 app/models/server/models/Sessions.mocks.js delete mode 100644 app/models/server/models/SmarshHistory.js delete mode 100644 app/models/server/models/Statistics.js delete mode 100644 app/models/server/models/Uploads.js delete mode 100644 app/models/server/models/UserDataFiles.js delete mode 100644 app/models/server/models/UsersSessions.js delete mode 100644 app/models/server/models/WebdavAccounts.js delete mode 100644 app/models/server/raw/Analytics.js create mode 100644 app/models/server/raw/Analytics.ts create mode 100644 app/models/server/raw/Avatars.ts create mode 100644 app/models/server/raw/CredentialTokens.ts delete mode 100644 app/models/server/raw/CustomSounds.js create mode 100644 app/models/server/raw/CustomSounds.ts delete mode 100644 app/models/server/raw/CustomUserStatus.js create mode 100644 app/models/server/raw/CustomUserStatus.ts delete mode 100644 app/models/server/raw/EmojiCustom.js create mode 100644 app/models/server/raw/EmojiCustom.ts create mode 100644 app/models/server/raw/ExportOperations.ts create mode 100644 app/models/server/raw/FederationKeys.ts create mode 100644 app/models/server/raw/FederationServers.ts delete mode 100644 app/models/server/raw/Integrations.js create mode 100644 app/models/server/raw/Integrations.ts create mode 100644 app/models/server/raw/Invites.ts delete mode 100644 app/models/server/raw/OAuthApps.js create mode 100644 app/models/server/raw/OAuthApps.ts create mode 100644 app/models/server/raw/OEmbedCache.ts create mode 100644 app/models/server/raw/ReadReceipts.ts create mode 100644 app/models/server/raw/Reports.ts delete mode 100644 app/models/server/raw/Roles.js create mode 100644 app/models/server/raw/Roles.ts delete mode 100644 app/models/server/raw/Sessions.js rename app/models/server/{models => raw}/Sessions.tests.js (99%) rename app/models/server/{models/Sessions.js => raw/Sessions.ts} (50%) create mode 100644 app/models/server/raw/SmarshHistory.ts delete mode 100644 app/models/server/raw/Statistics.js create mode 100644 app/models/server/raw/Statistics.ts create mode 100644 app/models/server/raw/Uploads.ts create mode 100644 app/models/server/raw/UserDataFiles.ts delete mode 100644 app/models/server/raw/WebdavAccounts.js create mode 100644 app/models/server/raw/WebdavAccounts.ts create mode 100644 app/models/server/raw/_Users.d.ts delete mode 100644 app/oauth2-server-config/server/oauth/default-services.js create mode 100644 app/oauth2-server-config/server/oauth/default-services.ts create mode 100644 definition/Federation.ts create mode 100644 definition/IAnalytic.ts create mode 100644 definition/IAvatar.ts create mode 100644 definition/ICredentialToken.ts create mode 100644 definition/ICustomSound.ts create mode 100644 definition/ICustomUserStatus.ts create mode 100644 definition/IEmojiCustom.ts create mode 100644 definition/IExportOperation.ts create mode 100644 definition/IInvite.ts create mode 100644 definition/IOAuthApps.ts create mode 100644 definition/IOEmbedCache.ts create mode 100644 definition/IReport.ts create mode 100644 definition/ISession.ts create mode 100644 definition/ISmarshHistory.ts create mode 100644 definition/IStatistic.ts create mode 100644 definition/IUpload.ts create mode 100644 definition/IUserDataFile.ts create mode 100644 definition/IWebdavAccount.ts rename ee/app/auditing/server/{index.js => index.ts} (79%) delete mode 100644 ee/app/authorization/server/resetEnterprisePermissions.js create mode 100644 ee/app/authorization/server/resetEnterprisePermissions.ts rename ee/app/canned-responses/server/{permissions.js => permissions.ts} (90%) delete mode 100644 ee/app/livechat-enterprise/server/permissions.js create mode 100644 ee/app/livechat-enterprise/server/permissions.ts rename server/lib/{pushConfig.js => pushConfig.ts} (83%) delete mode 100644 server/methods/afterVerifyEmail.js create mode 100644 server/methods/afterVerifyEmail.ts rename server/methods/{removeRoomOwner.js => removeRoomOwner.ts} (86%) rename server/methods/{removeUserFromRoom.js => removeUserFromRoom.ts} (89%) rename server/publications/settings/{index.js => index.ts} (55%) rename server/startup/migrations/{v174.js => v174.ts} (53%) delete mode 100644 server/startup/migrations/v176.js create mode 100644 server/startup/migrations/v176.ts rename server/startup/migrations/{v177.js => v177.ts} (72%) delete mode 100644 server/startup/migrations/v178.js create mode 100644 server/startup/migrations/v178.ts rename server/startup/migrations/{v184.js => v184.ts} (71%) delete mode 100644 server/startup/migrations/v185.js create mode 100644 server/startup/migrations/v185.ts rename server/startup/migrations/{v188.js => v188.ts} (51%) delete mode 100644 server/startup/migrations/v189.js create mode 100644 server/startup/migrations/v189.ts delete mode 100644 server/startup/migrations/v191.js create mode 100644 server/startup/migrations/v191.ts rename server/startup/migrations/{v195.js => v195.ts} (61%) rename server/startup/migrations/{v198.js => v198.ts} (54%) delete mode 100644 server/startup/migrations/v203.js create mode 100644 server/startup/migrations/v203.ts delete mode 100644 server/startup/migrations/v207.js create mode 100644 server/startup/migrations/v207.ts delete mode 100644 server/startup/migrations/v214.js create mode 100644 server/startup/migrations/v214.ts rename server/startup/migrations/{v215.js => v215.ts} (52%) delete mode 100644 server/startup/migrations/v217.js create mode 100644 server/startup/migrations/v217.ts delete mode 100644 server/startup/migrations/v218.js create mode 100644 server/startup/migrations/v218.ts rename server/startup/migrations/{v219.js => v219.ts} (70%) rename server/startup/migrations/{v220.js => v220.ts} (99%) delete mode 100644 server/startup/migrations/v223.js create mode 100644 server/startup/migrations/v223.ts delete mode 100644 server/startup/migrations/v224.js create mode 100644 server/startup/migrations/v224.ts rename server/startup/migrations/{v225.js => v225.ts} (70%) delete mode 100644 server/startup/migrations/v226.js create mode 100644 server/startup/migrations/v226.ts delete mode 100644 server/startup/migrations/v228.js create mode 100644 server/startup/migrations/v228.ts rename server/startup/migrations/{v229.js => v229.ts} (62%) diff --git a/app/api/server/lib/integrations.js b/app/api/server/lib/integrations.ts similarity index 65% rename from app/api/server/lib/integrations.js rename to app/api/server/lib/integrations.ts index 55db33a636a5e..ef5cab57ed942 100644 --- a/app/api/server/lib/integrations.js +++ b/app/api/server/lib/integrations.ts @@ -1,7 +1,9 @@ import { Integrations } from '../../../models/server/raw'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; +import { IIntegration } from '../../../../definition/IIntegration'; +import { IUser } from '../../../../definition/IUser'; -const hasIntegrationsPermission = async (userId, integration) => { +const hasIntegrationsPermission = async (userId: string, integration: IIntegration): Promise => { const type = integration.type === 'webhook-incoming' ? 'incoming' : 'outgoing'; if (await hasPermissionAsync(userId, `manage-${ type }-integrations`)) { @@ -15,7 +17,15 @@ const hasIntegrationsPermission = async (userId, integration) => { return false; }; -export const findOneIntegration = async ({ userId, integrationId, createdBy }) => { +export const findOneIntegration = async ({ + userId, + integrationId, + createdBy, +}: { + userId: string; + integrationId: string; + createdBy: IUser; +}): Promise => { const integration = await Integrations.findOneByIdAndCreatedByIfExists({ _id: integrationId, createdBy }); if (!integration) { throw new Error('The integration does not exists.'); diff --git a/app/api/server/lib/webdav.js b/app/api/server/lib/webdav.js deleted file mode 100644 index cf5a3c8ea8f1d..0000000000000 --- a/app/api/server/lib/webdav.js +++ /dev/null @@ -1,14 +0,0 @@ -import { WebdavAccounts } from '../../../models/server/raw'; - -export async function findWebdavAccountsByUserId({ uid }) { - return { - accounts: await WebdavAccounts.findWithUserId(uid, { - fields: { - _id: 1, - username: 1, - server_url: 1, - name: 1, - }, - }).toArray(), - }; -} diff --git a/app/api/server/lib/webdav.ts b/app/api/server/lib/webdav.ts new file mode 100644 index 0000000000000..fe2f17185bb6d --- /dev/null +++ b/app/api/server/lib/webdav.ts @@ -0,0 +1,16 @@ +import { WebdavAccounts } from '../../../models/server/raw'; +import { IWebdavAccount } from '../../../../definition/IWebdavAccount'; + +export async function findWebdavAccountsByUserId({ uid }: { uid: string }): Promise<{ accounts: IWebdavAccount[] }> { + return { + accounts: await WebdavAccounts.findWithUserId(uid, { + projection: { + _id: 1, + username: 1, + // eslint-disable-next-line @typescript-eslint/camelcase + server_url: 1, + name: 1, + }, + }).toArray(), + }; +} diff --git a/app/api/server/v1/channels.js b/app/api/server/v1/channels.js index 832aca11ad534..3e0139ec80b05 100644 --- a/app/api/server/v1/channels.js +++ b/app/api/server/v1/channels.js @@ -2,7 +2,8 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; import _ from 'underscore'; -import { Rooms, Subscriptions, Messages, Uploads, Integrations, Users } from '../../../models/server'; +import { Rooms, Subscriptions, Messages, Users } from '../../../models/server'; +import { Integrations, Uploads } from '../../../models/server/raw'; import { canAccessRoom, hasPermission, hasAtLeastOnePermission, hasAllPermission } from '../../../authorization/server'; import { mountIntegrationQueryBasedOnPermissions } from '../../../integrations/server/lib/mountQueriesBasedOnPermission'; import { normalizeMessagesForUser } from '../../../utils/server/lib/normalizeMessagesForUser'; @@ -294,19 +295,19 @@ API.v1.addRoute('channels.files', { authRequired: true }, { const ourQuery = Object.assign({}, query, { rid: findResult._id }); - const files = Uploads.find(ourQuery, { + const files = Promise.await(Uploads.find(ourQuery, { sort: sort || { name: 1 }, skip: offset, limit: count, fields, - }).fetch(); + }).toArray()); return API.v1.success({ files: files.map(addUserObjectToEveryObject), count: files.length, offset, - total: Uploads.find(ourQuery).count(), + total: Promise.await(Uploads.find(ourQuery).count()), }); }, }); @@ -340,21 +341,24 @@ API.v1.addRoute('channels.getIntegrations', { authRequired: true }, { } const { offset, count } = this.getPaginationItems(); - const { sort, fields, query } = this.parseJsonQuery(); + const { sort, fields: projection, query } = this.parseJsonQuery(); ourQuery = Object.assign(mountIntegrationQueryBasedOnPermissions(this.userId), query, ourQuery); - const integrations = Integrations.find(ourQuery, { + const cursor = Integrations.find(ourQuery, { sort: sort || { _createdAt: 1 }, skip: offset, limit: count, - fields, - }).fetch(); + projection, + }); + + const integrations = Promise.await(cursor.toArray()); + const total = Promise.await(cursor.count()); return API.v1.success({ integrations, count: integrations.length, offset, - total: Integrations.find(ourQuery).count(), + total, }); }, }); diff --git a/app/api/server/v1/email-inbox.js b/app/api/server/v1/email-inbox.js index e7452fc5ffe1e..61368a2d0a8ab 100644 --- a/app/api/server/v1/email-inbox.js +++ b/app/api/server/v1/email-inbox.js @@ -3,7 +3,7 @@ import { check, Match } from 'meteor/check'; import { API } from '../api'; import { findEmailInboxes, findOneEmailInbox, insertOneOrUpdateEmailInbox } from '../lib/emailInbox'; import { hasPermission } from '../../../authorization/server/functions/hasPermission'; -import { EmailInbox } from '../../../models'; +import { EmailInbox } from '../../../models/server/raw'; import Users from '../../../models/server/models/Users'; import { sendTestEmailToInbox } from '../../../../server/features/EmailInbox/EmailInbox_Outgoing'; @@ -79,12 +79,12 @@ API.v1.addRoute('email-inbox/:_id', { authRequired: true }, { const { _id } = this.urlParams; if (!_id) { throw new Error('error-invalid-param'); } - const emailInboxes = EmailInbox.findOneById(_id); + const emailInboxes = Promise.await(EmailInbox.findOneById(_id)); if (!emailInboxes) { return API.v1.notFound(); } - EmailInbox.removeById(_id); + Promise.await(EmailInbox.removeById(_id)); return API.v1.success({ _id }); }, }); diff --git a/app/api/server/v1/emoji-custom.js b/app/api/server/v1/emoji-custom.js index 403cca1d189ca..092e41c1de973 100644 --- a/app/api/server/v1/emoji-custom.js +++ b/app/api/server/v1/emoji-custom.js @@ -1,6 +1,6 @@ import { Meteor } from 'meteor/meteor'; -import { EmojiCustom } from '../../../models/server'; +import { EmojiCustom } from '../../../models/server/raw'; import { API } from '../api'; import { getUploadFormData } from '../lib/getUploadFormData'; import { findEmojisCustom } from '../lib/emoji-custom'; @@ -19,15 +19,15 @@ API.v1.addRoute('emoji-custom.list', { authRequired: true }, { } return API.v1.success({ emojis: { - update: EmojiCustom.find({ ...query, _updatedAt: { $gt: updatedSinceDate } }).fetch(), - remove: EmojiCustom.trashFindDeletedAfter(updatedSinceDate).fetch(), + update: Promise.await(EmojiCustom.find({ ...query, _updatedAt: { $gt: updatedSinceDate } }).toArray()), + remove: Promise.await(EmojiCustom.trashFindDeletedAfter(updatedSinceDate).toArray()), }, }); } return API.v1.success({ emojis: { - update: EmojiCustom.find(query).fetch(), + update: Promise.await(EmojiCustom.find(query).toArray()), remove: [], }, }); @@ -88,7 +88,7 @@ API.v1.addRoute('emoji-custom.update', { authRequired: true }, { throw new Meteor.Error('The required "_id" query param is missing.'); } - const emojiToUpdate = EmojiCustom.findOneById(fields._id); + const emojiToUpdate = Promise.await(EmojiCustom.findOneById(fields._id)); if (!emojiToUpdate) { throw new Meteor.Error('Emoji not found.'); } diff --git a/app/api/server/v1/groups.js b/app/api/server/v1/groups.js index 4cf5d029ad6b9..141bb94d49d61 100644 --- a/app/api/server/v1/groups.js +++ b/app/api/server/v1/groups.js @@ -3,7 +3,8 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; import { mountIntegrationQueryBasedOnPermissions } from '../../../integrations/server/lib/mountQueriesBasedOnPermission'; -import { Subscriptions, Rooms, Messages, Uploads, Integrations, Users } from '../../../models/server'; +import { Subscriptions, Rooms, Messages, Users } from '../../../models/server'; +import { Integrations, Uploads } from '../../../models/server/raw'; import { hasPermission, hasAtLeastOnePermission, canAccessRoom, hasAllPermission } from '../../../authorization/server'; import { normalizeMessagesForUser } from '../../../utils/server/lib/normalizeMessagesForUser'; import { API } from '../api'; @@ -272,18 +273,18 @@ API.v1.addRoute('groups.files', { authRequired: true }, { const ourQuery = Object.assign({}, query, { rid: findResult.rid }); - const files = Uploads.find(ourQuery, { + const files = Promise.await(Uploads.find(ourQuery, { sort: sort || { name: 1 }, skip: offset, limit: count, fields, - }).fetch(); + }).toArray()); return API.v1.success({ files: files.map(addUserObjectToEveryObject), count: files.length, offset, - total: Uploads.find(ourQuery).count(), + total: Promise.await(Uploads.find(ourQuery).count()), }); }, }); @@ -312,21 +313,24 @@ API.v1.addRoute('groups.getIntegrations', { authRequired: true }, { } const { offset, count } = this.getPaginationItems(); - const { sort, fields, query } = this.parseJsonQuery(); + const { sort, fields: projection, query } = this.parseJsonQuery(); const ourQuery = Object.assign(mountIntegrationQueryBasedOnPermissions(this.userId), query, { channel: { $in: channelsToSearch } }); - const integrations = Integrations.find(ourQuery, { + const cursor = Integrations.find(ourQuery, { sort: sort || { _createdAt: 1 }, skip: offset, limit: count, - fields, - }).fetch(); + projection, + }); + + const integrations = Promise.await(cursor.toArray()); + const total = Promise.await(cursor.count()); return API.v1.success({ integrations, count: integrations.length, offset, - total: Integrations.find(ourQuery).count(), + total, }); }, }); diff --git a/app/api/server/v1/im.js b/app/api/server/v1/im.js index a0325298f3f21..41d3d5dfb273c 100644 --- a/app/api/server/v1/im.js +++ b/app/api/server/v1/im.js @@ -1,7 +1,8 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; -import { Subscriptions, Uploads, Users, Messages, Rooms } from '../../../models/server'; +import { Subscriptions, Users, Messages, Rooms } from '../../../models/server'; +import { Uploads } from '../../../models/server/raw'; import { canAccessRoom, hasPermission } from '../../../authorization/server'; import { normalizeMessagesForUser } from '../../../utils/server/lib/normalizeMessagesForUser'; import { settings } from '../../../settings/server'; @@ -148,18 +149,18 @@ API.v1.addRoute(['dm.files', 'im.files'], { authRequired: true }, { const ourQuery = Object.assign({}, query, { rid: findResult.room._id }); - const files = Uploads.find(ourQuery, { + const files = Promise.await(Uploads.find(ourQuery, { sort: sort || { name: 1 }, skip: offset, limit: count, fields, - }).fetch(); + }).toArray()); return API.v1.success({ files: files.map(addUserObjectToEveryObject), count: files.length, offset, - total: Uploads.find(ourQuery).count(), + total: Promise.await(Uploads.find(ourQuery).count()), }); }, }); diff --git a/app/api/server/v1/instances.ts b/app/api/server/v1/instances.ts index e6586a7c12a73..d3db3489537a3 100644 --- a/app/api/server/v1/instances.ts +++ b/app/api/server/v1/instances.ts @@ -1,7 +1,7 @@ import { getInstanceConnection } from '../../../../server/stream/streamBroadcast'; import { hasPermission } from '../../../authorization/server'; import { API } from '../api'; -import InstanceStatus from '../../../models/server/models/InstanceStatus'; +import { InstanceStatus } from '../../../models/server/raw'; import { IInstanceStatus } from '../../../../definition/IInstanceStatus'; API.v1.addRoute('instances.get', { authRequired: true }, { @@ -10,7 +10,7 @@ API.v1.addRoute('instances.get', { authRequired: true }, { return API.v1.unauthorized(); } - const instances = InstanceStatus.find().fetch(); + const instances = Promise.await(InstanceStatus.find().toArray()); return API.v1.success({ instances: instances.map((instance: IInstanceStatus) => { diff --git a/app/api/server/v1/integrations.js b/app/api/server/v1/integrations.js index 480c3e8743eb1..c05544eb4b821 100644 --- a/app/api/server/v1/integrations.js +++ b/app/api/server/v1/integrations.js @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; import { hasAtLeastOnePermission } from '../../../authorization/server'; -import { IntegrationHistory, Integrations } from '../../../models'; +import { Integrations, IntegrationHistory } from '../../../models/server/raw'; import { API } from '../api'; import { mountIntegrationHistoryQueryBasedOnPermissions, mountIntegrationQueryBasedOnPermissions } from '../../../integrations/server/lib/mountQueriesBasedOnPermission'; import { findOneIntegration } from '../lib/integrations'; @@ -63,21 +63,24 @@ API.v1.addRoute('integrations.history', { authRequired: true }, { const { id } = this.queryParams; const { offset, count } = this.getPaginationItems(); - const { sort, fields, query } = this.parseJsonQuery(); + const { sort, fields: projection, query } = this.parseJsonQuery(); const ourQuery = Object.assign(mountIntegrationHistoryQueryBasedOnPermissions(this.userId, id), query); - const history = IntegrationHistory.find(ourQuery, { + const cursor = IntegrationHistory.find(ourQuery, { sort: sort || { _updatedAt: -1 }, skip: offset, limit: count, - fields, - }).fetch(); + projection, + }); + + const history = Promise.await(cursor.toArray()); + const total = Promise.await(cursor.count()); return API.v1.success({ history, offset, items: history.length, - total: IntegrationHistory.find(ourQuery).count(), + total, }); }, }); @@ -94,21 +97,25 @@ API.v1.addRoute('integrations.list', { authRequired: true }, { } const { offset, count } = this.getPaginationItems(); - const { sort, fields, query } = this.parseJsonQuery(); + const { sort, fields: projection, query } = this.parseJsonQuery(); const ourQuery = Object.assign(mountIntegrationQueryBasedOnPermissions(this.userId), query); - const integrations = Integrations.find(ourQuery, { + const cursor = Integrations.find(ourQuery, { sort: sort || { ts: -1 }, skip: offset, limit: count, - fields, - }).fetch(); + projection, + }); + + const total = Promise.await(cursor.count()); + + const integrations = Promise.await(cursor.toArray()); return API.v1.success({ integrations, offset, items: integrations.length, - total: Integrations.find(ourQuery).count(), + total, }); }, }); @@ -138,9 +145,9 @@ API.v1.addRoute('integrations.remove', { authRequired: true }, { switch (this.bodyParams.type) { case 'webhook-outgoing': if (this.bodyParams.target_url) { - integration = Integrations.findOne({ urls: this.bodyParams.target_url }); + integration = Promise.await(Integrations.findOne({ urls: this.bodyParams.target_url })); } else if (this.bodyParams.integrationId) { - integration = Integrations.findOne({ _id: this.bodyParams.integrationId }); + integration = Promise.await(Integrations.findOne({ _id: this.bodyParams.integrationId })); } if (!integration) { @@ -155,7 +162,7 @@ API.v1.addRoute('integrations.remove', { authRequired: true }, { integration, }); case 'webhook-incoming': - integration = Integrations.findOne({ _id: this.bodyParams.integrationId }); + integration = Promise.await(Integrations.findOne({ _id: this.bodyParams.integrationId })); if (!integration) { return API.v1.failure('No integration found.'); @@ -217,9 +224,9 @@ API.v1.addRoute('integrations.update', { authRequired: true }, { switch (this.bodyParams.type) { case 'webhook-outgoing': if (this.bodyParams.target_url) { - integration = Integrations.findOne({ urls: this.bodyParams.target_url }); + integration = Promise.await(Integrations.findOne({ urls: this.bodyParams.target_url })); } else if (this.bodyParams.integrationId) { - integration = Integrations.findOne({ _id: this.bodyParams.integrationId }); + integration = Promise.await(Integrations.findOne({ _id: this.bodyParams.integrationId })); } if (!integration) { @@ -229,10 +236,10 @@ API.v1.addRoute('integrations.update', { authRequired: true }, { Meteor.call('updateOutgoingIntegration', integration._id, this.bodyParams); return API.v1.success({ - integration: Integrations.findOne({ _id: integration._id }), + integration: Promise.await(Integrations.findOne({ _id: integration._id })), }); case 'webhook-incoming': - integration = Integrations.findOne({ _id: this.bodyParams.integrationId }); + integration = Promise.await(Integrations.findOne({ _id: this.bodyParams.integrationId })); if (!integration) { return API.v1.failure('No integration found.'); @@ -241,7 +248,7 @@ API.v1.addRoute('integrations.update', { authRequired: true }, { Meteor.call('updateIncomingIntegration', integration._id, this.bodyParams); return API.v1.success({ - integration: Integrations.findOne({ _id: integration._id }), + integration: Promise.await(Integrations.findOne({ _id: integration._id })), }); default: return API.v1.failure('Invalid integration type.'); diff --git a/app/api/server/v1/invites.js b/app/api/server/v1/invites.js index fd17ec3661908..f901247547db3 100644 --- a/app/api/server/v1/invites.js +++ b/app/api/server/v1/invites.js @@ -7,7 +7,7 @@ import { validateInviteToken } from '../../../invites/server/functions/validateI API.v1.addRoute('listInvites', { authRequired: true }, { get() { - const result = listInvites(this.userId); + const result = Promise.await(listInvites(this.userId)); return API.v1.success(result); }, }); @@ -15,7 +15,7 @@ API.v1.addRoute('listInvites', { authRequired: true }, { API.v1.addRoute('findOrCreateInvite', { authRequired: true }, { post() { const { rid, days, maxUses } = this.bodyParams; - const result = findOrCreateInvite(this.userId, { rid, days, maxUses }); + const result = Promise.await(findOrCreateInvite(this.userId, { rid, days, maxUses })); return API.v1.success(result); }, @@ -24,7 +24,7 @@ API.v1.addRoute('findOrCreateInvite', { authRequired: true }, { API.v1.addRoute('removeInvite/:_id', { authRequired: true }, { delete() { const { _id } = this.urlParams; - const result = removeInvite(this.userId, { _id }); + const result = Promise.await(removeInvite(this.userId, { _id })); return API.v1.success(result); }, @@ -34,7 +34,7 @@ API.v1.addRoute('useInviteToken', { authRequired: true }, { post() { const { token } = this.bodyParams; // eslint-disable-next-line react-hooks/rules-of-hooks - const result = useInviteToken(this.userId, token); + const result = Promise.await(useInviteToken(this.userId, token)); return API.v1.success(result); }, @@ -46,7 +46,7 @@ API.v1.addRoute('validateInviteToken', { authRequired: false }, { let valid = true; try { - validateInviteToken(token); + Promise.await(validateInviteToken(token)); } catch (e) { valid = false; } diff --git a/app/api/server/v1/permissions.js b/app/api/server/v1/permissions.ts similarity index 74% rename from app/api/server/v1/permissions.js rename to app/api/server/v1/permissions.ts index 4ac1661f07864..c2aab9afda543 100644 --- a/app/api/server/v1/permissions.js +++ b/app/api/server/v1/permissions.ts @@ -1,31 +1,29 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; -import { hasPermission } from '../../../authorization'; -import { Permissions, Roles } from '../../../models/server'; +import { hasPermission } from '../../../authorization/server'; import { API } from '../api'; +import { Permissions, Roles } from '../../../models/server/raw'; API.v1.addRoute('permissions.listAll', { authRequired: true }, { get() { const { updatedSince } = this.queryParams; - let updatedSinceDate; + let updatedSinceDate: Date | undefined; if (updatedSince) { if (isNaN(Date.parse(updatedSince))) { throw new Meteor.Error('error-roomId-param-invalid', 'The "updatedSince" query parameter must be a valid date.'); - } else { - updatedSinceDate = new Date(updatedSince); } + updatedSinceDate = new Date(updatedSince); } - let result; - Meteor.runAsUser(this.userId, () => { result = Meteor.call('permissions/get', updatedSinceDate); }); + const result = Promise.await(Meteor.call('permissions/get', updatedSinceDate)); if (Array.isArray(result)) { - result = { + return API.v1.success({ update: result, remove: [], - }; + }); } return API.v1.success(result); @@ -52,14 +50,14 @@ API.v1.addRoute('permissions.update', { authRequired: true }, { Object.keys(this.bodyParams.permissions).forEach((key) => { const element = this.bodyParams.permissions[key]; - if (!Permissions.findOneById(element._id)) { + if (!Promise.await(Permissions.findOneById(element._id))) { permissionNotFound = true; } Object.keys(element.roles).forEach((key) => { - const subelement = element.roles[key]; + const subElement = element.roles[key]; - if (!Roles.findOneById(subelement)) { + if (!Promise.await(Roles.findOneById(subElement))) { roleNotFound = true; } }); @@ -77,7 +75,7 @@ API.v1.addRoute('permissions.update', { authRequired: true }, { Permissions.createOrUpdate(element._id, element.roles); }); - const result = Meteor.runAsUser(this.userId, () => Meteor.call('permissions/get')); + const result = Promise.await(Meteor.call('permissions/get')); return API.v1.success({ permissions: result, diff --git a/app/api/server/v1/roles.js b/app/api/server/v1/roles.ts similarity index 76% rename from app/api/server/v1/roles.js rename to app/api/server/v1/roles.ts index 39d89164e4ab7..8d87ac7f25c79 100644 --- a/app/api/server/v1/roles.js +++ b/app/api/server/v1/roles.ts @@ -1,15 +1,16 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; -import { Roles, Users } from '../../../models'; +import { Users } from '../../../models/server'; import { API } from '../api'; import { getUsersInRole, hasPermission, hasRole } from '../../../authorization/server'; import { settings } from '../../../settings/server/index'; import { api } from '../../../../server/sdk/api'; +import { Roles } from '../../../models/server/raw'; API.v1.addRoute('roles.list', { authRequired: true }, { get() { - const roles = Roles.find({}, { fields: { _updatedAt: 0 } }).fetch(); + const roles = Promise.await(Roles.find({}, { fields: { _updatedAt: 0 } }).toArray()); return API.v1.success({ roles }); }, @@ -25,8 +26,8 @@ API.v1.addRoute('roles.sync', { authRequired: true }, { return API.v1.success({ roles: { - update: Roles.findByUpdatedDate(new Date(updatedSince), { fields: API.v1.defaultFieldsToExclude }).fetch(), - remove: Roles.trashFindDeletedAfter(new Date(updatedSince)).fetch(), + update: Promise.await(Roles.findByUpdatedDate(new Date(updatedSince), { fields: API.v1.defaultFieldsToExclude }).toArray()), + remove: Promise.await(Roles.trashFindDeletedAfter(new Date(updatedSince)).toArray()), }, }); }, @@ -52,15 +53,15 @@ API.v1.addRoute('roles.create', { authRequired: true }, { throw new Meteor.Error('error-action-not-allowed', 'Accessing permissions is not allowed'); } - if (Roles.findOneByIdOrName(roleData.name)) { + if (Promise.await(Roles.findOneByIdOrName(roleData.name))) { throw new Meteor.Error('error-duplicate-role-names-not-allowed', 'Role name already exists'); } if (['Users', 'Subscriptions'].includes(roleData.scope) === false) { roleData.scope = 'Users'; } - - const roleId = Roles.createWithRandomId(roleData.name, roleData.scope, roleData.description, false, roleData.mandatory2fa); + const a = Roles.createWithRandomId(roleData.name, roleData.scope, roleData.description, false, roleData.mandatory2fa); + const roleId = Promise.await(a).insertedId; if (settings.get('UI_DisplayRoles')) { api.broadcast('user.roleUpdate', { @@ -70,7 +71,7 @@ API.v1.addRoute('roles.create', { authRequired: true }, { } return API.v1.success({ - role: Roles.findOneByIdOrName(roleId, { fields: API.v1.defaultFieldsToExclude }), + role: Promise.await(Roles.findOneByIdOrName(roleId, { fields: API.v1.defaultFieldsToExclude })), }); }, }); @@ -95,7 +96,7 @@ API.v1.addRoute('roles.addUserToRole', { authRequired: true }, { }); return API.v1.success({ - role: Roles.findOneByIdOrName(this.bodyParams.roleName, { fields: API.v1.defaultFieldsToExclude }), + role: Promise.await(Roles.findOneByIdOrName(this.bodyParams.roleName, { fields: API.v1.defaultFieldsToExclude })), }); }, }); @@ -121,13 +122,14 @@ API.v1.addRoute('roles.getUsersInRole', { authRequired: true }, { if (roomId && !hasPermission(this.userId, 'view-other-user-channels')) { throw new Meteor.Error('error-not-allowed', 'Not allowed'); } - const users = getUsersInRole(role, roomId, { + const users = Promise.await(getUsersInRole(role, roomId, { limit: count, sort: { username: 1 }, skip: offset, fields, - }); - return API.v1.success({ users: users.fetch(), total: users.count() }); + })); + + return API.v1.success({ users: Promise.await(users.toArray()), total: Promise.await(users.count()) }); }, }); @@ -149,7 +151,7 @@ API.v1.addRoute('roles.update', { authRequired: true }, { mandatory2fa: this.bodyParams.mandatory2fa, }; - const role = Roles.findOneByIdOrName(roleData.roleId); + const role = Promise.await(Roles.findOneByIdOrName(roleData.roleId)); if (!role) { throw new Meteor.Error('error-invalid-roleId', 'This role does not exist'); @@ -160,7 +162,7 @@ API.v1.addRoute('roles.update', { authRequired: true }, { } if (roleData.name) { - const otherRole = Roles.findOneByIdOrName(roleData.name); + const otherRole = Promise.await(Roles.findOneByIdOrName(roleData.name)); if (otherRole && otherRole._id !== role._id) { throw new Meteor.Error('error-duplicate-role-names-not-allowed', 'Role name already exists'); } @@ -172,7 +174,7 @@ API.v1.addRoute('roles.update', { authRequired: true }, { } } - Roles.updateById(roleData.roleId, roleData.name, roleData.scope, roleData.description, roleData.mandatory2fa); + Promise.await(Roles.updateById(roleData.roleId, roleData.name, roleData.scope, roleData.description, roleData.mandatory2fa)); if (settings.get('UI_DisplayRoles')) { api.broadcast('user.roleUpdate', { @@ -182,7 +184,7 @@ API.v1.addRoute('roles.update', { authRequired: true }, { } return API.v1.success({ - role: Roles.findOneByIdOrName(roleData.roleId, { fields: API.v1.defaultFieldsToExclude }), + role: Promise.await(Roles.findOneByIdOrName(roleData.roleId, { fields: API.v1.defaultFieldsToExclude })), }); }, }); @@ -197,7 +199,7 @@ API.v1.addRoute('roles.delete', { authRequired: true }, { throw new Meteor.Error('error-action-not-allowed', 'Accessing permissions is not allowed'); } - const role = Roles.findOneByIdOrName(this.bodyParams.roleId); + const role = Promise.await(Roles.findOneByIdOrName(this.bodyParams.roleId)); if (!role) { throw new Meteor.Error('error-invalid-roleId', 'This role does not exist'); @@ -207,13 +209,13 @@ API.v1.addRoute('roles.delete', { authRequired: true }, { throw new Meteor.Error('error-role-protected', 'Cannot delete a protected role'); } - const existingUsers = Roles.findUsersInRole(role.name, role.scope); + const existingUsers = Promise.await(Roles.findUsersInRole(role.name, role.scope)); - if (existingUsers && existingUsers.count() > 0) { + if (existingUsers && Promise.await(existingUsers.count()) > 0) { throw new Meteor.Error('error-role-in-use', 'Cannot delete role because it\'s in use'); } - Roles.remove(role._id); + Promise.await(Roles.removeById(role._id)); return API.v1.success(); }, @@ -243,7 +245,7 @@ API.v1.addRoute('roles.removeUserFromRole', { authRequired: true }, { throw new Meteor.Error('error-invalid-user', 'There is no user with this username'); } - const role = Roles.findOneByIdOrName(data.roleName); + const role = Promise.await(Roles.findOneByIdOrName(data.roleName)); if (!role) { throw new Meteor.Error('error-invalid-roleId', 'This role does not exist'); @@ -254,13 +256,13 @@ API.v1.addRoute('roles.removeUserFromRole', { authRequired: true }, { } if (role._id === 'admin') { - const adminCount = Roles.findUsersInRole('admin').count(); + const adminCount = Promise.await(Promise.await(Roles.findUsersInRole('admin')).count()); if (adminCount === 1) { throw new Meteor.Error('error-admin-required', 'You need to have at least one admin'); } } - Roles.removeUserRoles(user._id, role.name, data.scope); + Promise.await(Roles.removeUserRoles(user._id, [role.name], data.scope)); if (settings.get('UI_DisplayRoles')) { api.broadcast('user.roleUpdate', { diff --git a/app/api/server/v1/settings.js b/app/api/server/v1/settings.ts similarity index 54% rename from app/api/server/v1/settings.js rename to app/api/server/v1/settings.ts index b9c720f3522a5..1f60f2ea2c589 100644 --- a/app/api/server/v1/settings.js +++ b/app/api/server/v1/settings.ts @@ -3,24 +3,65 @@ import { Match, check } from 'meteor/check'; import { ServiceConfiguration } from 'meteor/service-configuration'; import _ from 'underscore'; -import { Settings } from '../../../models/server'; -import { hasPermission } from '../../../authorization'; +import { Settings } from '../../../models/server/raw'; +import { hasPermission } from '../../../authorization/server'; import { API } from '../api'; import { SettingsEvents, settings } from '../../../settings/server'; import { setValue } from '../../../settings/server/raw'; +import { ISetting, ISettingColor, isSettingAction, isSettingColor } from '../../../../definition/ISetting'; -const fetchSettings = (query, sort, offset, count, fields) => { - const settings = Settings.find(query, { + +const fetchSettings = async (query: Parameters[0], sort: Parameters[1]['sort'], offset: Parameters[1]['skip'], count: Parameters[1]['limit'], fields: Parameters[1]['projection']): Promise => { + const settings = await Settings.find(query, { sort: sort || { _id: 1 }, skip: offset, limit: count, - fields: Object.assign({ _id: 1, value: 1, enterprise: 1, invalidValue: 1, modules: 1 }, fields), - }).fetch(); + projection: { _id: 1, value: 1, enterprise: 1, invalidValue: 1, modules: 1, ...fields }, + }).toArray() as unknown as ISetting[]; + SettingsEvents.emit('fetch-settings', settings); return settings; }; +type OauthCustomConfiguration = { + _id: string; + clientId?: string; + custom: unknown; + service?: string; + serverURL: unknown; + tokenPath: unknown; + identityPath: unknown; + authorizePath: unknown; + scope: unknown; + loginStyle: unknown; + tokenSentVia: unknown; + identityTokenSentVia: unknown; + keyField: unknown; + usernameField: unknown; + emailField: unknown; + nameField: unknown; + avatarField: unknown; + rolesClaim: unknown; + groupsClaim: unknown; + mapChannels: unknown; + channelsMap: unknown; + channelsAdmin: unknown; + mergeUsers: unknown; + mergeRoles: unknown; + accessTokenParam: unknown; + showButton: unknown; + + appId: unknown; + consumerKey: unknown; + + clientConfig: unknown; + buttonLabelText: unknown; + buttonLabelColor: unknown; + buttonColor: unknown; +} +const isOauthCustomConfiguration = (config: any): config is OauthCustomConfiguration => Boolean(config); + // settings endpoints API.v1.addRoute('settings.public', { authRequired: false }, { get() { @@ -34,7 +75,7 @@ API.v1.addRoute('settings.public', { authRequired: false }, { ourQuery = Object.assign({}, query, ourQuery); - const settings = fetchSettings(ourQuery, sort, offset, count, fields); + const settings = Promise.await(fetchSettings(ourQuery, sort, offset, count, fields)); return API.v1.success({ settings, @@ -47,11 +88,15 @@ API.v1.addRoute('settings.public', { authRequired: false }, { API.v1.addRoute('settings.oauth', { authRequired: false }, { get() { - const mountOAuthServices = () => { + const mountOAuthServices = (): object => { const oAuthServicesEnabled = ServiceConfiguration.configurations.find({}, { fields: { secret: 0 } }).fetch(); return oAuthServicesEnabled.map((service) => { - if (service.custom || ['saml', 'cas', 'wordpress'].includes(service.service)) { + if (!isOauthCustomConfiguration(service)) { + return service; + } + + if (service.custom || (service.service && ['saml', 'cas', 'wordpress'].includes(service.service))) { return { ...service }; } @@ -93,7 +138,7 @@ API.v1.addRoute('settings', { authRequired: true }, { const { offset, count } = this.getPaginationItems(); const { sort, fields, query } = this.parseJsonQuery(); - let ourQuery = { + let ourQuery: Parameters[0] = { hidden: { $ne: true }, }; @@ -103,7 +148,7 @@ API.v1.addRoute('settings', { authRequired: true }, { ourQuery = Object.assign({}, query, ourQuery); - const settings = fetchSettings(ourQuery, sort, offset, count, fields); + const settings = Promise.await(fetchSettings(ourQuery, sort, offset, count, fields)); return API.v1.success({ settings, @@ -119,26 +164,34 @@ API.v1.addRoute('settings/:_id', { authRequired: true }, { if (!hasPermission(this.userId, 'view-privileged-setting')) { return API.v1.unauthorized(); } - - return API.v1.success(_.pick(Settings.findOneNotHiddenById(this.urlParams._id), '_id', 'value')); + const setting = Promise.await(Settings.findOneNotHiddenById(this.urlParams._id)); + if (!setting) { + return API.v1.failure(); + } + return API.v1.success(_.pick(setting, '_id', 'value')); }, post: { twoFactorRequired: true, - action() { + action(this: any): void { if (!hasPermission(this.userId, 'edit-privileged-setting')) { return API.v1.unauthorized(); } // allow special handling of particular setting types - const setting = Settings.findOneNotHiddenById(this.urlParams._id); - if (setting.type === 'action' && this.bodyParams && this.bodyParams.execute) { + const setting = Promise.await(Settings.findOneNotHiddenById(this.urlParams._id)); + + if (!setting) { + return API.v1.failure(); + } + + if (isSettingAction(setting) && this.bodyParams && this.bodyParams.execute) { // execute the configured method Meteor.call(setting.value); return API.v1.success(); } - if (setting.type === 'color' && this.bodyParams && this.bodyParams.editor && this.bodyParams.value) { - Settings.updateOptionsById(this.urlParams._id, { editor: this.bodyParams.editor }); + if (isSettingColor(setting) && this.bodyParams && this.bodyParams.editor && this.bodyParams.value) { + Settings.updateOptionsById(this.urlParams._id, { editor: this.bodyParams.editor }); Settings.updateValueNotHiddenById(this.urlParams._id, this.bodyParams.value); return API.v1.success(); } @@ -146,8 +199,12 @@ API.v1.addRoute('settings/:_id', { authRequired: true }, { check(this.bodyParams, { value: Match.Any, }); - if (Settings.updateValueNotHiddenById(this.urlParams._id, this.bodyParams.value)) { - settings.set(Settings.findOneNotHiddenById(this.urlParams._id)); + if (Promise.await(Settings.updateValueNotHiddenById(this.urlParams._id, this.bodyParams.value))) { + const s = Promise.await(Settings.findOneNotHiddenById(this.urlParams._id)); + if (!s) { + return API.v1.failure(); + } + settings.set(s); setValue(this.urlParams._id, this.bodyParams.value); return API.v1.success(); } diff --git a/app/apps/server/bridges/internal.ts b/app/apps/server/bridges/internal.ts index 0d09646f38938..154adbdeb1040 100644 --- a/app/apps/server/bridges/internal.ts +++ b/app/apps/server/bridges/internal.ts @@ -2,8 +2,9 @@ import { InternalBridge } from '@rocket.chat/apps-engine/server/bridges/Internal import { ISetting } from '@rocket.chat/apps-engine/definition/settings'; import { AppServerOrchestrator } from '../orchestrator'; -import { Subscriptions, Settings } from '../../../models/server'; +import { Subscriptions } from '../../../models/server'; import { ISubscription } from '../../../../definition/ISubscription'; +import { Settings } from '../../../models/server/raw'; export class AppInternalBridge extends InternalBridge { // eslint-disable-next-line no-empty-function @@ -30,7 +31,7 @@ export class AppInternalBridge extends InternalBridge { } protected async getWorkspacePublicKey(): Promise { - const publicKeySetting = Settings.findById('Cloud_Workspace_PublicKey').fetch()[0]; + const publicKeySetting = await Settings.findOneById('Cloud_Workspace_PublicKey'); return this.orch.getConverters()?.get('settings').convertToApp(publicKeySetting); } diff --git a/app/apps/server/bridges/settings.ts b/app/apps/server/bridges/settings.ts index ad1c234b0af27..ba0626434ff92 100644 --- a/app/apps/server/bridges/settings.ts +++ b/app/apps/server/bridges/settings.ts @@ -1,7 +1,7 @@ import { ISetting } from '@rocket.chat/apps-engine/definition/settings'; import { ServerSettingBridge } from '@rocket.chat/apps-engine/server/bridges/ServerSettingBridge'; -import { Settings } from '../../../models/server'; +import { Settings } from '../../../models/server/raw'; import { AppServerOrchestrator } from '../orchestrator'; export class AppSettingBridge extends ServerSettingBridge { @@ -13,9 +13,8 @@ export class AppSettingBridge extends ServerSettingBridge { protected async getAll(appId: string): Promise> { this.orch.debugLog(`The App ${ appId } is getting all the settings.`); - return Settings.find({ secret: false }) - .fetch() - .map((s: ISetting) => this.orch.getConverters()?.get('settings').convertToApp(s)); + const settings = await Settings.find({ secret: false }).toArray(); + return settings.map((s) => this.orch.getConverters()?.get('settings').convertToApp(s)); } protected async getOneById(id: string, appId: string): Promise { @@ -46,8 +45,8 @@ export class AppSettingBridge extends ServerSettingBridge { protected async isReadableById(id: string, appId: string): Promise { this.orch.debugLog(`The App ${ appId } is checking if they can read the setting ${ id }.`); - - return !Settings.findOneById(id).secret; + const setting = await Settings.findOneById(id); + return Boolean(setting && !setting.secret); } protected async updateOne(setting: ISetting & { id: string }, appId: string): Promise { diff --git a/app/apps/server/communication/rest.js b/app/apps/server/communication/rest.js index 6d84f3797b95b..1386ba39cfa89 100644 --- a/app/apps/server/communication/rest.js +++ b/app/apps/server/communication/rest.js @@ -6,9 +6,10 @@ import { getUploadFormData } from '../../../api/server/lib/getUploadFormData'; import { getWorkspaceAccessToken, getUserCloudAccessToken } from '../../../cloud/server'; import { settings } from '../../../settings/server'; import { Info } from '../../../utils'; -import { Settings, Users } from '../../../models/server'; +import { Users } from '../../../models/server'; import { Apps } from '../orchestrator'; import { formatAppInstanceForRest } from '../../lib/misc/formatAppInstanceForRest'; +import { Settings } from '../../../models/server/raw'; const appsEngineVersionForMarketplace = Info.marketplaceApiVersion.replace(/-.*/g, ''); const getDefaultHeaders = () => ({ @@ -67,7 +68,7 @@ export class AppsRestApi { // Gets the Apps from the marketplace if (this.queryParams.marketplace) { const headers = getDefaultHeaders(); - const token = getWorkspaceAccessToken(); + const token = Promise.await(getWorkspaceAccessToken()); if (token) { headers.Authorization = `Bearer ${ token }`; } @@ -91,7 +92,7 @@ export class AppsRestApi { if (this.queryParams.categories) { const headers = getDefaultHeaders(); - const token = getWorkspaceAccessToken(); + const token = Promise.await(getWorkspaceAccessToken()); if (token) { headers.Authorization = `Bearer ${ token }`; } @@ -187,7 +188,7 @@ export class AppsRestApi { }); const marketplacePromise = new Promise((resolve, reject) => { - const token = getWorkspaceAccessToken(); + const token = Promise.await(getWorkspaceAccessToken()); HTTP.get(`${ baseUrl }/v1/apps/${ this.bodyParams.appId }?appVersion=${ this.bodyParams.version }`, { headers: { @@ -307,7 +308,7 @@ export class AppsRestApi { const baseUrl = orchestrator.getMarketplaceUrl(); const headers = {}; - const token = getWorkspaceAccessToken(); + const token = Promise.await(getWorkspaceAccessToken()); if (token) { headers.Authorization = `Bearer ${ token }`; } @@ -337,7 +338,7 @@ export class AppsRestApi { const baseUrl = orchestrator.getMarketplaceUrl(); const headers = {}; // DO NOT ATTACH THE FRAMEWORK/ENGINE VERSION HERE. - const token = getWorkspaceAccessToken(); + const token = Promise.await(getWorkspaceAccessToken()); if (token) { headers.Authorization = `Bearer ${ token }`; } @@ -363,7 +364,7 @@ export class AppsRestApi { const baseUrl = orchestrator.getMarketplaceUrl(); const headers = getDefaultHeaders(); - const token = getWorkspaceAccessToken(); + const token = Promise.await(getWorkspaceAccessToken()); if (token) { headers.Authorization = `Bearer ${ token }`; } @@ -507,12 +508,12 @@ export class AppsRestApi { const baseUrl = orchestrator.getMarketplaceUrl(); const headers = getDefaultHeaders(); - const token = getWorkspaceAccessToken(); + const token = Promise.await(getWorkspaceAccessToken()); if (token) { headers.Authorization = `Bearer ${ token }`; } - const [workspaceIdSetting] = Settings.findById('Cloud_Workspace_Id').fetch(); + const workspaceIdSetting = Promise.await(Settings.findOneById('Cloud_Workspace_Id')); let result; try { diff --git a/app/apps/server/converters/settings.js b/app/apps/server/converters/settings.js index 82ffcd2b2f0f1..bc5949bc7ccd1 100644 --- a/app/apps/server/converters/settings.js +++ b/app/apps/server/converters/settings.js @@ -1,14 +1,14 @@ import { SettingType } from '@rocket.chat/apps-engine/definition/settings'; -import { Settings } from '../../../models'; +import { Settings } from '../../../models/server/raw'; export class AppSettingsConverter { constructor(orch) { this.orch = orch; } - convertById(settingId) { - const setting = Settings.findOneNotHiddenById(settingId); + async convertById(settingId) { + const setting = await Settings.findOneNotHiddenById(settingId); return this.convertToApp(setting); } diff --git a/app/apps/server/converters/uploads.js b/app/apps/server/converters/uploads.js index d95f5d10067f4..efbda7ae5fd1b 100644 --- a/app/apps/server/converters/uploads.js +++ b/app/apps/server/converters/uploads.js @@ -1,5 +1,5 @@ import { transformMappedData } from '../../lib/misc/transformMappedData'; -import Uploads from '../../../models/server/models/Uploads'; +import { Uploads } from '../../../models/server/raw'; export class AppUploadsConverter { constructor(orch) { @@ -7,7 +7,7 @@ export class AppUploadsConverter { } convertById(id) { - const upload = Uploads.findOneById(id); + const upload = Promise.await(Uploads.findOneById(id)); return this.convertToApp(upload); } diff --git a/app/apps/server/cron.js b/app/apps/server/cron.js index 3201612ea6b60..d38eebe060b29 100644 --- a/app/apps/server/cron.js +++ b/app/apps/server/cron.js @@ -6,8 +6,9 @@ import { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus'; import { Apps } from './orchestrator'; import { getWorkspaceAccessToken } from '../../cloud/server'; -import { Settings, Users } from '../../models/server'; +import { Users } from '../../models/server'; import { sendMessagesToAdmins } from '../../../server/lib/sendMessagesToAdmins'; +import { Settings } from '../../models/server/raw'; const notifyAdminsAboutInvalidApps = Meteor.bindEnvironment(function _notifyAdminsAboutInvalidApps(apps) { @@ -27,7 +28,7 @@ const notifyAdminsAboutInvalidApps = Meteor.bindEnvironment(function _notifyAdmi const rocketCatMessage = 'There is one or more apps in an invalid state. Go to Administration > Apps to review.'; const link = '/admin/apps'; - sendMessagesToAdmins({ + Promise.await(sendMessagesToAdmins({ msgs: ({ adminUser }) => ({ msg: `*${ TAPi18n.__(title, adminUser.language) }*\n${ TAPi18n.__(rocketCatMessage, adminUser.language) }` }), banners: ({ adminUser }) => { Users.removeBannerById(adminUser._id, { id }); @@ -41,7 +42,7 @@ const notifyAdminsAboutInvalidApps = Meteor.bindEnvironment(function _notifyAdmi link, }]; }, - }); + })); return apps; }); @@ -59,19 +60,19 @@ const notifyAdminsAboutRenewedApps = Meteor.bindEnvironment(function _notifyAdmi const rocketCatMessage = 'There is one or more disabled apps with valid licenses. Go to Administration > Apps to review.'; - sendMessagesToAdmins({ + Promise.await(sendMessagesToAdmins({ msgs: ({ adminUser }) => ({ msg: `${ TAPi18n.__(rocketCatMessage, adminUser.language) }` }), - }); + })); }); export const appsUpdateMarketplaceInfo = Meteor.bindEnvironment(function _appsUpdateMarketplaceInfo() { - const token = getWorkspaceAccessToken(); + const token = Promise.await(getWorkspaceAccessToken()); const baseUrl = Apps.getMarketplaceUrl(); - const [workspaceIdSetting] = Settings.findById('Cloud_Workspace_Id').fetch(); + const workspaceIdSetting = Promise.await(Settings.getValueById('Cloud_Workspace_Id')); const currentSeats = Users.getActiveLocalUserCount(); - const fullUrl = `${ baseUrl }/v1/workspaces/${ workspaceIdSetting.value }/apps?seats=${ currentSeats }`; + const fullUrl = `${ baseUrl }/v1/workspaces/${ workspaceIdSetting }/apps?seats=${ currentSeats }`; const options = { headers: { Authorization: `Bearer ${ token }`, diff --git a/app/authentication/server/lib/restrictLoginAttempts.ts b/app/authentication/server/lib/restrictLoginAttempts.ts index d3ca8f78e5ca7..6561f3d3f12c5 100644 --- a/app/authentication/server/lib/restrictLoginAttempts.ts +++ b/app/authentication/server/lib/restrictLoginAttempts.ts @@ -1,12 +1,10 @@ import moment from 'moment'; import { ILoginAttempt } from '../ILoginAttempt'; -import { ServerEvents, Users, Rooms } from '../../../models/server/raw'; -import { IServerEventType } from '../../../../definition/IServerEvent'; -import { IUser } from '../../../../definition/IUser'; +import { ServerEvents, Users, Rooms, Sessions } from '../../../models/server/raw'; +import { IServerEventType, IServerEvent } from '../../../../definition/IServerEvent'; import { settings } from '../../../settings/server'; import { addMinutesToADate } from '../../../../lib/utils/addMinutesToADate'; -import Sessions from '../../../models/server/raw/Sessions'; import { getClientAddress } from '../../../../server/lib/getClientAddress'; import { sendMessage } from '../../../lib/server/functions'; import { Logger } from '../../../logger/server'; @@ -52,7 +50,7 @@ export const isValidLoginAttemptByIp = async (ip: string): Promise => { return true; } - const lastLogin = await Sessions.findLastLoginByIp(ip) as {loginAt?: Date} | undefined; + const lastLogin = await Sessions.findLastLoginByIp(ip); let failedAttemptsSinceLastLogin; if (!lastLogin || !lastLogin.loginAt) { @@ -128,7 +126,7 @@ export const isValidAttemptByUser = async (login: ILoginAttempt): Promise => { - const user: Partial = { + const user: IServerEvent['u'] = { _id: login.user?._id, username: login.user?.username || login.methodArguments[0].user?.username, }; @@ -142,10 +140,15 @@ export const saveFailedLoginAttempts = async (login: ILoginAttempt): Promise => { + const user: IServerEvent['u'] = { + _id: login.user?._id, + username: login.user?.username || login.methodArguments[0].user?.username, + }; + await ServerEvents.insertOne({ ip: getClientAddress(login.connection), t: IServerEventType.LOGIN, ts: new Date(), - u: login.user, + u: user, }); }; diff --git a/app/authentication/server/startup/index.js b/app/authentication/server/startup/index.js index ff9e6b02f431c..ed98b62aaf282 100644 --- a/app/authentication/server/startup/index.js +++ b/app/authentication/server/startup/index.js @@ -8,8 +8,8 @@ import { escapeRegExp, escapeHTML } from '@rocket.chat/string-helpers'; import * as Mailer from '../../../mailer/server/api'; import { settings } from '../../../settings/server'; import { callbacks } from '../../../callbacks/server'; -import { Roles, Users, Settings } from '../../../models/server'; -import { Users as UsersRaw } from '../../../models/server/raw'; +import { Users, Settings } from '../../../models/server'; +import { Roles, Users as UsersRaw } from '../../../models/server/raw'; import { addUserRoles } from '../../../authorization/server'; import { getAvatarSuggestionForUser } from '../../../lib/server/functions'; import { @@ -186,8 +186,7 @@ Accounts.onCreateUser(function(options, user = {}) { if (!user.active) { const destinations = []; - - Roles.findUsersInRole('admin').forEach((adminUser) => { + Promise.await(Roles.findUsersInRole('admin').toArray()).forEach((adminUser) => { if (Array.isArray(adminUser.emails)) { adminUser.emails.forEach((email) => { destinations.push(`${ adminUser.name }<${ email.address }>`); diff --git a/app/authorization/client/hasPermission.ts b/app/authorization/client/hasPermission.ts index 744903bc50629..23a0aeeca8a61 100644 --- a/app/authorization/client/hasPermission.ts +++ b/app/authorization/client/hasPermission.ts @@ -7,11 +7,11 @@ import { IUser } from '../../../definition/IUser'; import { IRole } from '../../../definition/IRole'; import { IPermission } from '../../../definition/IPermission'; -const isValidScope = (scope: IRole['scope']): scope is keyof typeof Models => +const isValidScope = (scope: IRole['scope']): boolean => typeof scope === 'string' && scope in Models; const createPermissionValidator = (quantifier: (predicate: (permissionId: IPermission['_id']) => boolean) => boolean) => - (permissionIds: IPermission['_id'][], scope: IRole['scope'], userId: IUser['_id']): boolean => { + (permissionIds: IPermission['_id'][], scope: string | undefined, userId: IUser['_id']): boolean => { const user: IUser | null = Models.Users.findOneById(userId, { fields: { roles: 1 } }); const checkEachPermission = quantifier.bind(permissionIds); @@ -34,7 +34,7 @@ const createPermissionValidator = (quantifier: (predicate: (permissionId: IPermi return false; } - const model = Models[roleScope]; + const model = Models[roleScope as keyof typeof Models]; return model.isUserInRole && model.isUserInRole(userId, roleName, scope); }); }); @@ -46,8 +46,8 @@ const all = createPermissionValidator(Array.prototype.every); const validatePermissions = ( permissions: IPermission['_id'] | IPermission['_id'][], - scope: IRole['scope'], - predicate: (permissionIds: IPermission['_id'][], scope: IRole['scope'], userId: IUser['_id']) => boolean, + scope: string | undefined, + predicate: (permissionIds: IPermission['_id'][], scope: string | undefined, userId: IUser['_id']) => boolean, userId?: IUser['_id'] | null, ): boolean => { userId = userId ?? Meteor.userId(); @@ -65,17 +65,17 @@ const validatePermissions = ( export const hasAllPermission = ( permissions: IPermission['_id'] | IPermission['_id'][], - scope?: IRole['scope'], + scope?: string, ): boolean => validatePermissions(permissions, scope, all); export const hasAtLeastOnePermission = ( permissions: IPermission['_id'] | IPermission['_id'][], - scope?: IRole['scope'], + scope?: string, ): boolean => validatePermissions(permissions, scope, atLeastOne); export const userHasAllPermission = ( permissions: IPermission['_id'] | IPermission['_id'][], - scope?: IRole['scope'], + scope?: string, userId?: IUser['_id'] | null, ): boolean => validatePermissions(permissions, scope, all, userId); diff --git a/app/authorization/server/functions/addUserRoles.js b/app/authorization/server/functions/addUserRoles.ts similarity index 54% rename from app/authorization/server/functions/addUserRoles.js rename to app/authorization/server/functions/addUserRoles.ts index 46302e81eb8d7..dda983ff6a3cb 100644 --- a/app/authorization/server/functions/addUserRoles.js +++ b/app/authorization/server/functions/addUserRoles.ts @@ -2,9 +2,11 @@ import { Meteor } from 'meteor/meteor'; import _ from 'underscore'; import { getRoles } from './getRoles'; -import { Users, Roles } from '../../../models'; +import { Users } from '../../../models/server'; +import { IRole, IUser } from '../../../../definition/IUser'; +import { Roles } from '../../../models/server/raw'; -export const addUserRoles = (userId, roleNames, scope) => { +export const addUserRoles = (userId: IUser['_id'], roleNames: IRole['name'][], scope?: string): boolean => { if (!userId || !roleNames) { return false; } @@ -16,17 +18,19 @@ export const addUserRoles = (userId, roleNames, scope) => { }); } - roleNames = [].concat(roleNames); + if (!Array.isArray(roleNames)) { // TODO: remove this check + roleNames = [roleNames]; + } + const existingRoleNames = _.pluck(getRoles(), '_id'); const invalidRoleNames = _.difference(roleNames, existingRoleNames); if (!_.isEmpty(invalidRoleNames)) { for (const role of invalidRoleNames) { - Roles.createOrUpdate(role); + Promise.await(Roles.createOrUpdate(role)); } } - Roles.addUserRoles(userId, roleNames, scope); - + Promise.await(Roles.addUserRoles(userId, roleNames, scope)); return true; }; diff --git a/app/authorization/server/functions/getRoles.js b/app/authorization/server/functions/getRoles.js deleted file mode 100644 index 9d20c72d29a94..0000000000000 --- a/app/authorization/server/functions/getRoles.js +++ /dev/null @@ -1,3 +0,0 @@ -import { Roles } from '../../../models'; - -export const getRoles = () => Roles.find().fetch(); diff --git a/app/authorization/server/functions/getRoles.ts b/app/authorization/server/functions/getRoles.ts new file mode 100644 index 0000000000000..27de1000bb0a5 --- /dev/null +++ b/app/authorization/server/functions/getRoles.ts @@ -0,0 +1,4 @@ +import { IRole } from '../../../../definition/IUser'; +import { Roles } from '../../../models/server/raw'; + +export const getRoles = (): IRole[] => Promise.await(Roles.find().toArray()); diff --git a/app/authorization/server/functions/getUsersInRole.js b/app/authorization/server/functions/getUsersInRole.js deleted file mode 100644 index 27c369acf9ffd..0000000000000 --- a/app/authorization/server/functions/getUsersInRole.js +++ /dev/null @@ -1,3 +0,0 @@ -import { Roles } from '../../../models'; - -export const getUsersInRole = (roleName, scope, options) => Roles.findUsersInRole(roleName, scope, options); diff --git a/app/authorization/server/functions/getUsersInRole.ts b/app/authorization/server/functions/getUsersInRole.ts new file mode 100644 index 0000000000000..8a8bcadf3dba2 --- /dev/null +++ b/app/authorization/server/functions/getUsersInRole.ts @@ -0,0 +1,14 @@ + + +import { Cursor, FindOneOptions, WithoutProjection } from 'mongodb'; + +import { IRole, IUser } from '../../../../definition/IUser'; +import { Roles } from '../../../models/server/raw'; + +export function getUsersInRole(name: IRole['name'], scope?: string): Promise>; + +export function getUsersInRole(name: IRole['name'], scope: string | undefined, options: WithoutProjection>): Promise>; + +export function getUsersInRole

(name: IRole['name'], scope: string | undefined, options: FindOneOptions

): Promise>; + +export function getUsersInRole

(name: IRole['name'], scope: string | undefined, options?: any | undefined): Promise> { return Roles.findUsersInRole(name, scope, options); } diff --git a/app/authorization/server/functions/removeUserFromRoles.js b/app/authorization/server/functions/removeUserFromRoles.js index b08c2778addba..a55d722bb891b 100644 --- a/app/authorization/server/functions/removeUserFromRoles.js +++ b/app/authorization/server/functions/removeUserFromRoles.js @@ -2,7 +2,8 @@ import { Meteor } from 'meteor/meteor'; import _ from 'underscore'; import { getRoles } from './getRoles'; -import { Users, Roles } from '../../../models'; +import { Users } from '../../../models/server'; +import { Roles } from '../../../models/server/raw'; export const removeUserFromRoles = (userId, roleNames, scope) => { if (!userId || !roleNames) { @@ -27,7 +28,7 @@ export const removeUserFromRoles = (userId, roleNames, scope) => { }); } - Roles.removeUserRoles(userId, roleNames, scope); + Promise.await(Roles.removeUserRoles(userId, roleNames, scope)); return true; }; diff --git a/app/authorization/server/functions/upsertPermissions.js b/app/authorization/server/functions/upsertPermissions.ts similarity index 88% rename from app/authorization/server/functions/upsertPermissions.js rename to app/authorization/server/functions/upsertPermissions.ts index a2e78302ad402..ad4c06c224717 100644 --- a/app/authorization/server/functions/upsertPermissions.js +++ b/app/authorization/server/functions/upsertPermissions.ts @@ -1,11 +1,11 @@ /* eslint no-multi-spaces: 0 */ -import Roles from '../../../models/server/models/Roles'; -import Permissions from '../../../models/server/models/Permissions'; -import Settings from '../../../models/server/models/Settings'; import { settings } from '../../../settings/server'; import { getSettingPermissionId, CONSTANTS } from '../../lib'; +import { Permissions, Roles, Settings } from '../../../models/server/raw'; +import { IPermission } from '../../../../definition/IPermission'; +import { ISetting } from '../../../../definition/ISetting'; -export const upsertPermissions = () => { +export const upsertPermissions = async (): Promise => { // Note: // 1.if we need to create a role that can only edit channel message, but not edit group message // then we can define edit--message instead of edit-message @@ -153,8 +153,8 @@ export const upsertPermissions = () => { ]; - for (const permission of permissions) { - Permissions.create(permission._id, permission.roles); + for await (const permission of permissions) { + await Permissions.create(permission._id, permission.roles); } const defaultRoles = [ @@ -171,29 +171,30 @@ export const upsertPermissions = () => { { name: 'livechat-manager', scope: 'Users', description: 'Livechat Manager' }, ]; - for (const role of defaultRoles) { - Roles.createOrUpdate(role.name, role.scope, role.description, true, false); + for await (const role of defaultRoles) { + await Roles.createOrUpdate(role.name, role.scope as 'Users' | 'Subscriptions', role.description, true, false); } - const getPreviousPermissions = function(settingId) { - const previousSettingPermissions = {}; + const getPreviousPermissions = async function(settingId?: string): Promise> { + const previousSettingPermissions: { + [key: string]: IPermission; + } = {}; - const selector = { level: CONSTANTS.SETTINGS_LEVEL }; - if (settingId) { - selector.settingId = settingId; - } + const selector = { level: 'settings' as const, ...settingId && { settingId } }; - Permissions.find(selector).forEach( - function(permission) { + await Permissions.find(selector).forEach( + function(permission: IPermission) { previousSettingPermissions[permission._id] = permission; }); return previousSettingPermissions; }; - const createSettingPermission = function(setting, previousSettingPermissions) { + const createSettingPermission = async function(setting: ISetting, previousSettingPermissions: { + [key: string]: IPermission; + }): Promise { const permissionId = getSettingPermissionId(setting._id); - const permission = { - level: CONSTANTS.SETTINGS_LEVEL, + const permission: Omit = { + level: CONSTANTS.SETTINGS_LEVEL as 'settings' | undefined, // copy those setting-properties which are needed to properly publish the setting-based permissions settingId: setting._id, group: setting.group, @@ -212,19 +213,19 @@ export const upsertPermissions = () => { permission.sectionPermissionId = getSettingPermissionId(setting.section); } - const existent = Permissions.findOne({ + const existent = await Permissions.findOne({ _id: permissionId, ...permission, }, { fields: { _id: 1 } }); if (!existent) { try { - Permissions.upsert({ _id: permissionId }, { $set: permission }); + await Permissions.update({ _id: permissionId }, { $set: permission }, { upsert: true }); } catch (e) { if (!e.message.includes('E11000')) { // E11000 refers to a MongoDB error that can occur when using unique indexes for upserts // https://docs.mongodb.com/manual/reference/method/db.collection.update/#use-unique-indexes - Permissions.upsert({ _id: permissionId }, { $set: permission }); + await Permissions.update({ _id: permissionId }, { $set: permission }, { upsert: true }); } } } @@ -232,17 +233,17 @@ export const upsertPermissions = () => { delete previousSettingPermissions[permissionId]; }; - const createPermissionsForExistingSettings = function() { - const previousSettingPermissions = getPreviousPermissions(); + const createPermissionsForExistingSettings = async function(): Promise { + const previousSettingPermissions = await getPreviousPermissions(); - Settings.findNotHidden().fetch().forEach((setting) => { + (await Settings.findNotHidden().toArray()).forEach((setting) => { createSettingPermission(setting, previousSettingPermissions); }); // remove permissions for non-existent settings - for (const obsoletePermission in previousSettingPermissions) { + for await (const obsoletePermission of Object.keys(previousSettingPermissions)) { if (previousSettingPermissions.hasOwnProperty(obsoletePermission)) { - Permissions.remove({ _id: obsoletePermission }); + await Permissions.deleteOne({ _id: obsoletePermission }); } } }; @@ -251,9 +252,9 @@ export const upsertPermissions = () => { createPermissionsForExistingSettings(); // register a callback for settings for be create in higher-level-packages - settings.on('*', function([settingId]) { - const previousSettingPermissions = getPreviousPermissions(settingId); - const setting = Settings.findOneById(settingId); + settings.on('*', async function([settingId]) { + const previousSettingPermissions = await getPreviousPermissions(settingId); + const setting = await Settings.findOneById(settingId); if (setting) { if (!setting.hidden) { createSettingPermission(setting, previousSettingPermissions); diff --git a/app/authorization/server/methods/addPermissionToRole.js b/app/authorization/server/methods/addPermissionToRole.ts similarity index 72% rename from app/authorization/server/methods/addPermissionToRole.js rename to app/authorization/server/methods/addPermissionToRole.ts index 5ca74ed3dbc9b..42990b114437c 100644 --- a/app/authorization/server/methods/addPermissionToRole.js +++ b/app/authorization/server/methods/addPermissionToRole.ts @@ -1,11 +1,12 @@ import { Meteor } from 'meteor/meteor'; -import { Permissions } from '../../../models/server'; + import { hasPermission } from '../functions/hasPermission'; import { CONSTANTS, AuthorizationUtils } from '../../lib'; +import { Permissions } from '../../../models/server/raw'; Meteor.methods({ - 'authorization:addPermissionToRole'(permissionId, role) { + async 'authorization:addPermissionToRole'(permissionId, role) { if (AuthorizationUtils.isPermissionRestrictedForRole(permissionId, role)) { throw new Meteor.Error('error-action-not-allowed', 'Permission is restricted', { method: 'authorization:addPermissionToRole', @@ -14,7 +15,14 @@ Meteor.methods({ } const uid = Meteor.userId(); - const permission = Permissions.findOneById(permissionId); + const permission = await Permissions.findOneById(permissionId); + + if (!permission) { + throw new Meteor.Error('error-invalid-permission', 'Permission does not exist', { + method: 'authorization:addPermissionToRole', + action: 'Adding_permission', + }); + } if (!uid || !hasPermission(uid, 'access-permissions') || (permission.level === CONSTANTS.SETTINGS_LEVEL && !hasPermission(uid, 'access-setting-permissions'))) { throw new Meteor.Error('error-action-not-allowed', 'Adding permission is not allowed', { diff --git a/app/authorization/server/methods/addUserToRole.js b/app/authorization/server/methods/addUserToRole.ts similarity index 84% rename from app/authorization/server/methods/addUserToRole.js rename to app/authorization/server/methods/addUserToRole.ts index a7fdd21ec24dc..3182d327ff476 100644 --- a/app/authorization/server/methods/addUserToRole.js +++ b/app/authorization/server/methods/addUserToRole.ts @@ -1,13 +1,14 @@ import { Meteor } from 'meteor/meteor'; import _ from 'underscore'; -import { Users, Roles } from '../../../models/server'; +import { Users } from '../../../models/server'; import { settings } from '../../../settings/server'; import { hasPermission } from '../functions/hasPermission'; import { api } from '../../../../server/sdk/api'; +import { Roles } from '../../../models/server/raw'; Meteor.methods({ - 'authorization:addUserToRole'(roleName, username, scope) { + async 'authorization:addUserToRole'(roleName, username, scope) { if (!Meteor.userId() || !hasPermission(Meteor.userId(), 'access-permissions')) { throw new Meteor.Error('error-action-not-allowed', 'Accessing permissions is not allowed', { method: 'authorization:addUserToRole', @@ -41,13 +42,13 @@ Meteor.methods({ } // verify if user can be added to given scope - if (scope && !Roles.canAddUserToRole(user._id, roleName, scope)) { + if (scope && !await Roles.canAddUserToRole(user._id, roleName, scope)) { throw new Meteor.Error('error-invalid-user', 'User is not part of given room', { method: 'authorization:addUserToRole', }); } - const add = Roles.addUserRoles(user._id, roleName, scope); + const add = await Roles.addUserRoles(user._id, [roleName], scope); if (settings.get('UI_DisplayRoles')) { api.broadcast('user.roleUpdate', { diff --git a/app/authorization/server/methods/deleteRole.js b/app/authorization/server/methods/deleteRole.ts similarity index 67% rename from app/authorization/server/methods/deleteRole.js rename to app/authorization/server/methods/deleteRole.ts index 8613e1761b0a5..8925942b23f3d 100644 --- a/app/authorization/server/methods/deleteRole.js +++ b/app/authorization/server/methods/deleteRole.ts @@ -1,10 +1,10 @@ import { Meteor } from 'meteor/meteor'; -import * as Models from '../../../models/server'; +import { Roles } from '../../../models/server/raw'; import { hasPermission } from '../functions/hasPermission'; Meteor.methods({ - 'authorization:deleteRole'(roleName) { + async 'authorization:deleteRole'(roleName) { if (!Meteor.userId() || !hasPermission(Meteor.userId(), 'access-permissions')) { throw new Meteor.Error('error-action-not-allowed', 'Accessing permissions is not allowed', { method: 'authorization:deleteRole', @@ -12,7 +12,7 @@ Meteor.methods({ }); } - const role = Models.Roles.findOne(roleName); + const role = await Roles.findOne(roleName); if (!role) { throw new Meteor.Error('error-invalid-role', 'Invalid role', { method: 'authorization:deleteRole', @@ -25,16 +25,14 @@ Meteor.methods({ }); } - const roleScope = role.scope || 'Users'; - const model = Models[roleScope]; - const existingUsers = model && model.findUsersInRoles && model.findUsersInRoles(roleName); + const users = await(await Roles.findUsersInRole(roleName)).count(); - if (existingUsers && existingUsers.count() > 0) { + if (users > 0) { throw new Meteor.Error('error-role-in-use', 'Cannot delete role because it\'s in use', { method: 'authorization:deleteRole', }); } - return Models.Roles.remove(role.name); + return Roles.removeById(role.name); }, }); diff --git a/app/authorization/server/methods/removeRoleFromPermission.js b/app/authorization/server/methods/removeRoleFromPermission.ts similarity index 70% rename from app/authorization/server/methods/removeRoleFromPermission.js rename to app/authorization/server/methods/removeRoleFromPermission.ts index e0aa20ed34dbb..c31592a0ceca6 100644 --- a/app/authorization/server/methods/removeRoleFromPermission.js +++ b/app/authorization/server/methods/removeRoleFromPermission.ts @@ -1,13 +1,18 @@ import { Meteor } from 'meteor/meteor'; -import { Permissions } from '../../../models/server'; import { hasPermission } from '../functions/hasPermission'; import { CONSTANTS } from '../../lib'; +import { Permissions } from '../../../models/server/raw'; Meteor.methods({ - 'authorization:removeRoleFromPermission'(permissionId, role) { + async 'authorization:removeRoleFromPermission'(permissionId, role) { const uid = Meteor.userId(); - const permission = Permissions.findOneById(permissionId); + const permission = await Permissions.findOneById(permissionId); + + + if (!permission) { + throw new Meteor.Error('error-permission-not-found', 'Permission not found', { method: 'authorization:removeRoleFromPermission' }); + } if (!uid || !hasPermission(uid, 'access-permissions') || (permission.level === CONSTANTS.SETTINGS_LEVEL && !hasPermission(uid, 'access-setting-permissions'))) { throw new Meteor.Error('error-action-not-allowed', 'Removing permission is not allowed', { diff --git a/app/authorization/server/methods/removeUserFromRole.js b/app/authorization/server/methods/removeUserFromRole.js index 9a36a8895870c..d98ff825af9b8 100644 --- a/app/authorization/server/methods/removeUserFromRole.js +++ b/app/authorization/server/methods/removeUserFromRole.js @@ -1,13 +1,13 @@ import { Meteor } from 'meteor/meteor'; import _ from 'underscore'; -import { Roles } from '../../../models/server'; import { settings } from '../../../settings/server'; import { hasPermission } from '../functions/hasPermission'; import { api } from '../../../../server/sdk/api'; +import { Roles } from '../../../models/server/raw'; Meteor.methods({ - 'authorization:removeUserFromRole'(roleName, username, scope) { + async 'authorization:removeUserFromRole'(roleName, username, scope) { if (!Meteor.userId() || !hasPermission(Meteor.userId(), 'access-permissions')) { throw new Meteor.Error('error-action-not-allowed', 'Access permissions is not allowed', { method: 'authorization:removeUserFromRole', @@ -44,7 +44,7 @@ Meteor.methods({ }, }).count(); - const userIsAdmin = user.roles.indexOf('admin') > -1; + const userIsAdmin = user.roles?.indexOf('admin') > -1; if (adminCount === 1 && userIsAdmin) { throw new Meteor.Error('error-action-not-allowed', 'Leaving the app without admins is not allowed', { method: 'removeUserFromRole', @@ -53,7 +53,7 @@ Meteor.methods({ } } - const remove = Roles.removeUserRoles(user._id, roleName, scope); + const remove = await Roles.removeUserRoles(user._id, [roleName], scope); if (settings.get('UI_DisplayRoles')) { api.broadcast('user.roleUpdate', { type: 'removed', diff --git a/app/authorization/server/methods/saveRole.js b/app/authorization/server/methods/saveRole.ts similarity index 80% rename from app/authorization/server/methods/saveRole.js rename to app/authorization/server/methods/saveRole.ts index 5e09f211240d7..04f431ba9906e 100644 --- a/app/authorization/server/methods/saveRole.js +++ b/app/authorization/server/methods/saveRole.ts @@ -1,12 +1,12 @@ import { Meteor } from 'meteor/meteor'; -import { Roles } from '../../../models/server'; import { settings } from '../../../settings/server'; import { hasPermission } from '../functions/hasPermission'; import { api } from '../../../../server/sdk/api'; +import { Roles } from '../../../models/server/raw'; Meteor.methods({ - 'authorization:saveRole'(roleData) { + async 'authorization:saveRole'(roleData) { if (!Meteor.userId() || !hasPermission(Meteor.userId(), 'access-permissions')) { throw new Meteor.Error('error-action-not-allowed', 'Accessing permissions is not allowed', { method: 'authorization:saveRole', @@ -24,7 +24,7 @@ Meteor.methods({ roleData.scope = 'Users'; } - const update = Roles.createOrUpdate(roleData.name, roleData.scope, roleData.description, false, roleData.mandatory2fa); + const update = await Roles.createOrUpdate(roleData.name, roleData.scope, roleData.description, false, roleData.mandatory2fa); if (settings.get('UI_DisplayRoles')) { api.broadcast('user.roleUpdate', { type: 'changed', diff --git a/app/authorization/server/streamer/permissions/index.js b/app/authorization/server/streamer/permissions/index.js deleted file mode 100644 index edffbdfe3e734..0000000000000 --- a/app/authorization/server/streamer/permissions/index.js +++ /dev/null @@ -1,25 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import Permissions from '../../../../models/server/models/Permissions'; - -Meteor.methods({ - 'permissions/get'(updatedAt) { - // TODO: should we return this for non logged users? - // TODO: we could cache this collection - - const records = Permissions.find().fetch(); - - if (updatedAt instanceof Date) { - return { - update: records.filter((record) => record._updatedAt > updatedAt), - remove: Permissions.trashFindDeletedAfter( - updatedAt, - {}, - { fields: { _id: 1, _deletedAt: 1 } }, - ).fetch(), - }; - } - - return records; - }, -}); diff --git a/app/authorization/server/streamer/permissions/index.ts b/app/authorization/server/streamer/permissions/index.ts new file mode 100644 index 0000000000000..5494f8f1f78ec --- /dev/null +++ b/app/authorization/server/streamer/permissions/index.ts @@ -0,0 +1,28 @@ +import { Meteor } from 'meteor/meteor'; +import { check, Match } from 'meteor/check'; + +import { Permissions } from '../../../../models/server/raw'; + +Meteor.methods({ + async 'permissions/get'(updatedAt: Date) { + check(updatedAt, Match.Maybe(Date)); + + // TODO: should we return this for non logged users? + // TODO: we could cache this collection + + const records = await Permissions.find(updatedAt && { _updatedAt: { $gt: updatedAt } }).toArray(); + + if (updatedAt instanceof Date) { + return { + update: records, + remove: await Permissions.trashFindDeletedAfter( + updatedAt, + {}, + { fields: { _id: 1, _deletedAt: 1 } }, + ).toArray(), + }; + } + + return records; + }, +}); diff --git a/app/autotranslate/server/permissions.js b/app/autotranslate/server/permissions.js deleted file mode 100644 index 64ce0028fa872..0000000000000 --- a/app/autotranslate/server/permissions.js +++ /dev/null @@ -1,11 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { Permissions } from '../../models'; - -Meteor.startup(() => { - if (Permissions) { - if (!Permissions.findOne({ _id: 'auto-translate' })) { - Permissions.insert({ _id: 'auto-translate', roles: ['admin'] }); - } - } -}); diff --git a/app/autotranslate/server/permissions.ts b/app/autotranslate/server/permissions.ts new file mode 100644 index 0000000000000..5ce05e8f1ef72 --- /dev/null +++ b/app/autotranslate/server/permissions.ts @@ -0,0 +1,9 @@ +import { Meteor } from 'meteor/meteor'; + +import { Permissions } from '../../models/server/raw'; + +Meteor.startup(async () => { + if (!await Permissions.findOne({ _id: 'auto-translate' })) { + Permissions.create('auto-translate', ['admin']); + } +}); diff --git a/app/cas/server/cas_server.js b/app/cas/server/cas_server.js index 646e87a8f0539..cc569eeab4419 100644 --- a/app/cas/server/cas_server.js +++ b/app/cas/server/cas_server.js @@ -10,7 +10,8 @@ import CAS from 'cas'; import { logger } from './cas_rocketchat'; import { settings } from '../../settings'; -import { Rooms, CredentialTokens } from '../../models/server'; +import { Rooms } from '../../models/server'; +import { CredentialTokens } from '../../models/server/raw'; import { _setRealName } from '../../lib'; import { createRoom } from '../../lib/server/functions/createRoom'; @@ -43,7 +44,7 @@ const casTicket = function(req, token, callback) { service: `${ appUrl }/_cas/${ token }`, }); - cas.validate(ticketId, Meteor.bindEnvironment(function(err, status, username, details) { + cas.validate(ticketId, Meteor.bindEnvironment(async function(err, status, username, details) { if (err) { logger.error(`error when trying to validate: ${ err.message }`); } else if (status) { @@ -54,11 +55,11 @@ const casTicket = function(req, token, callback) { if (details && details.attributes) { _.extend(user_info, { attributes: details.attributes }); } - CredentialTokens.create(token, user_info); + await CredentialTokens.create(token, user_info); } else { logger.error(`Unable to validate ticket: ${ ticketId }`); } - // logger.debug("Receveied response: " + JSON.stringify(details, null , 4)); + // logger.debug("Received response: " + JSON.stringify(details, null , 4)); callback(); })); @@ -114,7 +115,8 @@ Accounts.registerLoginHandler(function(options) { return undefined; } - const credentials = CredentialTokens.findOneById(options.cas.credentialToken); + // TODO: Sync wrapper due to the chain conversion to async models + const credentials = Promise.await(CredentialTokens.findOneNotExpiredById(options.cas.credentialToken)); if (credentials === undefined) { throw new Meteor.Error(Accounts.LoginCancelledError.numericError, 'no matching login attempt found'); diff --git a/app/channel-settings/server/functions/saveRoomName.js b/app/channel-settings/server/functions/saveRoomName.js index 5d3197d133b35..0cc31cfa77b44 100644 --- a/app/channel-settings/server/functions/saveRoomName.js +++ b/app/channel-settings/server/functions/saveRoomName.js @@ -1,6 +1,7 @@ import { Meteor } from 'meteor/meteor'; -import { Rooms, Messages, Subscriptions, Integrations } from '../../../models/server'; +import { Rooms, Messages, Subscriptions } from '../../../models/server'; +import { Integrations } from '../../../models/server/raw'; import { roomTypes, getValidRoomName } from '../../../utils/server'; import { callbacks } from '../../../callbacks/server'; import { checkUsernameAvailability } from '../../../lib/server/functions'; @@ -19,7 +20,7 @@ const updateRoomName = (rid, displayName, isDiscussion) => { return Rooms.setNameById(rid, slugifiedRoomName, displayName) && Subscriptions.updateNameAndAlertByRoomId(rid, slugifiedRoomName, displayName); }; -export const saveRoomName = function(rid, displayName, user, sendMessage = true) { +export async function saveRoomName(rid, displayName, user, sendMessage = true) { const room = Rooms.findOneById(rid); if (roomTypes.getConfig(room.t).preventRenaming()) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { @@ -35,10 +36,10 @@ export const saveRoomName = function(rid, displayName, user, sendMessage = true) return; } - Integrations.updateRoomName(room.name, displayName); + await Integrations.updateRoomName(room.name, displayName); if (sendMessage) { Messages.createRoomRenamedWithRoomIdRoomNameAndUser(rid, displayName, user); } callbacks.run('afterRoomNameChange', { rid, name: displayName, oldName: room.name }); return displayName; -}; +} diff --git a/app/channel-settings/server/methods/saveRoomSettings.js b/app/channel-settings/server/methods/saveRoomSettings.js index 811c492fb70d4..59c0bb239f791 100644 --- a/app/channel-settings/server/methods/saveRoomSettings.js +++ b/app/channel-settings/server/methods/saveRoomSettings.js @@ -128,7 +128,7 @@ const validators = { const settingSavers = { roomName({ value, rid, user, room }) { - if (!saveRoomName(rid, value, user)) { + if (!Promise.await(saveRoomName(rid, value, user))) { return; } @@ -231,13 +231,13 @@ const settingSavers = { favorite({ value, rid }) { Rooms.saveFavoriteById(rid, value.favorite, value.defaultValue); }, - roomAvatar({ value, rid, user }) { - setRoomAvatar(rid, value, user); + async roomAvatar({ value, rid, user }) { + await setRoomAvatar(rid, value, user); }, }; Meteor.methods({ - saveRoomSettings(rid, settings, value) { + async saveRoomSettings(rid, settings, value) { const userId = Meteor.userId(); if (!userId) { @@ -313,10 +313,10 @@ Meteor.methods({ }); // saving data - Object.keys(settings).forEach((setting) => { + for await (const setting of Object.keys(settings)) { const value = settings[setting]; - const saver = settingSavers[setting]; + const saver = await settingSavers[setting]; if (saver) { saver({ value, @@ -325,7 +325,7 @@ Meteor.methods({ user, }); } - }); + } Meteor.defer(function() { const room = Rooms.findOneById(rid); diff --git a/app/cloud/server/functions/buildRegistrationData.js b/app/cloud/server/functions/buildRegistrationData.js index d8ecff67687f8..5346558e23ab6 100644 --- a/app/cloud/server/functions/buildRegistrationData.js +++ b/app/cloud/server/functions/buildRegistrationData.js @@ -1,10 +1,11 @@ import { settings } from '../../../settings/server'; -import { Users, Statistics } from '../../../models/server'; +import { Users } from '../../../models/server'; +import { Statistics } from '../../../models/server/raw'; import { statistics } from '../../../statistics'; import { LICENSE_VERSION } from '../license'; -export function buildWorkspaceRegistrationData() { - const stats = Statistics.findLast() || statistics.get(); +export async function buildWorkspaceRegistrationData() { + const stats = await Statistics.findLast() || statistics.get(); const address = settings.get('Site_Url'); const siteName = settings.get('Site_Name'); diff --git a/app/cloud/server/functions/startRegisterWorkspace.js b/app/cloud/server/functions/startRegisterWorkspace.js index bb533c79c5809..2f9e4f90b7894 100644 --- a/app/cloud/server/functions/startRegisterWorkspace.js +++ b/app/cloud/server/functions/startRegisterWorkspace.js @@ -7,18 +7,17 @@ import { Settings } from '../../../models'; import { buildWorkspaceRegistrationData } from './buildRegistrationData'; import { SystemLogger } from '../../../../server/lib/logger/system'; - -export function startRegisterWorkspace(resend = false) { +export async function startRegisterWorkspace(resend = false) { const { workspaceRegistered, connectToCloud } = retrieveRegistrationStatus(); if ((workspaceRegistered && connectToCloud) || process.env.TEST_MODE) { - syncWorkspace(true); + await syncWorkspace(true); return true; } Settings.updateValueById('Register_Server', true); - const regInfo = buildWorkspaceRegistrationData(); + const regInfo = await buildWorkspaceRegistrationData(); const cloudUrl = settings.get('Cloud_Url'); diff --git a/app/cloud/server/functions/syncWorkspace.js b/app/cloud/server/functions/syncWorkspace.js index 03f67acf4a4b9..1b1021402e25e 100644 --- a/app/cloud/server/functions/syncWorkspace.js +++ b/app/cloud/server/functions/syncWorkspace.js @@ -10,13 +10,13 @@ import { getAndCreateNpsSurvey } from '../../../../server/services/nps/getAndCre import { NPS, Banner } from '../../../../server/sdk'; import { SystemLogger } from '../../../../server/lib/logger/system'; -export function syncWorkspace(reconnectCheck = false) { +export async function syncWorkspace(reconnectCheck = false) { const { workspaceRegistered, connectToCloud } = retrieveRegistrationStatus(); if (!workspaceRegistered || (!connectToCloud && !reconnectCheck)) { return false; } - const info = buildWorkspaceRegistrationData(); + const info = await buildWorkspaceRegistrationData(); const workspaceUrl = settings.get('Cloud_Workspace_Registration_Client_Uri'); @@ -64,11 +64,11 @@ export function syncWorkspace(reconnectCheck = false) { const startAt = new Date(data.nps.startAt); - Promise.await(NPS.create({ + await NPS.create({ npsId, startAt, expireAt: new Date(expireAt), - })); + }); const now = new Date(); @@ -79,19 +79,19 @@ export function syncWorkspace(reconnectCheck = false) { // add banners if (data.banners) { - for (const banner of data.banners) { + for await (const banner of data.banners) { const { createdAt, expireAt, startAt, } = banner; - Promise.await(Banner.create({ + await Banner.create({ ...banner, createdAt: new Date(createdAt), expireAt: new Date(expireAt), startAt: new Date(startAt), - })); + }); } } diff --git a/app/cloud/server/methods.js b/app/cloud/server/methods.js index 7723566601f5a..83847711a603b 100644 --- a/app/cloud/server/methods.js +++ b/app/cloud/server/methods.js @@ -26,7 +26,7 @@ Meteor.methods({ return retrieveRegistrationStatus(); }, - 'cloud:getWorkspaceRegisterData'() { + async 'cloud:getWorkspaceRegisterData'() { if (!Meteor.userId()) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'cloud:getWorkspaceRegisterData' }); } @@ -35,9 +35,9 @@ Meteor.methods({ throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'cloud:getWorkspaceRegisterData' }); } - return Buffer.from(JSON.stringify(buildWorkspaceRegistrationData())).toString('base64'); + return Buffer.from(JSON.stringify(await buildWorkspaceRegistrationData())).toString('base64'); }, - 'cloud:registerWorkspace'() { + async 'cloud:registerWorkspace'() { if (!Meteor.userId()) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'cloud:startRegister' }); } @@ -48,7 +48,7 @@ Meteor.methods({ return startRegisterWorkspace(); }, - 'cloud:syncWorkspace'() { + async 'cloud:syncWorkspace'() { if (!Meteor.userId()) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'cloud:syncWorkspace' }); } diff --git a/app/crowd/server/crowd.js b/app/crowd/server/crowd.js index d5b4b5b97ef36..dba130ef489e3 100644 --- a/app/crowd/server/crowd.js +++ b/app/crowd/server/crowd.js @@ -208,7 +208,7 @@ export class CROWD { if (settings.get('CROWD_Remove_Orphaned_Users') === true) { logger.info('Removing user:', crowd_username); Meteor.defer(function() { - deleteUser(user._id); + Promise.await(deleteUser(user._id)); logger.info('User removed:', crowd_username); }); } diff --git a/app/custom-sounds/server/methods/deleteCustomSound.js b/app/custom-sounds/server/methods/deleteCustomSound.js index b72c852bacfc6..d168fac12d1d4 100644 --- a/app/custom-sounds/server/methods/deleteCustomSound.js +++ b/app/custom-sounds/server/methods/deleteCustomSound.js @@ -1,16 +1,16 @@ import { Meteor } from 'meteor/meteor'; -import { CustomSounds } from '../../../models'; +import { CustomSounds } from '../../../models/server/raw'; import { hasPermission } from '../../../authorization'; import { Notifications } from '../../../notifications'; import { RocketChatFileCustomSoundsInstance } from '../startup/custom-sounds'; Meteor.methods({ - deleteCustomSound(_id) { + async deleteCustomSound(_id) { let sound = null; if (hasPermission(this.userId, 'manage-sounds')) { - sound = CustomSounds.findOneById(_id); + sound = await CustomSounds.findOneById(_id); } else { throw new Meteor.Error('not_authorized'); } @@ -20,7 +20,7 @@ Meteor.methods({ } RocketChatFileCustomSoundsInstance.deleteFile(`${ sound._id }.${ sound.extension }`); - CustomSounds.removeById(_id); + await CustomSounds.removeById(_id); Notifications.notifyAll('deleteCustomSound', { soundData: sound }); return true; diff --git a/app/custom-sounds/server/methods/insertOrUpdateSound.js b/app/custom-sounds/server/methods/insertOrUpdateSound.js index d3fe25e0173b0..b1fa7c749747c 100644 --- a/app/custom-sounds/server/methods/insertOrUpdateSound.js +++ b/app/custom-sounds/server/methods/insertOrUpdateSound.js @@ -3,12 +3,12 @@ import s from 'underscore.string'; import { check } from 'meteor/check'; import { hasPermission } from '../../../authorization'; -import { CustomSounds } from '../../../models'; +import { CustomSounds } from '../../../models/server/raw'; import { Notifications } from '../../../notifications'; import { RocketChatFileCustomSoundsInstance } from '../startup/custom-sounds'; Meteor.methods({ - insertOrUpdateSound(soundData) { + async insertOrUpdateSound(soundData) { if (!hasPermission(this.userId, 'manage-sounds')) { throw new Meteor.Error('not_authorized'); } @@ -34,9 +34,9 @@ Meteor.methods({ if (soundData._id) { check(soundData._id, String); - matchingResults = CustomSounds.findByNameExceptId(soundData.name, soundData._id).fetch(); + matchingResults = await CustomSounds.findByNameExceptId(soundData.name, soundData._id).toArray(); } else { - matchingResults = CustomSounds.findByName(soundData.name).fetch(); + matchingResults = await CustomSounds.findByName(soundData.name).toArray(); } if (matchingResults.length > 0) { @@ -50,7 +50,7 @@ Meteor.methods({ extension: soundData.extension, }; - const _id = CustomSounds.create(createSound); + const _id = await (await CustomSounds.create(createSound)).insertedId; createSound._id = _id; return _id; @@ -61,7 +61,7 @@ Meteor.methods({ } if (soundData.name !== soundData.previousName) { - CustomSounds.setName(soundData._id, soundData.name); + await CustomSounds.setName(soundData._id, soundData.name); Notifications.notifyAll('updateCustomSound', { soundData }); } diff --git a/app/custom-sounds/server/methods/listCustomSounds.js b/app/custom-sounds/server/methods/listCustomSounds.js index 90bf6db20435a..475da52286be1 100644 --- a/app/custom-sounds/server/methods/listCustomSounds.js +++ b/app/custom-sounds/server/methods/listCustomSounds.js @@ -1,9 +1,9 @@ import { Meteor } from 'meteor/meteor'; -import { CustomSounds } from '../../../models'; +import { CustomSounds } from '../../../models/server/raw'; Meteor.methods({ - listCustomSounds() { - return CustomSounds.find({}).fetch(); + async listCustomSounds() { + return CustomSounds.find({}).toArray(); }, }); diff --git a/app/discussion/server/permissions.js b/app/discussion/server/permissions.ts similarity index 87% rename from app/discussion/server/permissions.js rename to app/discussion/server/permissions.ts index 3d54e4c66b16b..da3ac2ee2290a 100644 --- a/app/discussion/server/permissions.js +++ b/app/discussion/server/permissions.ts @@ -1,6 +1,7 @@ import { Meteor } from 'meteor/meteor'; -import { Permissions } from '../../models'; +import { Permissions } from '../../models/server/raw'; + Meteor.startup(() => { // Add permissions for discussion diff --git a/app/emoji-custom/server/methods/deleteEmojiCustom.js b/app/emoji-custom/server/methods/deleteEmojiCustom.js index 7393f245b459e..2964c5ff6cd66 100644 --- a/app/emoji-custom/server/methods/deleteEmojiCustom.js +++ b/app/emoji-custom/server/methods/deleteEmojiCustom.js @@ -2,22 +2,22 @@ import { Meteor } from 'meteor/meteor'; import { api } from '../../../../server/sdk/api'; import { hasPermission } from '../../../authorization'; -import { EmojiCustom } from '../../../models'; +import { EmojiCustom } from '../../../models/server/raw'; import { RocketChatFileEmojiCustomInstance } from '../startup/emoji-custom'; Meteor.methods({ - deleteEmojiCustom(emojiID) { + async deleteEmojiCustom(emojiID) { if (!hasPermission(this.userId, 'manage-emoji')) { throw new Meteor.Error('not_authorized'); } - const emoji = EmojiCustom.findOneById(emojiID); + const emoji = await EmojiCustom.findOneById(emojiID); if (emoji == null) { throw new Meteor.Error('Custom_Emoji_Error_Invalid_Emoji', 'Invalid emoji', { method: 'deleteEmojiCustom' }); } RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${ emoji.name }.${ emoji.extension }`)); - EmojiCustom.removeById(emojiID); + await EmojiCustom.removeById(emojiID); api.broadcast('emoji.deleteCustom', emoji); return true; diff --git a/app/emoji-custom/server/methods/insertOrUpdateEmoji.js b/app/emoji-custom/server/methods/insertOrUpdateEmoji.js index b96b40b2fbd06..23843c81cec95 100644 --- a/app/emoji-custom/server/methods/insertOrUpdateEmoji.js +++ b/app/emoji-custom/server/methods/insertOrUpdateEmoji.js @@ -4,12 +4,12 @@ import s from 'underscore.string'; import limax from 'limax'; import { hasPermission } from '../../../authorization'; -import { EmojiCustom } from '../../../models'; +import { EmojiCustom } from '../../../models/server/raw'; import { RocketChatFileEmojiCustomInstance } from '../startup/emoji-custom'; import { api } from '../../../../server/sdk/api'; Meteor.methods({ - insertOrUpdateEmoji(emojiData) { + async insertOrUpdateEmoji(emojiData) { if (!hasPermission(this.userId, 'manage-emoji')) { throw new Meteor.Error('not_authorized'); } @@ -50,14 +50,14 @@ Meteor.methods({ let matchingResults = []; if (emojiData._id) { - matchingResults = EmojiCustom.findByNameOrAliasExceptID(emojiData.name, emojiData._id).fetch(); - for (const alias of emojiData.aliases) { - matchingResults = matchingResults.concat(EmojiCustom.findByNameOrAliasExceptID(alias, emojiData._id).fetch()); + matchingResults = await EmojiCustom.findByNameOrAliasExceptID(emojiData.name, emojiData._id).toArray(); + for await (const alias of emojiData.aliases) { + matchingResults = matchingResults.concat(await EmojiCustom.findByNameOrAliasExceptID(alias, emojiData._id).toArray()); } } else { - matchingResults = EmojiCustom.findByNameOrAlias(emojiData.name).fetch(); - for (const alias of emojiData.aliases) { - matchingResults = matchingResults.concat(EmojiCustom.findByNameOrAlias(alias).fetch()); + matchingResults = await EmojiCustom.findByNameOrAlias(emojiData.name).toArray(); + for await (const alias of emojiData.aliases) { + matchingResults = matchingResults.concat(await EmojiCustom.findByNameOrAlias(alias).toArray()); } } @@ -77,7 +77,7 @@ Meteor.methods({ extension: emojiData.extension, }; - const _id = EmojiCustom.create(createEmoji); + const _id = (await EmojiCustom.create(createEmoji)).insertedId; api.broadcast('emoji.updateCustom', createEmoji); @@ -90,7 +90,7 @@ Meteor.methods({ RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${ emojiData.previousName }.${ emojiData.extension }`)); RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${ emojiData.previousName }.${ emojiData.previousExtension }`)); - EmojiCustom.setExtension(emojiData._id, emojiData.extension); + await EmojiCustom.setExtension(emojiData._id, emojiData.extension); } else if (emojiData.name !== emojiData.previousName) { const rs = RocketChatFileEmojiCustomInstance.getFileWithReadStream(encodeURIComponent(`${ emojiData.previousName }.${ emojiData.previousExtension }`)); if (rs !== null) { @@ -104,13 +104,13 @@ Meteor.methods({ } if (emojiData.name !== emojiData.previousName) { - EmojiCustom.setName(emojiData._id, emojiData.name); + await EmojiCustom.setName(emojiData._id, emojiData.name); } if (emojiData.aliases) { - EmojiCustom.setAliases(emojiData._id, emojiData.aliases); + await EmojiCustom.setAliases(emojiData._id, emojiData.aliases); } else { - EmojiCustom.setAliases(emojiData._id, []); + await EmojiCustom.setAliases(emojiData._id, []); } api.broadcast('emoji.updateCustom', emojiData); diff --git a/app/emoji-custom/server/methods/listEmojiCustom.js b/app/emoji-custom/server/methods/listEmojiCustom.js index d06b382af85e6..d66aeee1a6add 100644 --- a/app/emoji-custom/server/methods/listEmojiCustom.js +++ b/app/emoji-custom/server/methods/listEmojiCustom.js @@ -1,9 +1,9 @@ import { Meteor } from 'meteor/meteor'; -import { EmojiCustom } from '../../../models'; +import { EmojiCustom } from '../../../models/server/raw'; Meteor.methods({ - listEmojiCustom(options = {}) { - return EmojiCustom.find(options).fetch(); + async listEmojiCustom(options = {}) { + return EmojiCustom.find(options).toArray(); }, }); diff --git a/app/federation/server/endpoints/dispatch.js b/app/federation/server/endpoints/dispatch.js index ae392ac8aac86..333a30bbeebf3 100644 --- a/app/federation/server/endpoints/dispatch.js +++ b/app/federation/server/endpoints/dispatch.js @@ -4,12 +4,13 @@ import { API } from '../../../api/server'; import { serverLogger } from '../lib/logger'; import { contextDefinitions, eventTypes } from '../../../models/server/models/FederationEvents'; import { - FederationRoomEvents, FederationServers, + FederationRoomEvents, Messages, Rooms, Subscriptions, Users, } from '../../../models/server'; +import { FederationServers } from '../../../models/server/raw'; import { normalizers } from '../normalizers'; import { deleteRoom } from '../../../lib/server/functions'; import { Notifications } from '../../../notifications/server'; @@ -139,7 +140,7 @@ const eventHandlers = { // Refresh the servers list if (federationAltered) { - FederationServers.refreshServers(); + await FederationServers.refreshServers(); // Update the room's federation property Rooms.update({ _id: roomId }, { $set: { 'federation.domains': domainsAfterAdd } }); @@ -163,7 +164,7 @@ const eventHandlers = { Subscriptions.removeByRoomIdAndUserId(roomId, user._id); // Refresh the servers list - FederationServers.refreshServers(); + await FederationServers.refreshServers(); // Update the room's federation property Rooms.update({ _id: roomId }, { $set: { 'federation.domains': domainsAfterRemoval } }); @@ -186,7 +187,7 @@ const eventHandlers = { Subscriptions.removeByRoomIdAndUserId(roomId, user._id); // Refresh the servers list - FederationServers.refreshServers(); + await FederationServers.refreshServers(); // Update the room's federation property Rooms.update({ _id: roomId }, { $set: { 'federation.domains': domainsAfterRemoval } }); @@ -226,7 +227,7 @@ const eventHandlers = { const { federation: { origin } } = denormalizedMessage; - const { upload, buffer } = getUpload(origin, denormalizedMessage.file._id); + const { upload, buffer } = await getUpload(origin, denormalizedMessage.file._id); const oldUploadId = upload._id; @@ -444,7 +445,7 @@ const eventHandlers = { }; API.v1.addRoute('federation.events.dispatch', { authRequired: false, rateLimiterOptions: { numRequestsAllowed: 30, intervalTimeInMS: 1000 } }, { - async post() { + post() { if (!isFederationEnabled()) { return API.v1.failure('Federation not enabled'); } @@ -454,7 +455,7 @@ API.v1.addRoute('federation.events.dispatch', { authRequired: false, rateLimiter let payload; try { - payload = decryptIfNeeded(this.request, this.bodyParams); + payload = Promise.await(decryptIfNeeded(this.request, this.bodyParams)); } catch (err) { return API.v1.failure('Could not decrypt payload'); } @@ -472,7 +473,7 @@ API.v1.addRoute('federation.events.dispatch', { authRequired: false, rateLimiter let eventResult; if (eventHandlers[event.type]) { - eventResult = await eventHandlers[event.type](event); + eventResult = Promise.await(eventHandlers[event.type](event)); } // If there was an error handling the event, take action @@ -480,7 +481,7 @@ API.v1.addRoute('federation.events.dispatch', { authRequired: false, rateLimiter try { serverLogger.debug({ msg: 'federation.events.dispatch => Event has missing parents', event }); - requestEventsFromLatest(event.origin, getFederationDomain(), contextDefinitions.defineType(event), event.context, eventResult.latestEventIds); + Promise.await(requestEventsFromLatest(event.origin, getFederationDomain(), contextDefinitions.defineType(event), event.context, eventResult.latestEventIds)); // And stop handling the events break; diff --git a/app/federation/server/endpoints/requestFromLatest.js b/app/federation/server/endpoints/requestFromLatest.js index cac0168c8c12d..84fd69f88d3af 100644 --- a/app/federation/server/endpoints/requestFromLatest.js +++ b/app/federation/server/endpoints/requestFromLatest.js @@ -8,7 +8,7 @@ import { isFederationEnabled } from '../lib/isFederationEnabled'; import { dispatchEvents } from '../handler'; API.v1.addRoute('federation.events.requestFromLatest', { authRequired: false }, { - async post() { + post() { if (!isFederationEnabled()) { return API.v1.failure('Federation not enabled'); } @@ -18,7 +18,7 @@ API.v1.addRoute('federation.events.requestFromLatest', { authRequired: false }, let payload; try { - payload = decryptIfNeeded(this.request, this.bodyParams); + payload = Promise.await(decryptIfNeeded(this.request, this.bodyParams)); } catch (err) { return API.v1.failure('Could not decrypt payload'); } @@ -54,7 +54,7 @@ API.v1.addRoute('federation.events.requestFromLatest', { authRequired: false }, } // Dispatch all the events, on the same request - dispatchEvents([fromDomain], missingEvents); + Promise.await(dispatchEvents([fromDomain], missingEvents)); return API.v1.success(); }, diff --git a/app/federation/server/endpoints/uploads.js b/app/federation/server/endpoints/uploads.js index 7735a630f15e7..a997b2aff3073 100644 --- a/app/federation/server/endpoints/uploads.js +++ b/app/federation/server/endpoints/uploads.js @@ -1,5 +1,5 @@ import { API } from '../../../api/server'; -import { Uploads } from '../../../models/server'; +import { Uploads } from '../../../models/server/raw'; import { FileUpload } from '../../../file-upload/server'; import { isFederationEnabled } from '../lib/isFederationEnabled'; @@ -11,7 +11,7 @@ API.v1.addRoute('federation.uploads', { authRequired: false }, { const { upload_id } = this.requestParams(); - const upload = Uploads.findOneById(upload_id); + const upload = Promise.await(Uploads.findOneById(upload_id)); if (!upload) { return API.v1.failure('There is no such file in this server'); diff --git a/app/federation/server/functions/addUser.js b/app/federation/server/functions/addUser.js index eebd1656260b2..314b7893fbc10 100644 --- a/app/federation/server/functions/addUser.js +++ b/app/federation/server/functions/addUser.js @@ -1,15 +1,16 @@ import { Meteor } from 'meteor/meteor'; import * as federationErrors from './errors'; -import { FederationServers, Users } from '../../../models/server'; +import { Users } from '../../../models/server'; +import { FederationServers } from '../../../models/server/raw'; import { getUserByUsername } from '../handler'; -export function addUser(query) { +export async function addUser(query) { if (!Meteor.userId()) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'addUser' }); } - const user = getUserByUsername(query); + const user = await getUserByUsername(query); if (!user) { throw federationErrors.userNotFound(query); @@ -22,7 +23,7 @@ export function addUser(query) { userId = Users.create(user); // Refresh the servers list - FederationServers.refreshServers(); + await FederationServers.refreshServers(); } catch (err) { // This might get called twice by the createDirectMessage method // so we need to handle the situation accordingly diff --git a/app/federation/server/functions/dashboard.js b/app/federation/server/functions/dashboard.js index 137ef802c5dc1..3f256bf3d88ab 100644 --- a/app/federation/server/functions/dashboard.js +++ b/app/federation/server/functions/dashboard.js @@ -1,21 +1,22 @@ import { Meteor } from 'meteor/meteor'; -import { FederationServers, FederationRoomEvents, Users } from '../../../models/server'; +import { FederationRoomEvents, Users } from '../../../models/server'; +import { FederationServers } from '../../../models/server/raw'; -export function getStatistics() { +export async function getStatistics() { const numberOfEvents = FederationRoomEvents.find().count(); const numberOfFederatedUsers = Users.findRemote().count(); - const numberOfServers = FederationServers.find().count(); + const numberOfServers = await FederationServers.find().count(); return { numberOfEvents, numberOfFederatedUsers, numberOfServers }; } -export function federationGetOverviewData() { +export async function federationGetOverviewData() { if (!Meteor.userId()) { throw new Meteor.Error('not-authorized'); } - const { numberOfEvents, numberOfFederatedUsers, numberOfServers } = getStatistics(); + const { numberOfEvents, numberOfFederatedUsers, numberOfServers } = await getStatistics(); return { data: [{ @@ -31,12 +32,12 @@ export function federationGetOverviewData() { }; } -export function federationGetServers() { +export async function federationGetServers() { if (!Meteor.userId()) { throw new Meteor.Error('not-authorized'); } - const servers = FederationServers.find().fetch(); + const servers = await FederationServers.find().toArray(); return { data: servers, diff --git a/app/federation/server/functions/helpers.js b/app/federation/server/functions/helpers.js deleted file mode 100644 index 4113b6014edd2..0000000000000 --- a/app/federation/server/functions/helpers.js +++ /dev/null @@ -1,69 +0,0 @@ -import { Settings, Subscriptions, Users } from '../../../models/server'; -import { STATUS_ENABLED, STATUS_REGISTERING } from '../constants'; - -export const getNameAndDomain = (fullyQualifiedName) => fullyQualifiedName.split('@'); -export const isFullyQualified = (name) => name.indexOf('@') !== -1; - -export function isRegisteringOrEnabled() { - const status = Settings.findOneById('FEDERATION_Status'); - return [STATUS_ENABLED, STATUS_REGISTERING].includes(status && status.value); -} - -export function updateStatus(status) { - Settings.updateValueById('FEDERATION_Status', status); -} - -export function updateEnabled(enabled) { - Settings.updateValueById('FEDERATION_Enabled', enabled); -} - -export const checkRoomType = (room) => room.t === 'p' || room.t === 'd'; -export const checkRoomDomainsLength = (domains) => domains.length <= (process.env.FEDERATED_DOMAINS_LENGTH || 10); - -export const hasExternalDomain = ({ federation }) => { - // same test as isFederated(room) - if (!federation) { - return false; - } - - return federation.domains - .some((domain) => domain !== federation.origin); -}; - -export const isLocalUser = ({ federation }, localDomain) => - !federation || federation.origin === localDomain; - -export const getFederatedRoomData = (room) => { - let hasFederatedUser = false; - - let users = null; - let subscriptions = null; - - if (room.t === 'd') { - // Check if there is a federated user on this room - hasFederatedUser = room.usernames.some(isFullyQualified); - } else { - // Find all subscriptions of this room - subscriptions = Subscriptions.findByRoomIdWhenUsernameExists(room._id).fetch(); - subscriptions = subscriptions.reduce((acc, s) => { - acc[s.u._id] = s; - - return acc; - }, {}); - - // Get all user ids - const userIds = Object.keys(subscriptions); - - // Load all the users - users = Users.findUsersWithUsernameByIds(userIds).fetch(); - - // Check if there is a federated user on this room - hasFederatedUser = users.some((u) => isFullyQualified(u.username)); - } - - return { - hasFederatedUser, - users, - subscriptions, - }; -}; diff --git a/app/federation/server/functions/helpers.ts b/app/federation/server/functions/helpers.ts new file mode 100644 index 0000000000000..e8cb5b3e5170c --- /dev/null +++ b/app/federation/server/functions/helpers.ts @@ -0,0 +1,77 @@ +import { IRoom, isDirectMessageRoom } from '../../../../definition/IRoom'; +import { ISubscription } from '../../../../definition/ISubscription'; +import { IRegisterUser, IUser } from '../../../../definition/IUser'; +import { Subscriptions, Users } from '../../../models/server'; +import { Settings } from '../../../models/server/raw'; +import { STATUS_ENABLED, STATUS_REGISTERING } from '../constants'; + +export const getNameAndDomain = (fullyQualifiedName: string): string [] => fullyQualifiedName.split('@'); + +export const isFullyQualified = (name: string): boolean => name.indexOf('@') !== -1; + +export async function isRegisteringOrEnabled(): Promise { + const value = await Settings.getValueById('FEDERATION_Status'); + return typeof value === 'string' && [STATUS_ENABLED, STATUS_REGISTERING].includes(value); +} + +export async function updateStatus(status: string): Promise { + await Settings.updateValueById('FEDERATION_Status', status); +} + +export async function updateEnabled(enabled: boolean): Promise { + await Settings.updateValueById('FEDERATION_Enabled', enabled); +} + +export const checkRoomType = (room: IRoom): boolean => room.t === 'p' || room.t === 'd'; +export const checkRoomDomainsLength = (domains: unknown[]): boolean => domains.length <= (process.env.FEDERATED_DOMAINS_LENGTH || 10); + +export const hasExternalDomain = ({ federation }: { federation: { origin: string; domains: string[] } }): boolean => { + // same test as isFederated(room) + if (!federation) { + return false; + } + + return federation.domains + .some((domain) => domain !== federation.origin); +}; + +export const isLocalUser = ({ federation }: { federation: { origin: string } }, localDomain: string): boolean => + !federation || federation.origin === localDomain; + +export const getFederatedRoomData = (room: IRoom): { + hasFederatedUser: boolean; + users: IUser[]; + subscriptions: { [k: string]: ISubscription } | undefined; +} => { + if (isDirectMessageRoom(room)) { + // Check if there is a federated user on this room + + return { + users: [], + hasFederatedUser: room.usernames.some(isFullyQualified), + subscriptions: undefined, + }; + } + + // Find all subscriptions of this room + const s = Subscriptions.findByRoomIdWhenUsernameExists(room._id).fetch() as ISubscription[]; + const subscriptions = s.reduce((acc, s) => { + acc[s.u._id] = s; + return acc; + }, {} as { [k: string]: ISubscription }); + + // Get all user ids + const userIds = Object.keys(subscriptions); + + // Load all the users + const users: IRegisterUser[] = Users.findUsersWithUsernameByIds(userIds).fetch(); + + // Check if there is a federated user on this room + const hasFederatedUser = users.some((u) => isFullyQualified(u.username)); + + return { + hasFederatedUser, + users, + subscriptions, + }; +}; diff --git a/app/federation/server/handler/index.js b/app/federation/server/handler/index.js index 7827ddf063a78..46aec0b7dcea1 100644 --- a/app/federation/server/handler/index.js +++ b/app/federation/server/handler/index.js @@ -5,7 +5,7 @@ import { clientLogger } from '../lib/logger'; import { isFederationEnabled } from '../lib/isFederationEnabled'; import { federationRequestToPeer } from '../lib/http'; -export function federationSearchUsers(query) { +export async function federationSearchUsers(query) { if (!isFederationEnabled()) { throw disabled('client.searchUsers'); } @@ -16,12 +16,12 @@ export function federationSearchUsers(query) { const uri = `/api/v1/federation.users.search?${ qs.stringify({ username, domain: peerDomain }) }`; - const { data: { users } } = federationRequestToPeer('GET', peerDomain, uri); + const { data: { users } } = await federationRequestToPeer('GET', peerDomain, uri); return users; } -export function getUserByUsername(query) { +export async function getUserByUsername(query) { if (!isFederationEnabled()) { throw disabled('client.searchUsers'); } @@ -32,12 +32,12 @@ export function getUserByUsername(query) { const uri = `/api/v1/federation.users.getByUsername?${ qs.stringify({ username }) }`; - const { data: { user } } = federationRequestToPeer('GET', peerDomain, uri); + const { data: { user } } = await federationRequestToPeer('GET', peerDomain, uri); return user; } -export function requestEventsFromLatest(domain, fromDomain, contextType, contextQuery, latestEventIds) { +export async function requestEventsFromLatest(domain, fromDomain, contextType, contextQuery, latestEventIds) { if (!isFederationEnabled()) { throw disabled('client.requestEventsFromLatest'); } @@ -46,11 +46,11 @@ export function requestEventsFromLatest(domain, fromDomain, contextType, context const uri = '/api/v1/federation.events.requestFromLatest'; - federationRequestToPeer('POST', domain, uri, { fromDomain, contextType, contextQuery, latestEventIds }); + await federationRequestToPeer('POST', domain, uri, { fromDomain, contextType, contextQuery, latestEventIds }); } -export function dispatchEvents(domains, events) { +export async function dispatchEvents(domains, events) { if (!isFederationEnabled()) { throw disabled('client.dispatchEvents'); } @@ -61,17 +61,17 @@ export function dispatchEvents(domains, events) { const uri = '/api/v1/federation.events.dispatch'; - for (const domain of domains) { - federationRequestToPeer('POST', domain, uri, { events }, { ignoreErrors: true }); + for await (const domain of domains) { + await federationRequestToPeer('POST', domain, uri, { events }, { ignoreErrors: true }); } } -export function dispatchEvent(domains, event) { - dispatchEvents([...new Set(domains)], [event]); +export async function dispatchEvent(domains, event) { + await dispatchEvents([...new Set(domains)], [event]); } -export function getUpload(domain, fileId) { - const { data: { upload, buffer } } = federationRequestToPeer('GET', domain, `/api/v1/federation.uploads?${ qs.stringify({ upload_id: fileId }) }`); +export async function getUpload(domain, fileId) { + const { data: { upload, buffer } } = await federationRequestToPeer('GET', domain, `/api/v1/federation.uploads?${ qs.stringify({ upload_id: fileId }) }`); return { upload, buffer: Buffer.from(buffer) }; } diff --git a/app/federation/server/hooks/afterCreateDirectRoom.js b/app/federation/server/hooks/afterCreateDirectRoom.js index ac05794e1c2ee..79e6fc992836e 100644 --- a/app/federation/server/hooks/afterCreateDirectRoom.js +++ b/app/federation/server/hooks/afterCreateDirectRoom.js @@ -41,7 +41,7 @@ async function afterCreateDirectRoom(room, extras) { })); // Dispatch the events - dispatchEvents(normalizedRoom.federation.domains, [genesisEvent, ...events]); + await dispatchEvents(normalizedRoom.federation.domains, [genesisEvent, ...events]); } catch (err) { await deleteRoom(room._id); diff --git a/app/federation/server/hooks/afterCreateRoom.js b/app/federation/server/hooks/afterCreateRoom.js index 75dfeeac6575a..905e108740cf8 100644 --- a/app/federation/server/hooks/afterCreateRoom.js +++ b/app/federation/server/hooks/afterCreateRoom.js @@ -47,7 +47,7 @@ export async function doAfterCreateRoom(room, users, subscriptions) { const genesisEvent = await FederationRoomEvents.createGenesisEvent(getFederationDomain(), normalizedRoom); // Dispatch the events - dispatchEvents(normalizedRoom.federation.domains, [genesisEvent, ...addUserEvents]); + await dispatchEvents(normalizedRoom.federation.domains, [genesisEvent, ...addUserEvents]); } async function afterCreateRoom(roomOwner, room) { diff --git a/app/federation/server/lib/crypt.js b/app/federation/server/lib/crypt.js index 7a231a13fb911..5a7685a2e9e09 100644 --- a/app/federation/server/lib/crypt.js +++ b/app/federation/server/lib/crypt.js @@ -1,19 +1,19 @@ -import { FederationKeys } from '../../../models/server'; +import { FederationKeys } from '../../../models/server/raw'; import { getFederationDomain } from './getFederationDomain'; import { search } from './dns'; import { cryptLogger } from './logger'; -export function decrypt(data, peerKey) { +export async function decrypt(data, peerKey) { // // Decrypt the payload const payloadBuffer = Buffer.from(data); // Decrypt with the peer's public key try { - data = FederationKeys.loadKey(peerKey, 'public').decryptPublic(payloadBuffer); + data = (await FederationKeys.loadKey(peerKey, 'public')).decryptPublic(payloadBuffer); // Decrypt with the local private key - data = FederationKeys.getPrivateKey().decrypt(data); + data = (await FederationKeys.getPrivateKey()).decrypt(data); } catch (err) { cryptLogger.error(err); @@ -23,7 +23,7 @@ export function decrypt(data, peerKey) { return JSON.parse(data.toString()); } -export function decryptIfNeeded(request, bodyParams) { +export async function decryptIfNeeded(request, bodyParams) { // // Look for the domain that sent this event const remotePeerDomain = request.headers['x-federation-domain']; @@ -48,17 +48,17 @@ export function decryptIfNeeded(request, bodyParams) { return decrypt(bodyParams, peerKey); } -export function encrypt(data, peerKey) { +export async function encrypt(data, peerKey) { if (!data) { return data; } try { // Encrypt with the peer's public key - data = FederationKeys.loadKey(peerKey, 'public').encrypt(data); + data = (await FederationKeys.loadKey(peerKey, 'public')).encrypt(data); // Encrypt with the local private key - return FederationKeys.getPrivateKey().encryptPrivate(data); + return (await FederationKeys.getPrivateKey()).encryptPrivate(data); } catch (err) { cryptLogger.error(err); diff --git a/app/federation/server/lib/dns.js b/app/federation/server/lib/dns.js index 0c4e2f348e1b9..0080ddae625be 100644 --- a/app/federation/server/lib/dns.js +++ b/app/federation/server/lib/dns.js @@ -17,12 +17,12 @@ const memoizedDnsResolveTXT = mem(dnsResolveTXT, { maxAge: cacheMaxAge }); const hubUrl = process.env.NODE_ENV === 'development' ? 'http://localhost:8080' : 'https://hub.rocket.chat'; -export function registerWithHub(peerDomain, url, publicKey) { +export async function registerWithHub(peerDomain, url, publicKey) { const body = { domain: peerDomain, url, public_key: publicKey }; try { // If there is no DNS entry for that, get from the Hub - federationRequest('POST', `${ hubUrl }/api/v1/peers`, body); + await federationRequest('POST', `${ hubUrl }/api/v1/peers`, body); return true; } catch (err) { @@ -32,12 +32,12 @@ export function registerWithHub(peerDomain, url, publicKey) { } } -export function searchHub(peerDomain) { +export async function searchHub(peerDomain) { try { dnsLogger.debug(`searchHub: peerDomain=${ peerDomain }`); // If there is no DNS entry for that, get from the Hub - const { data: { peer } } = federationRequest('GET', `${ hubUrl }/api/v1/peers?search=${ peerDomain }`); + const { data: { peer } } = await federationRequest('GET', `${ hubUrl }/api/v1/peers?search=${ peerDomain }`); if (!peer) { dnsLogger.debug(`searchHub: could not find peerDomain=${ peerDomain }`); diff --git a/app/federation/server/lib/http.js b/app/federation/server/lib/http.js index 542a2d32ef9ea..e18d09b8e86d8 100644 --- a/app/federation/server/lib/http.js +++ b/app/federation/server/lib/http.js @@ -6,14 +6,14 @@ import { getFederationDomain } from './getFederationDomain'; import { search } from './dns'; import { encrypt } from './crypt'; -export function federationRequest(method, url, body, headers, peerKey = null) { +export async function federationRequest(method, url, body, headers, peerKey = null) { let data = null; if ((method === 'POST' || method === 'PUT') && body) { data = EJSON.toJSONValue(body); if (peerKey) { - data = encrypt(data, peerKey); + data = await encrypt(data, peerKey); } } @@ -22,7 +22,7 @@ export function federationRequest(method, url, body, headers, peerKey = null) { return MeteorHTTP.call(method, url, { data, timeout: 2000, headers: { ...headers, 'x-federation-domain': getFederationDomain() } }); } -export function federationRequestToPeer(method, peerDomain, uri, body, options = {}) { +export async function federationRequestToPeer(method, peerDomain, uri, body, options = {}) { const ignoreErrors = peerDomain === getFederationDomain() ? false : options.ignoreErrors; const { url: baseUrl, publicKey } = search(peerDomain); @@ -39,7 +39,7 @@ export function federationRequestToPeer(method, peerDomain, uri, body, options = try { httpLogger.debug({ msg: 'federationRequestToPeer', url: `${ baseUrl }${ uri }` }); - result = federationRequest(method, `${ baseUrl }${ uri }`, body, options.headers || {}, peerKey); + result = await federationRequest(method, `${ baseUrl }${ uri }`, body, options.headers || {}, peerKey); } catch (err) { httpLogger.error({ msg: `${ ignoreErrors ? '[IGNORED] ' : '' }Error`, err }); diff --git a/app/federation/server/startup/generateKeys.js b/app/federation/server/startup/generateKeys.js index 012cdd0b48f47..32eaacc304184 100644 --- a/app/federation/server/startup/generateKeys.js +++ b/app/federation/server/startup/generateKeys.js @@ -1,6 +1,8 @@ -import { FederationKeys } from '../../../models/server'; +import { FederationKeys } from '../../../models/server/raw'; // Create key pair if needed -if (!FederationKeys.getPublicKey()) { - FederationKeys.generateKeys(); -} +(async () => { + if (!await FederationKeys.getPublicKey()) { + await FederationKeys.generateKeys(); + } +})(); diff --git a/app/federation/server/startup/settings.ts b/app/federation/server/startup/settings.ts index cfa7fda19e6af..36ade9e70eeb1 100644 --- a/app/federation/server/startup/settings.ts +++ b/app/federation/server/startup/settings.ts @@ -7,11 +7,11 @@ import { getFederationDiscoveryMethod } from '../lib/getFederationDiscoveryMetho import { registerWithHub } from '../lib/dns'; import { enableCallbacks, disableCallbacks } from '../lib/callbacks'; import { setupLogger } from '../lib/logger'; -import { FederationKeys } from '../../../models/server'; +import { FederationKeys } from '../../../models/server/raw'; import { STATUS_ENABLED, STATUS_REGISTERING, STATUS_ERROR_REGISTERING, STATUS_DISABLED } from '../constants'; -Meteor.startup(function() { - const federationPublicKey = FederationKeys.getPublicKeyString(); +Meteor.startup(async function() { + const federationPublicKey = await FederationKeys.getPublicKeyString(); settingsRegistry.addGroup('Federation', function() { this.add('FEDERATION_Enabled', false, { @@ -36,7 +36,7 @@ Meteor.startup(function() { // disableReset: true, }); - this.add('FEDERATION_Public_Key', federationPublicKey, { + this.add('FEDERATION_Public_Key', federationPublicKey || '', { readonly: true, type: 'string', multiline: true, @@ -65,26 +65,26 @@ Meteor.startup(function() { }); }); -const updateSettings = function(): void { +const updateSettings = async function(): Promise { // Get the key pair - if (getFederationDiscoveryMethod() === 'hub' && !isRegisteringOrEnabled()) { + if (getFederationDiscoveryMethod() === 'hub' && !Promise.await(isRegisteringOrEnabled())) { // Register with hub try { - updateStatus(STATUS_REGISTERING); + await updateStatus(STATUS_REGISTERING); - registerWithHub(getFederationDomain(), settings.get('Site_Url'), FederationKeys.getPublicKeyString()); + await registerWithHub(getFederationDomain(), settings.get('Site_Url'), await FederationKeys.getPublicKeyString()); - updateStatus(STATUS_ENABLED); + await updateStatus(STATUS_ENABLED); } catch (err) { // Disable federation - updateEnabled(false); + await updateEnabled(false); - updateStatus(STATUS_ERROR_REGISTERING); + await updateStatus(STATUS_ERROR_REGISTERING); } - } else { - updateStatus(STATUS_ENABLED); + return; } + await updateStatus(STATUS_ENABLED); }; // Add settings listeners @@ -92,11 +92,11 @@ settings.watch('FEDERATION_Enabled', function enableOrDisable(value) { setupLogger.info(`Federation is ${ value ? 'enabled' : 'disabled' }`); if (value) { - updateSettings(); + Promise.await(updateSettings()); enableCallbacks(); } else { - updateStatus(STATUS_DISABLED); + Promise.await(updateStatus(STATUS_DISABLED)); disableCallbacks(); } diff --git a/app/file-upload/server/lib/FileUpload.js b/app/file-upload/server/lib/FileUpload.js index aa8d49ab408eb..d361ed3e2294a 100644 --- a/app/file-upload/server/lib/FileUpload.js +++ b/app/file-upload/server/lib/FileUpload.js @@ -2,6 +2,7 @@ import fs from 'fs'; import stream from 'stream'; import { Meteor } from 'meteor/meteor'; +import { Mongo } from 'meteor/mongo'; import streamBuffers from 'stream-buffers'; import Future from 'fibers/future'; import sharp from 'sharp'; @@ -13,9 +14,7 @@ import filesize from 'filesize'; import { AppsEngineException } from '@rocket.chat/apps-engine/definition/exceptions'; import { settings } from '../../../settings/server'; -import Uploads from '../../../models/server/models/Uploads'; -import UserDataFiles from '../../../models/server/models/UserDataFiles'; -import Avatars from '../../../models/server/models/Avatars'; +import { Avatars, UserDataFiles, Uploads } from '../../../models/server/raw'; import Users from '../../../models/server/models/Users'; import Rooms from '../../../models/server/models/Rooms'; import Settings from '../../../models/server/models/Settings'; @@ -41,6 +40,9 @@ settings.watch('FileUpload_MaxFileSize', function(value) { } }); +const AvatarModel = new Mongo.Collection(Avatars.col.collectionName); +const UserDataFilesModel = new Mongo.Collection(UserDataFiles.col.collectionName); +const UploadsModel = new Mongo.Collection(Uploads.col.collectionName); export const FileUpload = { handlers: {}, @@ -139,7 +141,7 @@ export const FileUpload = { defaultUploads() { return { - collection: Uploads.model, + collection: UploadsModel, filter: new UploadFS.Filter({ onCheck: FileUpload.validateFileUpload, }), @@ -161,7 +163,7 @@ export const FileUpload = { defaultAvatars() { return { - collection: Avatars.model, + collection: AvatarModel, filter: new UploadFS.Filter({ onCheck: FileUpload.validateAvatarUpload, }), @@ -176,7 +178,7 @@ export const FileUpload = { defaultUserDataFiles() { return { - collection: UserDataFiles.model, + collection: UserDataFilesModel, getPath(file) { return `${ settings.get('uniqueID') }/uploads/userData/${ file.userId }`; }, @@ -254,7 +256,7 @@ export const FileUpload = { }, resizeImagePreview(file) { - file = Uploads.findOneById(file._id); + file = Promise.await(Uploads.findOneById(file._id)); file = FileUpload.addExtensionTo(file); const image = FileUpload.getStore('Uploads')._store.getReadStream(file._id, file); @@ -279,7 +281,7 @@ export const FileUpload = { return; } - file = Uploads.findOneById(file._id); + file = Promise.await(Uploads.findOneById(file._id)); file = FileUpload.addExtensionTo(file); const store = FileUpload.getStore('Uploads'); const image = store._store.getReadStream(file._id, file); @@ -378,11 +380,11 @@ export const FileUpload = { } // update file record to match user's username const user = Users.findOneById(file.userId); - const oldAvatar = Avatars.findOneByName(user.username); + const oldAvatar = Promise.await(Avatars.findOneByName(user.username)); if (oldAvatar) { - Avatars.deleteFile(oldAvatar._id); + Promise.await(Avatars.deleteFile(oldAvatar._id)); } - Avatars.updateFileNameById(file._id, user.username); + Promise.await(Avatars.updateFileNameById(file._id, user.username)); // console.log('upload finished ->', file); }, @@ -571,11 +573,11 @@ export class FileUploadClass { this.store.delete(fileId); } - return this.model.deleteFile(fileId); + return Promise.await(this.model.deleteFile(fileId)); } deleteById(fileId) { - const file = this.model.findOneById(fileId); + const file = Promise.await(this.model.findOneById(fileId)); if (!file) { return; @@ -587,7 +589,7 @@ export class FileUploadClass { } deleteByName(fileName) { - const file = this.model.findOneByName(fileName); + const file = Promise.await(this.model.findOneByName(fileName)); if (!file) { return; @@ -600,7 +602,7 @@ export class FileUploadClass { deleteByRoomId(rid) { - const file = this.model.findOneByRoomId(rid); + const file = Promise.await(this.model.findOneByRoomId(rid)); if (!file) { return; diff --git a/app/file-upload/server/lib/requests.js b/app/file-upload/server/lib/requests.js index 80a3b4213b38d..3b2e8dad19d77 100644 --- a/app/file-upload/server/lib/requests.js +++ b/app/file-upload/server/lib/requests.js @@ -1,13 +1,13 @@ import { WebApp } from 'meteor/webapp'; import { FileUpload } from './FileUpload'; -import { Uploads } from '../../../models'; +import { Uploads } from '../../../models/server/raw'; -WebApp.connectHandlers.use(FileUpload.getPath(), function(req, res, next) { +WebApp.connectHandlers.use(FileUpload.getPath(), async function(req, res, next) { const match = /^\/([^\/]+)\/(.*)/.exec(req.url); if (match && match[1]) { - const file = Uploads.findOneById(match[1]); + const file = await Uploads.findOneById(match[1]); if (file) { if (!FileUpload.requestCanAccessFiles(req)) { diff --git a/app/file-upload/server/methods/getS3FileUrl.js b/app/file-upload/server/methods/getS3FileUrl.js index f68f720d171be..cfffdfcc032af 100644 --- a/app/file-upload/server/methods/getS3FileUrl.js +++ b/app/file-upload/server/methods/getS3FileUrl.js @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { UploadFS } from 'meteor/jalik:ufs'; import { settings } from '../../../settings/server'; -import { Uploads } from '../../../models'; +import { Uploads } from '../../../models/server/raw'; let protectedFiles; @@ -11,11 +11,11 @@ settings.watch('FileUpload_ProtectFiles', function(value) { }); Meteor.methods({ - getS3FileUrl(fileId) { + async getS3FileUrl(fileId) { if (protectedFiles && !Meteor.userId()) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'sendFileMessage' }); } - const file = Uploads.findOneById(fileId); + const file = await Uploads.findOneById(fileId); return UploadFS.getStore('AmazonS3:Uploads').getRedirectURL(file); }, diff --git a/app/file-upload/server/methods/sendFileMessage.ts b/app/file-upload/server/methods/sendFileMessage.ts index 80dec7bca683e..886f9167e07ee 100644 --- a/app/file-upload/server/methods/sendFileMessage.ts +++ b/app/file-upload/server/methods/sendFileMessage.ts @@ -3,8 +3,7 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; import _ from 'underscore'; -import { Uploads } from '../../../models/server'; -import { Rooms } from '../../../models/server/raw'; +import { Rooms, Uploads } from '../../../models/server/raw'; import { callbacks } from '../../../callbacks/server'; import { FileUpload } from '../lib/FileUpload'; import { canAccessRoom } from '../../../authorization/server/functions/canAccessRoom'; @@ -35,7 +34,7 @@ Meteor.methods({ tmid: Match.Optional(String), }); - Uploads.updateFileComplete(file._id, user._id, _.omit(file, '_id')); + await Uploads.updateFileComplete(file._id, user._id, _.omit(file, '_id')); const fileUrl = FileUpload.getPath(`${ file._id }/${ encodeURI(file.name) }`); diff --git a/app/integrations/server/api/api.js b/app/integrations/server/api/api.js index 17a04b6c35822..eb223c67c9ad6 100644 --- a/app/integrations/server/api/api.js +++ b/app/integrations/server/api/api.js @@ -14,6 +14,7 @@ import { incomingLogger } from '../logger'; import { processWebhookMessage } from '../../../lib/server'; import { API, APIClass, defaultRateLimiterOptions } from '../../../api/server'; import * as Models from '../../../models/server'; +import { Integrations } from '../../../models/server/raw'; import { settings } from '../../../settings/server'; const compiledScripts = {}; @@ -129,9 +130,10 @@ function removeIntegration(options, user) { incomingLogger.info('Remove integration'); incomingLogger.debug(options); - const integrationToRemove = Models.Integrations.findOne({ - urls: options.target_url, - }); + const integrationToRemove = Promise.await(Integrations.findOneByUrl(options.target_url)); + if (!integrationToRemove) { + return API.v1.failure('integration-not-found'); + } Meteor.runAsUser(user._id, () => Meteor.call('deleteOutgoingIntegration', integrationToRemove._id)); @@ -373,10 +375,10 @@ const Api = new WebHookAPI({ } } - this.integration = Models.Integrations.findOne({ + this.integration = Promise.await(Integrations.findOne({ _id: this.request.params.integrationId, token: decodeURIComponent(this.request.params.token), - }); + })); if (!this.integration) { incomingLogger.info(`Invalid integration id ${ this.request.params.integrationId } or token ${ this.request.params.token }`); diff --git a/app/integrations/server/lib/triggerHandler.js b/app/integrations/server/lib/triggerHandler.js index 33cc33ddb24d1..27f71a4e5729d 100644 --- a/app/integrations/server/lib/triggerHandler.js +++ b/app/integrations/server/lib/triggerHandler.js @@ -10,6 +10,7 @@ import Fiber from 'fibers'; import Future from 'fibers/future'; import * as Models from '../../../models/server'; +import { Integrations, IntegrationHistory } from '../../../models/server/raw'; import { settings } from '../../../settings/server'; import { getRoomByNameOrIdWithOptionToJoin, processWebhookMessage } from '../../../lib/server'; import { outgoingLogger } from '../logger'; @@ -22,7 +23,7 @@ export class RocketChatIntegrationHandler { this.compiledScripts = {}; this.triggers = {}; - Models.Integrations.find({ type: 'webhook-outgoing' }).fetch().forEach((data) => this.addIntegration(data)); + Promise.await(Integrations.find({ type: 'webhook-outgoing' }).forEach((data) => this.addIntegration(data))); } addIntegration(record) { @@ -142,11 +143,11 @@ export class RocketChatIntegrationHandler { } if (historyId) { - Models.IntegrationHistory.update({ _id: historyId }, { $set: history }); + Promise.await(IntegrationHistory.updateOne({ _id: historyId }, { $set: history })); return historyId; } history._createdAt = new Date(); - return Models.IntegrationHistory.insert(Object.assign({ _id: Random.id() }, history)); + return Promise.await(IntegrationHistory.insertOne({ _id: Random.id(), ...history })); } // Trigger is the trigger, nameOrId is a string which is used to try and find a room, room is a room, message is a message, and data contains "user_name" if trigger.impersonateUser is truthful. @@ -715,7 +716,7 @@ export class RocketChatIntegrationHandler { if (result.statusCode === 410) { this.updateHistory({ historyId, step: 'after-process-http-status-410', error: true }); outgoingLogger.error(`Disabling the Integration "${ trigger.name }" because the status code was 401 (Gone).`); - Models.Integrations.update({ _id: trigger._id }, { $set: { enabled: false } }); + Promise.await(Integrations.updateOne({ _id: trigger._id }, { $set: { enabled: false } })); return; } diff --git a/app/integrations/server/methods/clearIntegrationHistory.js b/app/integrations/server/methods/clearIntegrationHistory.ts similarity index 70% rename from app/integrations/server/methods/clearIntegrationHistory.js rename to app/integrations/server/methods/clearIntegrationHistory.ts index 87eec581e37aa..f4ef3e974b961 100644 --- a/app/integrations/server/methods/clearIntegrationHistory.js +++ b/app/integrations/server/methods/clearIntegrationHistory.ts @@ -1,17 +1,20 @@ import { Meteor } from 'meteor/meteor'; -import { hasPermission } from '../../../authorization'; -import { IntegrationHistory, Integrations } from '../../../models'; +import { hasPermission } from '../../../authorization/server'; +import { IntegrationHistory, Integrations } from '../../../models/server/raw'; import notifications from '../../../notifications/server/lib/Notifications'; Meteor.methods({ - clearIntegrationHistory(integrationId) { + async clearIntegrationHistory(integrationId) { let integration; if (hasPermission(this.userId, 'manage-outgoing-integrations') || hasPermission(this.userId, 'manage-outgoing-integrations', 'bot')) { - integration = Integrations.findOne(integrationId); + integration = await Integrations.findOneById(integrationId); } else if (hasPermission(this.userId, 'manage-own-outgoing-integrations') || hasPermission(this.userId, 'manage-own-outgoing-integrations', 'bot')) { - integration = Integrations.findOne(integrationId, { fields: { '_createdBy._id': this.userId } }); + integration = await Integrations.findOne({ + _id: integrationId, + '_createdBy._id': this.userId, + }); } else { throw new Meteor.Error('not_authorized', 'Unauthorized', { method: 'clearIntegrationHistory' }); } @@ -20,7 +23,7 @@ Meteor.methods({ throw new Meteor.Error('error-invalid-integration', 'Invalid integration', { method: 'clearIntegrationHistory' }); } - IntegrationHistory.removeByIntegrationId(integrationId); + await IntegrationHistory.removeByIntegrationId(integrationId); notifications.streamIntegrationHistory.emit(integrationId, { type: 'removed' }); diff --git a/app/integrations/server/methods/incoming/addIncomingIntegration.js b/app/integrations/server/methods/incoming/addIncomingIntegration.js index 6e86dacd5700e..23b339ed48fb4 100644 --- a/app/integrations/server/methods/incoming/addIncomingIntegration.js +++ b/app/integrations/server/methods/incoming/addIncomingIntegration.js @@ -4,13 +4,14 @@ import { Babel } from 'meteor/babel-compiler'; import _ from 'underscore'; import s from 'underscore.string'; -import { hasPermission, hasAllPermission } from '../../../../authorization'; -import { Users, Rooms, Integrations, Roles, Subscriptions } from '../../../../models'; +import { hasPermission, hasAllPermission } from '../../../../authorization/server'; +import { Users, Rooms, Subscriptions } from '../../../../models/server'; +import { Integrations, Roles } from '../../../../models/server/raw'; const validChannelChars = ['@', '#']; Meteor.methods({ - addIncomingIntegration(integration) { + async addIncomingIntegration(integration) { if (!hasPermission(this.userId, 'manage-incoming-integrations') && !hasPermission(this.userId, 'manage-own-incoming-integrations')) { throw new Meteor.Error('not_authorized', 'Unauthorized', { method: 'addIncomingIntegration' }); } @@ -95,9 +96,11 @@ Meteor.methods({ integration._createdAt = new Date(); integration._createdBy = Users.findOne(this.userId, { fields: { username: 1 } }); - Roles.addUserRoles(user._id, 'bot'); + await Roles.addUserRoles(user._id, 'bot'); - integration._id = Integrations.insert(integration); + const result = await Integrations.insertOne(integration); + + integration._id = result.insertedId; return integration; }, diff --git a/app/integrations/server/methods/incoming/deleteIncomingIntegration.js b/app/integrations/server/methods/incoming/deleteIncomingIntegration.ts similarity index 56% rename from app/integrations/server/methods/incoming/deleteIncomingIntegration.js rename to app/integrations/server/methods/incoming/deleteIncomingIntegration.ts index 96c25116a10d2..bbd158f20ae0f 100644 --- a/app/integrations/server/methods/incoming/deleteIncomingIntegration.js +++ b/app/integrations/server/methods/incoming/deleteIncomingIntegration.ts @@ -1,16 +1,19 @@ import { Meteor } from 'meteor/meteor'; -import { hasPermission } from '../../../../authorization'; -import { Integrations } from '../../../../models'; +import { hasPermission } from '../../../../authorization/server'; +import { Integrations } from '../../../../models/server/raw'; Meteor.methods({ - deleteIncomingIntegration(integrationId) { + async deleteIncomingIntegration(integrationId) { let integration; if (hasPermission(this.userId, 'manage-incoming-integrations')) { - integration = Integrations.findOne(integrationId); + integration = Integrations.findOneById(integrationId); } else if (hasPermission(this.userId, 'manage-own-incoming-integrations')) { - integration = Integrations.findOne(integrationId, { fields: { '_createdBy._id': this.userId } }); + integration = Integrations.findOne({ + _id: integrationId, + '_createdBy._id': this.userId, + }); } else { throw new Meteor.Error('not_authorized', 'Unauthorized', { method: 'deleteIncomingIntegration' }); } @@ -19,7 +22,7 @@ Meteor.methods({ throw new Meteor.Error('error-invalid-integration', 'Invalid integration', { method: 'deleteIncomingIntegration' }); } - Integrations.remove({ _id: integrationId }); + await Integrations.removeById(integrationId); return true; }, diff --git a/app/integrations/server/methods/incoming/updateIncomingIntegration.js b/app/integrations/server/methods/incoming/updateIncomingIntegration.js index 5e7b3517ba0ed..fc5a6d384b951 100644 --- a/app/integrations/server/methods/incoming/updateIncomingIntegration.js +++ b/app/integrations/server/methods/incoming/updateIncomingIntegration.js @@ -3,13 +3,14 @@ import { Babel } from 'meteor/babel-compiler'; import _ from 'underscore'; import s from 'underscore.string'; -import { Integrations, Rooms, Users, Roles, Subscriptions } from '../../../../models'; -import { hasAllPermission, hasPermission } from '../../../../authorization'; +import { Rooms, Users, Subscriptions } from '../../../../models/server'; +import { Integrations, Roles } from '../../../../models/server/raw'; +import { hasAllPermission, hasPermission } from '../../../../authorization/server'; const validChannelChars = ['@', '#']; Meteor.methods({ - updateIncomingIntegration(integrationId, integration) { + async updateIncomingIntegration(integrationId, integration) { if (!_.isString(integration.channel) || integration.channel.trim() === '') { throw new Meteor.Error('error-invalid-channel', 'Invalid channel', { method: 'updateIncomingIntegration' }); } @@ -25,9 +26,9 @@ Meteor.methods({ let currentIntegration; if (hasPermission(this.userId, 'manage-incoming-integrations')) { - currentIntegration = Integrations.findOne(integrationId); + currentIntegration = await Integrations.findOneById(integrationId); } else if (hasPermission(this.userId, 'manage-own-incoming-integrations')) { - currentIntegration = Integrations.findOne({ _id: integrationId, '_createdBy._id': this.userId }); + currentIntegration = await Integrations.findOne({ _id: integrationId, '_createdBy._id': this.userId }); } else { throw new Meteor.Error('not_authorized', 'Unauthorized', { method: 'updateIncomingIntegration' }); } @@ -43,14 +44,14 @@ Meteor.methods({ integration.scriptCompiled = Babel.compile(integration.script, babelOptions).code; integration.scriptError = undefined; - Integrations.update(integrationId, { + await Integrations.updateOne({ _id: integrationId }, { $set: { scriptCompiled: integration.scriptCompiled }, $unset: { scriptError: 1 }, }); } catch (e) { integration.scriptCompiled = undefined; integration.scriptError = _.pick(e, 'name', 'message', 'stack'); - Integrations.update(integrationId, { + await Integrations.updateOne({ _id: integrationId }, { $set: { scriptError: integration.scriptError, }, @@ -100,9 +101,9 @@ Meteor.methods({ throw new Meteor.Error('error-invalid-post-as-user', 'Invalid Post As User', { method: 'updateIncomingIntegration' }); } - Roles.addUserRoles(user._id, 'bot'); + await Roles.addUserRoles(user._id, 'bot'); - Integrations.update(integrationId, { + await Integrations.updateOne({ _id: integrationId }, { $set: { enabled: integration.enabled, name: integration.name, @@ -117,6 +118,6 @@ Meteor.methods({ }, }); - return Integrations.findOne(integrationId); + return Integrations.findOneById(integrationId); }, }); diff --git a/app/integrations/server/methods/outgoing/addOutgoingIntegration.js b/app/integrations/server/methods/outgoing/addOutgoingIntegration.js index 5baf6e88cda45..ae6f1aa6933db 100644 --- a/app/integrations/server/methods/outgoing/addOutgoingIntegration.js +++ b/app/integrations/server/methods/outgoing/addOutgoingIntegration.js @@ -1,11 +1,12 @@ import { Meteor } from 'meteor/meteor'; -import { hasPermission } from '../../../../authorization'; -import { Users, Integrations } from '../../../../models'; +import { hasPermission } from '../../../../authorization/server'; +import { Users } from '../../../../models/server'; +import { Integrations } from '../../../../models/server/raw'; import { integrations } from '../../../lib/rocketchat'; Meteor.methods({ - addOutgoingIntegration(integration) { + async addOutgoingIntegration(integration) { if (!hasPermission(this.userId, 'manage-outgoing-integrations') && !hasPermission(this.userId, 'manage-own-outgoing-integrations') && !hasPermission(this.userId, 'manage-outgoing-integrations', 'bot') @@ -17,7 +18,9 @@ Meteor.methods({ integration._createdAt = new Date(); integration._createdBy = Users.findOne(this.userId, { fields: { username: 1 } }); - integration._id = Integrations.insert(integration); + + const result = await Integrations.insertOne(integration); + integration._id = result.insertedId; return integration; }, diff --git a/app/integrations/server/methods/outgoing/deleteOutgoingIntegration.js b/app/integrations/server/methods/outgoing/deleteOutgoingIntegration.ts similarity index 63% rename from app/integrations/server/methods/outgoing/deleteOutgoingIntegration.js rename to app/integrations/server/methods/outgoing/deleteOutgoingIntegration.ts index 07823b22bb2cb..a63e845eaa77a 100644 --- a/app/integrations/server/methods/outgoing/deleteOutgoingIntegration.js +++ b/app/integrations/server/methods/outgoing/deleteOutgoingIntegration.ts @@ -1,16 +1,19 @@ import { Meteor } from 'meteor/meteor'; -import { hasPermission } from '../../../../authorization'; -import { IntegrationHistory, Integrations } from '../../../../models'; +import { hasPermission } from '../../../../authorization/server'; +import { IntegrationHistory, Integrations } from '../../../../models/server/raw'; Meteor.methods({ - deleteOutgoingIntegration(integrationId) { + async deleteOutgoingIntegration(integrationId) { let integration; if (hasPermission(this.userId, 'manage-outgoing-integrations') || hasPermission(this.userId, 'manage-outgoing-integrations', 'bot')) { - integration = Integrations.findOne(integrationId); + integration = Integrations.findOneById(integrationId); } else if (hasPermission(this.userId, 'manage-own-outgoing-integrations') || hasPermission(this.userId, 'manage-own-outgoing-integrations', 'bot')) { - integration = Integrations.findOne(integrationId, { fields: { '_createdBy._id': this.userId } }); + integration = Integrations.findOne({ + _id: integrationId, + '_createdBy._id': this.userId, + }); } else { throw new Meteor.Error('not_authorized', 'Unauthorized', { method: 'deleteOutgoingIntegration' }); } @@ -19,8 +22,8 @@ Meteor.methods({ throw new Meteor.Error('error-invalid-integration', 'Invalid integration', { method: 'deleteOutgoingIntegration' }); } - Integrations.remove({ _id: integrationId }); - IntegrationHistory.removeByIntegrationId(integrationId); + await Integrations.removeById(integrationId); + await IntegrationHistory.removeByIntegrationId(integrationId); return true; }, diff --git a/app/integrations/server/methods/outgoing/replayOutgoingIntegration.js b/app/integrations/server/methods/outgoing/replayOutgoingIntegration.ts similarity index 69% rename from app/integrations/server/methods/outgoing/replayOutgoingIntegration.js rename to app/integrations/server/methods/outgoing/replayOutgoingIntegration.ts index 8d88cde3ea28d..bf3136525bc99 100644 --- a/app/integrations/server/methods/outgoing/replayOutgoingIntegration.js +++ b/app/integrations/server/methods/outgoing/replayOutgoingIntegration.ts @@ -1,17 +1,20 @@ import { Meteor } from 'meteor/meteor'; -import { hasPermission } from '../../../../authorization'; -import { Integrations, IntegrationHistory } from '../../../../models'; +import { hasPermission } from '../../../../authorization/server'; +import { Integrations, IntegrationHistory } from '../../../../models/server/raw'; import { triggerHandler } from '../../lib/triggerHandler'; Meteor.methods({ - replayOutgoingIntegration({ integrationId, historyId }) { + async replayOutgoingIntegration({ integrationId, historyId }) { let integration; if (hasPermission(this.userId, 'manage-outgoing-integrations') || hasPermission(this.userId, 'manage-outgoing-integrations', 'bot')) { - integration = Integrations.findOne(integrationId); + integration = await Integrations.findOneById(integrationId); } else if (hasPermission(this.userId, 'manage-own-outgoing-integrations') || hasPermission(this.userId, 'manage-own-outgoing-integrations', 'bot')) { - integration = Integrations.findOne(integrationId, { fields: { '_createdBy._id': this.userId } }); + integration = await Integrations.findOne({ + _id: integrationId, + '_createdBy._id': this.userId, + }); } else { throw new Meteor.Error('not_authorized', 'Unauthorized', { method: 'replayOutgoingIntegration' }); } @@ -20,7 +23,7 @@ Meteor.methods({ throw new Meteor.Error('error-invalid-integration', 'Invalid integration', { method: 'replayOutgoingIntegration' }); } - const history = IntegrationHistory.findOneByIntegrationIdAndHistoryId(integration._id, historyId); + const history = await IntegrationHistory.findOneByIntegrationIdAndHistoryId(integration._id, historyId); if (!history) { throw new Meteor.Error('error-invalid-integration-history', 'Invalid Integration History', { method: 'replayOutgoingIntegration' }); diff --git a/app/integrations/server/methods/outgoing/updateOutgoingIntegration.js b/app/integrations/server/methods/outgoing/updateOutgoingIntegration.js index 981a7890bc290..e9e4bc1ba9682 100644 --- a/app/integrations/server/methods/outgoing/updateOutgoingIntegration.js +++ b/app/integrations/server/methods/outgoing/updateOutgoingIntegration.js @@ -1,11 +1,12 @@ import { Meteor } from 'meteor/meteor'; -import { hasPermission } from '../../../../authorization'; -import { Integrations, Users } from '../../../../models'; +import { hasPermission } from '../../../../authorization/server'; +import { Users } from '../../../../models/server'; +import { Integrations } from '../../../../models/server/raw'; import { integrations } from '../../../lib/rocketchat'; Meteor.methods({ - updateOutgoingIntegration(integrationId, integration) { + async updateOutgoingIntegration(integrationId, integration) { integration = integrations.validateOutgoing(integration, this.userId); if (!integration.token || integration.token.trim() === '') { @@ -15,9 +16,9 @@ Meteor.methods({ let currentIntegration; if (hasPermission(this.userId, 'manage-outgoing-integrations')) { - currentIntegration = Integrations.findOne(integrationId); + currentIntegration = await Integrations.findOneById(integrationId); } else if (hasPermission(this.userId, 'manage-own-outgoing-integrations')) { - currentIntegration = Integrations.findOne({ _id: integrationId, '_createdBy._id': this.userId }); + currentIntegration = await Integrations.findOne({ _id: integrationId, '_createdBy._id': this.userId }); } else { throw new Meteor.Error('not_authorized', 'Unauthorized', { method: 'updateOutgoingIntegration' }); } @@ -26,18 +27,18 @@ Meteor.methods({ throw new Meteor.Error('invalid_integration', '[methods] updateOutgoingIntegration -> integration not found'); } if (integration.scriptCompiled) { - Integrations.update(integrationId, { + await Integrations.updateOne({ _id: integrationId }, { $set: { scriptCompiled: integration.scriptCompiled }, $unset: { scriptError: 1 }, }); } else { - Integrations.update(integrationId, { + await Integrations.updateOne({ _id: integrationId }, { $set: { scriptError: integration.scriptError }, $unset: { scriptCompiled: 1 }, }); } - Integrations.update(integrationId, { + await Integrations.updateOne({ _id: integrationId }, { $set: { event: integration.event, enabled: integration.enabled, @@ -65,6 +66,6 @@ Meteor.methods({ }, }); - return Integrations.findOne(integrationId); + return Integrations.findOneById(integrationId); }, }); diff --git a/app/invites/server/functions/findOrCreateInvite.js b/app/invites/server/functions/findOrCreateInvite.js index c875a53906d0a..3b608e7121e05 100644 --- a/app/invites/server/functions/findOrCreateInvite.js +++ b/app/invites/server/functions/findOrCreateInvite.js @@ -3,7 +3,8 @@ import { Random } from 'meteor/random'; import { hasPermission } from '../../../authorization'; import { Notifications } from '../../../notifications'; -import { Invites, Subscriptions, Rooms } from '../../../models/server'; +import { Subscriptions, Rooms } from '../../../models/server'; +import { Invites } from '../../../models/server/raw'; import { settings } from '../../../settings'; import { getURL } from '../../../utils/lib/getURL'; import { roomTypes, RoomMemberActions } from '../../../utils/server'; @@ -23,7 +24,7 @@ function getInviteUrl(invite) { const possibleDays = [0, 1, 7, 15, 30]; const possibleUses = [0, 1, 5, 10, 25, 50, 100]; -export const findOrCreateInvite = (userId, invite) => { +export const findOrCreateInvite = async (userId, invite) => { if (!userId || !invite) { return false; } @@ -57,7 +58,7 @@ export const findOrCreateInvite = (userId, invite) => { } // Before anything, let's check if there's an existing invite with the same settings for the same channel and user and that has not yet expired. - const existing = Invites.findOneByUserRoomMaxUsesAndExpiration(userId, invite.rid, maxUses, days); + const existing = await Invites.findOneByUserRoomMaxUsesAndExpiration(userId, invite.rid, maxUses, days); // If an existing invite was found, return it's _id instead of creating a new one. if (existing) { @@ -86,7 +87,7 @@ export const findOrCreateInvite = (userId, invite) => { uses: 0, }; - Invites.create(createInvite); + await Invites.insertOne(createInvite); Notifications.notifyUser(userId, 'updateInvites', { invite: createInvite }); createInvite.url = getInviteUrl(createInvite); diff --git a/app/invites/server/functions/listInvites.js b/app/invites/server/functions/listInvites.js index 476a5f729e092..10d67435237dc 100644 --- a/app/invites/server/functions/listInvites.js +++ b/app/invites/server/functions/listInvites.js @@ -1,9 +1,9 @@ import { Meteor } from 'meteor/meteor'; -import { hasPermission } from '../../../authorization'; -import { Invites } from '../../../models'; +import { hasPermission } from '../../../authorization/server'; +import { Invites } from '../../../models/server/raw'; -export const listInvites = (userId) => { +export const listInvites = async (userId) => { if (!userId) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'listInvites' }); } @@ -12,5 +12,5 @@ export const listInvites = (userId) => { throw new Meteor.Error('not_authorized'); } - return Invites.find({}).fetch(); + return Invites.find({}).toArray(); }; diff --git a/app/invites/server/functions/removeInvite.js b/app/invites/server/functions/removeInvite.js index eadbe67966b3b..0ea066a8e2879 100644 --- a/app/invites/server/functions/removeInvite.js +++ b/app/invites/server/functions/removeInvite.js @@ -1,9 +1,9 @@ import { Meteor } from 'meteor/meteor'; import { hasPermission } from '../../../authorization'; -import Invites from '../../../models/server/models/Invites'; +import { Invites } from '../../../models/server/raw'; -export const removeInvite = (userId, invite) => { +export const removeInvite = async (userId, invite) => { if (!userId || !invite) { return false; } @@ -17,13 +17,13 @@ export const removeInvite = (userId, invite) => { } // Before anything, let's check if there's an existing invite - const existing = Invites.findOneById(invite._id); + const existing = await Invites.findOneById(invite._id); if (!existing) { throw new Meteor.Error('invalid-invitation-id', 'Invalid Invitation _id', { method: 'removeInvite' }); } - Invites.removeById(invite._id); + await Invites.removeById(invite._id); return true; }; diff --git a/app/invites/server/functions/useInviteToken.js b/app/invites/server/functions/useInviteToken.js index 3cf638fd3e942..6fcef0a407883 100644 --- a/app/invites/server/functions/useInviteToken.js +++ b/app/invites/server/functions/useInviteToken.js @@ -1,11 +1,12 @@ import { Meteor } from 'meteor/meteor'; -import { Invites, Users, Subscriptions } from '../../../models/server'; +import { Users, Subscriptions } from '../../../models/server'; +import { Invites } from '../../../models/server/raw'; import { validateInviteToken } from './validateInviteToken'; import { addUserToRoom } from '../../../lib/server/functions/addUserToRoom'; import { roomTypes, RoomMemberActions } from '../../../utils/server'; -export const useInviteToken = (userId, token) => { +export const useInviteToken = async (userId, token) => { if (!userId) { throw new Meteor.Error('error-invalid-user', 'The user is invalid', { method: 'useInviteToken', field: 'userId' }); } @@ -14,7 +15,7 @@ export const useInviteToken = (userId, token) => { throw new Meteor.Error('error-invalid-token', 'The invite token is invalid.', { method: 'useInviteToken', field: 'token' }); } - const { inviteData, room } = validateInviteToken(token); + const { inviteData, room } = await validateInviteToken(token); if (!roomTypes.getConfig(room.t).allowMemberAction(room, RoomMemberActions.INVITE)) { throw new Meteor.Error('error-room-type-not-allowed', 'Can\'t join room of this type via invite', { method: 'useInviteToken', field: 'token' }); @@ -25,7 +26,7 @@ export const useInviteToken = (userId, token) => { const subscription = Subscriptions.findOneByRoomIdAndUserId(room._id, user._id, { fields: { _id: 1 } }); if (!subscription) { - Invites.increaseUsageById(inviteData._id); + await Invites.increaseUsageById(inviteData._id); } // If the user already has an username, then join the invite room, diff --git a/app/invites/server/functions/validateInviteToken.js b/app/invites/server/functions/validateInviteToken.js index dda8add8b6123..81febb4394423 100644 --- a/app/invites/server/functions/validateInviteToken.js +++ b/app/invites/server/functions/validateInviteToken.js @@ -1,13 +1,14 @@ import { Meteor } from 'meteor/meteor'; -import { Invites, Rooms } from '../../../models'; +import { Rooms } from '../../../models'; +import { Invites } from '../../../models/server/raw'; -export const validateInviteToken = (token) => { +export const validateInviteToken = async (token) => { if (!token || typeof token !== 'string') { throw new Meteor.Error('error-invalid-token', 'The invite token is invalid.', { method: 'validateInviteToken', field: 'token' }); } - const inviteData = Invites.findOneById(token); + const inviteData = await Invites.findOneById(token); if (!inviteData) { throw new Meteor.Error('error-invalid-token', 'The invite token is invalid.', { method: 'validateInviteToken', field: 'token' }); } diff --git a/app/lib/server/functions/deleteMessage.ts b/app/lib/server/functions/deleteMessage.ts index 8f698842e205e..96e563b1a4648 100644 --- a/app/lib/server/functions/deleteMessage.ts +++ b/app/lib/server/functions/deleteMessage.ts @@ -2,14 +2,15 @@ import { Meteor } from 'meteor/meteor'; import { FileUpload } from '../../../file-upload/server'; import { settings } from '../../../settings/server'; -import { Messages, Uploads, Rooms } from '../../../models/server'; +import { Messages, Rooms } from '../../../models/server'; +import { Uploads } from '../../../models/server/raw'; import { Notifications } from '../../../notifications/server'; import { callbacks } from '../../../callbacks/server'; import { Apps } from '../../../apps/server'; import { IMessage } from '../../../../definition/IMessage'; import { IUser } from '../../../../definition/IUser'; -export const deleteMessage = function(message: IMessage, user: IUser): void { +export const deleteMessage = async function(message: IMessage, user: IUser): Promise { const deletedMsg = Messages.findOneById(message._id); const isThread = deletedMsg.tcount > 0; const keepHistory = settings.get('Message_KeepHistory') || isThread; @@ -36,9 +37,9 @@ export const deleteMessage = function(message: IMessage, user: IUser): void { Messages.setHiddenById(message._id, true); } - files.forEach((file) => { - file?._id && Uploads.update(file._id, { $set: { _hidden: true } }); - }); + for await (const file of files) { + file?._id && await Uploads.update({ _id: file._id }, { $set: { _hidden: true } }); + } } else { if (!showDeletedStatus) { Messages.removeById(message._id); diff --git a/app/lib/server/functions/deleteUser.js b/app/lib/server/functions/deleteUser.js index 680517db54055..4193774e11364 100644 --- a/app/lib/server/functions/deleteUser.js +++ b/app/lib/server/functions/deleteUser.js @@ -2,7 +2,8 @@ import { Meteor } from 'meteor/meteor'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { FileUpload } from '../../../file-upload/server'; -import { Users, Subscriptions, Messages, Rooms, Integrations, FederationServers } from '../../../models/server'; +import { Users, Subscriptions, Messages, Rooms } from '../../../models/server'; +import { FederationServers, Integrations } from '../../../models/server/raw'; import { settings } from '../../../settings/server'; import { updateGroupDMsName } from './updateGroupDMsName'; import { relinquishRoomOwnerships } from './relinquishRoomOwnerships'; @@ -10,7 +11,7 @@ import { getSubscribedRoomsForUserWithDetails, shouldRemoveOrChangeOwner } from import { getUserSingleOwnedRooms } from './getUserSingleOwnedRooms'; import { api } from '../../../../server/sdk/api'; -export const deleteUser = function(userId, confirmRelinquish = false) { +export async function deleteUser(userId, confirmRelinquish = false) { const user = Users.findOneById(userId, { fields: { username: 1, avatarOrigin: 1, federation: 1 }, }); @@ -36,7 +37,7 @@ export const deleteUser = function(userId, confirmRelinquish = false) { // Users without username can't do anything, so there is nothing to remove if (user.username != null) { - relinquishRoomOwnerships(userId, subscribedRooms); + await relinquishRoomOwnerships(userId, subscribedRooms); const messageErasureType = settings.get('Message_ErasureType'); switch (messageErasureType) { @@ -64,7 +65,7 @@ export const deleteUser = function(userId, confirmRelinquish = false) { FileUpload.getStore('Avatars').deleteByName(user.username); } - Integrations.disableByUserId(userId); // Disables all the integrations which rely on the user being deleted. + await Integrations.disableByUserId(userId); // Disables all the integrations which rely on the user being deleted. api.broadcast('user.deleted', user); } @@ -75,5 +76,5 @@ export const deleteUser = function(userId, confirmRelinquish = false) { updateGroupDMsName(user); // Refresh the servers list - FederationServers.refreshServers(); -}; + await FederationServers.refreshServers(); +} diff --git a/app/lib/server/functions/relinquishRoomOwnerships.js b/app/lib/server/functions/relinquishRoomOwnerships.js index 7c56e3bc05a52..f5c403f1b2b14 100644 --- a/app/lib/server/functions/relinquishRoomOwnerships.js +++ b/app/lib/server/functions/relinquishRoomOwnerships.js @@ -1,5 +1,6 @@ import { FileUpload } from '../../../file-upload/server'; -import { Subscriptions, Messages, Rooms, Roles } from '../../../models/server'; +import { Subscriptions, Messages, Rooms } from '../../../models/server'; +import { Roles } from '../../../models/server/raw'; const bulkRoomCleanUp = (rids) => { // no bulk deletion for files @@ -12,11 +13,14 @@ const bulkRoomCleanUp = (rids) => { ])); }; -export const relinquishRoomOwnerships = function(userId, subscribedRooms, removeDirectMessages = true) { +export const relinquishRoomOwnerships = async function(userId, subscribedRooms, removeDirectMessages = true) { // change owners - subscribedRooms - .filter(({ shouldChangeOwner }) => shouldChangeOwner) - .forEach(({ newOwner, rid }) => Roles.addUserRoles(newOwner, ['owner'], rid)); + const changeOwner = subscribedRooms + .filter(({ shouldChangeOwner }) => shouldChangeOwner); + + for await (const { newOwner, rid } of changeOwner) { + await Roles.addUserRoles(newOwner, ['owner'], rid); + } const roomIdsToRemove = subscribedRooms.filter(({ shouldBeRemoved }) => shouldBeRemoved).map(({ rid }) => rid); diff --git a/app/lib/server/functions/setRoomAvatar.js b/app/lib/server/functions/setRoomAvatar.js index 9b0ea487c7583..540de27992019 100644 --- a/app/lib/server/functions/setRoomAvatar.js +++ b/app/lib/server/functions/setRoomAvatar.js @@ -2,13 +2,14 @@ import { Meteor } from 'meteor/meteor'; import { RocketChatFile } from '../../../file'; import { FileUpload } from '../../../file-upload'; -import { Rooms, Avatars, Messages } from '../../../models/server'; +import { Rooms, Messages } from '../../../models/server'; +import { Avatars } from '../../../models/server/raw'; import { api } from '../../../../server/sdk/api'; -export const setRoomAvatar = function(rid, dataURI, user) { +export const setRoomAvatar = async function(rid, dataURI, user) { const fileStore = FileUpload.getStore('Avatars'); - const current = Avatars.findOneByRoomId(rid); + const current = await Avatars.findOneByRoomId(rid); if (!dataURI) { fileStore.deleteByRoomId(rid); diff --git a/app/lib/server/functions/setUserActiveStatus.js b/app/lib/server/functions/setUserActiveStatus.js index 8089fbf7d1cf0..77a137695fdda 100644 --- a/app/lib/server/functions/setUserActiveStatus.js +++ b/app/lib/server/functions/setUserActiveStatus.js @@ -63,7 +63,7 @@ export function setUserActiveStatus(userId, active, confirmRelinquish = false) { } closeOmnichannelConversations(user, livechatSubscribedRooms); - relinquishRoomOwnerships(user, chatSubscribedRooms, false); + Promise.await(relinquishRoomOwnerships(user, chatSubscribedRooms, false)); } if (active && !user.active) { diff --git a/app/lib/server/functions/setUsername.js b/app/lib/server/functions/setUsername.js index 8795fb6b01fcf..97a05291f5d1b 100644 --- a/app/lib/server/functions/setUsername.js +++ b/app/lib/server/functions/setUsername.js @@ -3,7 +3,8 @@ import s from 'underscore.string'; import { Accounts } from 'meteor/accounts-base'; import { settings } from '../../../settings'; -import { Users, Invites } from '../../../models/server'; +import { Users } from '../../../models/server'; +import { Invites } from '../../../models/server/raw'; import { hasPermission } from '../../../authorization'; import { RateLimiter } from '../lib'; import { addUserToRoom } from './addUserToRoom'; @@ -70,7 +71,7 @@ export const _setUsername = function(userId, u, fullUser) { // If it's the first username and the user has an invite Token, then join the invite room if (!previousUsername && user.inviteToken) { - const inviteData = Invites.findOneById(user.inviteToken); + const inviteData = Promise.await(Invites.findOneById(user.inviteToken)); if (inviteData && inviteData.rid) { addUserToRoom(inviteData.rid, user); } diff --git a/app/lib/server/lib/getRoomRoles.js b/app/lib/server/lib/getRoomRoles.js index 9c3718628782a..6ed6527c53686 100644 --- a/app/lib/server/lib/getRoomRoles.js +++ b/app/lib/server/lib/getRoomRoles.js @@ -1,7 +1,8 @@ import _ from 'underscore'; import { settings } from '../../../settings'; -import { Subscriptions, Users, Roles } from '../../../models'; +import { Subscriptions, Users } from '../../../models'; +import { Roles } from '../../../models/server/raw'; export function getRoomRoles(rid) { const options = { @@ -17,7 +18,7 @@ export function getRoomRoles(rid) { const UI_Use_Real_Name = settings.get('UI_Use_Real_Name') === true; - const roles = Roles.find({ scope: 'Subscriptions', description: { $exists: 1, $ne: '' } }).fetch(); + const roles = Promise.await(Roles.find({ scope: 'Subscriptions', description: { $exists: 1, $ne: '' } }).toArray()); const subscriptions = Subscriptions.findByRoomIdAndRoles(rid, _.pluck(roles, '_id'), options).fetch(); if (!UI_Use_Real_Name) { diff --git a/app/lib/server/methods/deleteMessage.js b/app/lib/server/methods/deleteMessage.js index 086b9caee7f91..8be7da4e5e456 100644 --- a/app/lib/server/methods/deleteMessage.js +++ b/app/lib/server/methods/deleteMessage.js @@ -6,7 +6,7 @@ import { Messages } from '../../../models'; import { deleteMessage } from '../functions'; Meteor.methods({ - deleteMessage(message) { + async deleteMessage(message) { check(message, Match.ObjectIncluding({ _id: String, })); diff --git a/app/lib/server/methods/deleteUserOwnAccount.js b/app/lib/server/methods/deleteUserOwnAccount.js index 1ff7494a87516..2d7f269b08f75 100644 --- a/app/lib/server/methods/deleteUserOwnAccount.js +++ b/app/lib/server/methods/deleteUserOwnAccount.js @@ -9,7 +9,7 @@ import { Users } from '../../../models'; import { deleteUser } from '../functions'; Meteor.methods({ - deleteUserOwnAccount(password, confirmRelinquish) { + async deleteUserOwnAccount(password, confirmRelinquish) { check(password, String); if (!Meteor.userId()) { @@ -39,7 +39,7 @@ Meteor.methods({ throw new Meteor.Error('error-invalid-username', 'Invalid username', { method: 'deleteUserOwnAccount' }); } - deleteUser(userId, confirmRelinquish); + await deleteUser(userId, confirmRelinquish); return true; }, diff --git a/app/lib/server/methods/leaveRoom.js b/app/lib/server/methods/leaveRoom.ts similarity index 76% rename from app/lib/server/methods/leaveRoom.js rename to app/lib/server/methods/leaveRoom.ts index 561d7bdb548ac..cce12a25b8f84 100644 --- a/app/lib/server/methods/leaveRoom.js +++ b/app/lib/server/methods/leaveRoom.ts @@ -1,13 +1,14 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; -import { hasPermission, hasRole, getUsersInRole } from '../../../authorization'; -import { Subscriptions, Rooms } from '../../../models'; +import { hasPermission, hasRole } from '../../../authorization/server'; +import { Subscriptions, Rooms } from '../../../models/server'; import { removeUserFromRoom } from '../functions'; import { roomTypes, RoomMemberActions } from '../../../utils/server'; +import { Roles } from '../../../models/server/raw'; Meteor.methods({ - leaveRoom(rid) { + async leaveRoom(rid) { check(rid, String); if (!Meteor.userId()) { @@ -17,7 +18,7 @@ Meteor.methods({ const room = Rooms.findOneById(rid); const user = Meteor.user(); - if (!roomTypes.getConfig(room.t).allowMemberAction(room, RoomMemberActions.LEAVE)) { + if (!user || !roomTypes.getConfig(room.t).allowMemberAction(room, RoomMemberActions.LEAVE)) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'leaveRoom' }); } @@ -32,7 +33,8 @@ Meteor.methods({ // If user is room owner, check if there are other owners. If there isn't anyone else, warn user to set a new owner. if (hasRole(user._id, 'owner', room._id)) { - const numOwners = getUsersInRole('owner', room._id).count(); + const cursor = await Roles.findUsersInRole('owner', room._id); + const numOwners = Promise.await(cursor.count()); if (numOwners === 1) { throw new Meteor.Error('error-you-are-last-owner', 'You are the last owner. Please set new owner before leaving the room.', { method: 'leaveRoom' }); } diff --git a/app/lib/server/methods/refreshOAuthService.js b/app/lib/server/methods/refreshOAuthService.ts similarity index 66% rename from app/lib/server/methods/refreshOAuthService.js rename to app/lib/server/methods/refreshOAuthService.ts index e0ef565cb45e1..14dbeaa1fcd6f 100644 --- a/app/lib/server/methods/refreshOAuthService.js +++ b/app/lib/server/methods/refreshOAuthService.ts @@ -1,11 +1,11 @@ import { Meteor } from 'meteor/meteor'; import { ServiceConfiguration } from 'meteor/service-configuration'; -import { hasPermission } from '../../../authorization'; -import { Settings } from '../../../models'; +import { hasPermission } from '../../../authorization/server'; +import { Settings } from '../../../models/server/raw'; Meteor.methods({ - refreshOAuthService() { + async refreshOAuthService() { if (!Meteor.userId()) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'refreshOAuthService' }); } @@ -16,6 +16,6 @@ Meteor.methods({ ServiceConfiguration.configurations.remove({}); - Settings.update({ _id: /^(Accounts_OAuth_|SAML_|CAS_|Blockstack_).+/ }, { $set: { _updatedAt: new Date() } }, { multi: true }); + await Settings.update({ _id: /^(Accounts_OAuth_|SAML_|CAS_|Blockstack_).+/ }, { $set: { _updatedAt: new Date() } }, { multi: true }); }, }); diff --git a/app/lib/server/methods/removeOAuthService.js b/app/lib/server/methods/removeOAuthService.js deleted file mode 100644 index 5c271f3e75f0e..0000000000000 --- a/app/lib/server/methods/removeOAuthService.js +++ /dev/null @@ -1,51 +0,0 @@ -import { capitalize } from '@rocket.chat/string-helpers'; -import { Meteor } from 'meteor/meteor'; -import { check } from 'meteor/check'; - -import { hasPermission } from '../../../authorization'; -import { Settings } from '../../../models/server'; - -Meteor.methods({ - removeOAuthService(name) { - check(name, String); - - if (!Meteor.userId()) { - throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'removeOAuthService' }); - } - - if (hasPermission(Meteor.userId(), 'add-oauth-service') !== true) { - throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'removeOAuthService' }); - } - - name = name.toLowerCase().replace(/[^a-z0-9_]/g, ''); - name = capitalize(name); - Settings.removeById(`Accounts_OAuth_Custom-${ name }`); - Settings.removeById(`Accounts_OAuth_Custom-${ name }-url`); - Settings.removeById(`Accounts_OAuth_Custom-${ name }-token_path`); - Settings.removeById(`Accounts_OAuth_Custom-${ name }-identity_path`); - Settings.removeById(`Accounts_OAuth_Custom-${ name }-authorize_path`); - Settings.removeById(`Accounts_OAuth_Custom-${ name }-scope`); - Settings.removeById(`Accounts_OAuth_Custom-${ name }-access_token_param`); - Settings.removeById(`Accounts_OAuth_Custom-${ name }-token_sent_via`); - Settings.removeById(`Accounts_OAuth_Custom-${ name }-identity_token_sent_via`); - Settings.removeById(`Accounts_OAuth_Custom-${ name }-id`); - Settings.removeById(`Accounts_OAuth_Custom-${ name }-secret`); - Settings.removeById(`Accounts_OAuth_Custom-${ name }-button_label_text`); - Settings.removeById(`Accounts_OAuth_Custom-${ name }-button_label_color`); - Settings.removeById(`Accounts_OAuth_Custom-${ name }-button_color`); - Settings.removeById(`Accounts_OAuth_Custom-${ name }-login_style`); - Settings.removeById(`Accounts_OAuth_Custom-${ name }-key_field`); - Settings.removeById(`Accounts_OAuth_Custom-${ name }-username_field`); - Settings.removeById(`Accounts_OAuth_Custom-${ name }-email_field`); - Settings.removeById(`Accounts_OAuth_Custom-${ name }-name_field`); - Settings.removeById(`Accounts_OAuth_Custom-${ name }-avatar_field`); - Settings.removeById(`Accounts_OAuth_Custom-${ name }-roles_claim`); - Settings.removeById(`Accounts_OAuth_Custom-${ name }-merge_roles`); - Settings.removeById(`Accounts_OAuth_Custom-${ name }-merge_users`); - Settings.removeById(`Accounts_OAuth_Custom-${ name }-show_button`); - Settings.removeById(`Accounts_OAuth_Custom-${ name }-groups_claim`); - Settings.removeById(`Accounts_OAuth_Custom-${ name }-channels_admin`); - Settings.removeById(`Accounts_OAuth_Custom-${ name }-map_channels`); - Settings.removeById(`Accounts_OAuth_Custom-${ name }-groups_channel_map`); - }, -}); diff --git a/app/lib/server/methods/removeOAuthService.ts b/app/lib/server/methods/removeOAuthService.ts new file mode 100644 index 0000000000000..6b3f1ff535aec --- /dev/null +++ b/app/lib/server/methods/removeOAuthService.ts @@ -0,0 +1,54 @@ +import { capitalize } from '@rocket.chat/string-helpers'; +import { Meteor } from 'meteor/meteor'; +import { check } from 'meteor/check'; + +import { hasPermission } from '../../../authorization/server'; +import { Settings } from '../../../models/server/raw'; + + +Meteor.methods({ + async removeOAuthService(name) { + check(name, String); + + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'removeOAuthService' }); + } + + if (hasPermission(Meteor.userId(), 'add-oauth-service') !== true) { + throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'removeOAuthService' }); + } + + name = name.toLowerCase().replace(/[^a-z0-9_]/g, ''); + name = capitalize(name); + await Promise.all([ + Settings.removeById(`Accounts_OAuth_Custom-${ name }`), + Settings.removeById(`Accounts_OAuth_Custom-${ name }-url`), + Settings.removeById(`Accounts_OAuth_Custom-${ name }-token_path`), + Settings.removeById(`Accounts_OAuth_Custom-${ name }-identity_path`), + Settings.removeById(`Accounts_OAuth_Custom-${ name }-authorize_path`), + Settings.removeById(`Accounts_OAuth_Custom-${ name }-scope`), + Settings.removeById(`Accounts_OAuth_Custom-${ name }-access_token_param`), + Settings.removeById(`Accounts_OAuth_Custom-${ name }-token_sent_via`), + Settings.removeById(`Accounts_OAuth_Custom-${ name }-identity_token_sent_via`), + Settings.removeById(`Accounts_OAuth_Custom-${ name }-id`), + Settings.removeById(`Accounts_OAuth_Custom-${ name }-secret`), + Settings.removeById(`Accounts_OAuth_Custom-${ name }-button_label_text`), + Settings.removeById(`Accounts_OAuth_Custom-${ name }-button_label_color`), + Settings.removeById(`Accounts_OAuth_Custom-${ name }-button_color`), + Settings.removeById(`Accounts_OAuth_Custom-${ name }-login_style`), + Settings.removeById(`Accounts_OAuth_Custom-${ name }-key_field`), + Settings.removeById(`Accounts_OAuth_Custom-${ name }-username_field`), + Settings.removeById(`Accounts_OAuth_Custom-${ name }-email_field`), + Settings.removeById(`Accounts_OAuth_Custom-${ name }-name_field`), + Settings.removeById(`Accounts_OAuth_Custom-${ name }-avatar_field`), + Settings.removeById(`Accounts_OAuth_Custom-${ name }-roles_claim`), + Settings.removeById(`Accounts_OAuth_Custom-${ name }-merge_roles`), + Settings.removeById(`Accounts_OAuth_Custom-${ name }-merge_users`), + Settings.removeById(`Accounts_OAuth_Custom-${ name }-show_button`), + Settings.removeById(`Accounts_OAuth_Custom-${ name }-groups_claim`), + Settings.removeById(`Accounts_OAuth_Custom-${ name }-channels_admin`), + Settings.removeById(`Accounts_OAuth_Custom-${ name }-map_channels`), + Settings.removeById(`Accounts_OAuth_Custom-${ name }-groups_channel_map`), + ]); + }, +}); diff --git a/app/lib/server/methods/saveSetting.js b/app/lib/server/methods/saveSetting.js index b375b1ad5952c..993915db53f61 100644 --- a/app/lib/server/methods/saveSetting.js +++ b/app/lib/server/methods/saveSetting.js @@ -3,11 +3,11 @@ import { Match, check } from 'meteor/check'; import { hasPermission, hasAllPermission } from '../../../authorization/server'; import { getSettingPermissionId } from '../../../authorization/lib'; -import { Settings } from '../../../models'; import { twoFactorRequired } from '../../../2fa/server/twoFactorRequired'; +import { Settings } from '../../../models/server/raw'; Meteor.methods({ - saveSetting: twoFactorRequired(function(_id, value, editor) { + saveSetting: twoFactorRequired(async function(_id, value, editor) { const uid = Meteor.userId(); if (!uid) { throw new Meteor.Error('error-action-not-allowed', 'Editing settings is not allowed', { @@ -26,7 +26,7 @@ Meteor.methods({ // Verify the _id passed in is a string. check(_id, String); - const setting = Settings.db.findOneById(_id); + const setting = await Settings.findOneById(_id); // Verify the value is what it should be switch (setting.type) { @@ -44,7 +44,7 @@ Meteor.methods({ break; } - Settings.updateValueAndEditorById(_id, value, editor); + await Settings.updateValueAndEditorById(_id, value, editor); return true; }), }); diff --git a/app/lib/server/methods/saveSettings.js b/app/lib/server/methods/saveSettings.js index 6b99b3c7665cb..6da862bd9aa55 100644 --- a/app/lib/server/methods/saveSettings.js +++ b/app/lib/server/methods/saveSettings.js @@ -1,13 +1,13 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; -import { hasPermission } from '../../../authorization'; -import { Settings } from '../../../models'; +import { hasPermission } from '../../../authorization/server'; import { getSettingPermissionId } from '../../../authorization/lib'; import { twoFactorRequired } from '../../../2fa/server/twoFactorRequired'; +import { Settings } from '../../../models/server/raw'; Meteor.methods({ - saveSettings: twoFactorRequired(function(params = []) { + saveSettings: twoFactorRequired(async function(params = []) { const uid = Meteor.userId(); const settingsNotAllowed = []; if (uid === null) { @@ -18,16 +18,16 @@ Meteor.methods({ const editPrivilegedSetting = hasPermission(uid, 'edit-privileged-setting'); const manageSelectedSettings = hasPermission(uid, 'manage-selected-settings'); - params.forEach(({ _id, value }) => { + await Promise.all(params.map(async ({ _id, value }) => { // Verify the _id passed in is a string. check(_id, String); if (!editPrivilegedSetting && !(manageSelectedSettings && hasPermission(uid, getSettingPermissionId(_id)))) { return settingsNotAllowed.push(_id); } - const setting = Settings.db.findOneById(_id); + const setting = await Settings.findOneById(_id); // Verify the value is what it should be - switch (setting.type) { + switch (setting?.type) { case 'roomPick': check(value, Match.OneOf([Object], '')); break; @@ -44,7 +44,7 @@ Meteor.methods({ check(value, String); break; } - }); + })); if (settingsNotAllowed.length) { throw new Meteor.Error('error-action-not-allowed', 'Editing settings is not allowed', { @@ -53,8 +53,8 @@ Meteor.methods({ }); } - params.forEach(({ _id, value, editor }) => Settings.updateValueById(_id, value, editor)); + await Promise.all(params.map(({ _id, value, editor }) => Settings.updateValueById(_id, value, editor))); return true; - }), + }, {}), }); diff --git a/app/livechat/server/api/lib/livechat.js b/app/livechat/server/api/lib/livechat.js index a7a29598250f3..5720112b54638 100644 --- a/app/livechat/server/api/lib/livechat.js +++ b/app/livechat/server/api/lib/livechat.js @@ -1,7 +1,8 @@ import { Meteor } from 'meteor/meteor'; import { Random } from 'meteor/random'; -import { LivechatRooms, LivechatVisitors, LivechatDepartment, LivechatTrigger, EmojiCustom } from '../../../../models/server'; +import { LivechatRooms, LivechatVisitors, LivechatDepartment, LivechatTrigger } from '../../../../models/server'; +import { EmojiCustom } from '../../../../models/server/raw'; import { Livechat } from '../../lib/Livechat'; import { callbacks } from '../../../../callbacks/server'; import { normalizeAgent } from '../../lib/Helper'; @@ -86,12 +87,12 @@ export function normalizeHttpHeaderData(headers = {}) { const httpHeaders = Object.assign({}, headers); return { httpHeaders }; } -export function settings() { +export async function settings() { const initSettings = Livechat.getInitSettings(); const triggers = findTriggers(); const departments = findDepartments(); const sound = `${ Meteor.absoluteUrl() }sounds/chime.mp3`; - const emojis = EmojiCustom.find().fetch(); + const emojis = await EmojiCustom.find().toArray(); return { enabled: initSettings.Livechat_enabled, settings: { diff --git a/app/livechat/server/api/v1/config.js b/app/livechat/server/api/v1/config.js index e43509d4ba3ca..f1a49c1ed3955 100644 --- a/app/livechat/server/api/v1/config.js +++ b/app/livechat/server/api/v1/config.js @@ -17,7 +17,7 @@ API.v1.addRoute('livechat/config', { return API.v1.success({ config: { enabled: false } }); } - const config = settings(); + const config = Promise.await(settings()); const { token, department } = this.queryParams; const status = Livechat.online(department); diff --git a/app/livechat/server/api/v1/message.js b/app/livechat/server/api/v1/message.js index 178f571da4907..0fd39bc5d0867 100644 --- a/app/livechat/server/api/v1/message.js +++ b/app/livechat/server/api/v1/message.js @@ -108,7 +108,7 @@ API.v1.addRoute('livechat/message/:_id', { } if (message.file) { - message = normalizeMessageFileUpload(message); + message = Promise.await(normalizeMessageFileUpload(message)); } return API.v1.success({ message }); @@ -151,7 +151,7 @@ API.v1.addRoute('livechat/message/:_id', { if (result) { let message = Messages.findOneById(_id); if (message.file) { - message = normalizeMessageFileUpload(message); + message = Promise.await(normalizeMessageFileUpload(message)); } return API.v1.success({ message }); @@ -191,7 +191,7 @@ API.v1.addRoute('livechat/message/:_id', { throw new Meteor.Error('invalid-message'); } - const result = Livechat.deleteMessage({ guest, message }); + const result = Promise.await(Livechat.deleteMessage({ guest, message })); if (result) { return API.v1.success({ message: { @@ -251,7 +251,7 @@ API.v1.addRoute('livechat/messages.history/:rid', { const messages = loadMessageHistory({ userId: guest._id, rid, end, limit, ls, sort, offset, text }) .messages - .map(normalizeMessageFileUpload); + .map((...args) => Promise.await(normalizeMessageFileUpload(...args))); return API.v1.success({ messages }); } catch (e) { return API.v1.failure(e); diff --git a/app/livechat/server/api/v1/room.js b/app/livechat/server/api/v1/room.js index 5dcbf8cf1dd0f..88c9e47171105 100644 --- a/app/livechat/server/api/v1/room.js +++ b/app/livechat/server/api/v1/room.js @@ -166,7 +166,7 @@ API.v1.addRoute('livechat/room.survey', { throw new Meteor.Error('invalid-room'); } - const config = settings(); + const config = Promise.await(settings()); if (!config.survey || !config.survey.items || !config.survey.values) { throw new Meteor.Error('invalid-livechat-config'); } diff --git a/app/livechat/server/api/v1/videoCall.js b/app/livechat/server/api/v1/videoCall.js index 38b9c2d664919..7e30b10eadd73 100644 --- a/app/livechat/server/api/v1/videoCall.js +++ b/app/livechat/server/api/v1/videoCall.js @@ -35,7 +35,7 @@ API.v1.addRoute('livechat/video.call/:token', { }, }; const { room } = getRoom({ guest, rid, roomInfo }); - const config = settings(); + const config = Promise.await(settings()); if (!config.theme || !config.theme.actionLinks) { throw new Meteor.Error('invalid-livechat-config'); } diff --git a/app/livechat/server/hooks/saveAnalyticsData.js b/app/livechat/server/hooks/saveAnalyticsData.js index 4ca8832c153dc..96d336c228ca7 100644 --- a/app/livechat/server/hooks/saveAnalyticsData.js +++ b/app/livechat/server/hooks/saveAnalyticsData.js @@ -14,7 +14,7 @@ callbacks.add('afterSaveMessage', function(message, room) { } if (message.file) { - message = normalizeMessageFileUpload(message); + message = Promise.await(normalizeMessageFileUpload(message)); } const now = new Date(); diff --git a/app/livechat/server/hooks/sendToCRM.js b/app/livechat/server/hooks/sendToCRM.js index ac1ec663904bc..94e4cfcd22ebe 100644 --- a/app/livechat/server/hooks/sendToCRM.js +++ b/app/livechat/server/hooks/sendToCRM.js @@ -84,7 +84,7 @@ function sendToCRM(type, room, includeMessages = true) { } const { u } = message; - postData.messages.push(normalizeMessageFileUpload({ u, ...msg })); + postData.messages.push(Promise.await(normalizeMessageFileUpload({ u, ...msg }))); }); } diff --git a/app/livechat/server/hooks/sendToFacebook.js b/app/livechat/server/hooks/sendToFacebook.js index 1af4767e36568..7c1b00f312115 100644 --- a/app/livechat/server/hooks/sendToFacebook.js +++ b/app/livechat/server/hooks/sendToFacebook.js @@ -29,7 +29,7 @@ callbacks.add('afterSaveMessage', function(message, room) { } if (message.file) { - message = normalizeMessageFileUpload(message); + message = Promise.await(normalizeMessageFileUpload(message)); } OmniChannel.reply({ diff --git a/app/livechat/server/lib/Livechat.js b/app/livechat/server/lib/Livechat.js index aa2f93eb698d5..aa399564bfa5f 100644 --- a/app/livechat/server/lib/Livechat.js +++ b/app/livechat/server/lib/Livechat.js @@ -222,7 +222,7 @@ export const Livechat = { return true; }, - deleteMessage({ guest, message }) { + async deleteMessage({ guest, message }) { Livechat.logger.debug(`Attempting to delete a message by visitor ${ guest._id }`); check(message, Match.ObjectIncluding({ _id: String })); @@ -239,7 +239,7 @@ export const Livechat = { throw new Meteor.Error('error-action-not-allowed', 'Message deleting not allowed', { method: 'livechatDeleteMessage' }); } - deleteMessage(message, guest); + await deleteMessage(message, guest); return true; }, diff --git a/app/livechat/server/sendMessageBySMS.js b/app/livechat/server/sendMessageBySMS.js index 6094d1dc62a33..5e3bd1f133424 100644 --- a/app/livechat/server/sendMessageBySMS.js +++ b/app/livechat/server/sendMessageBySMS.js @@ -31,7 +31,7 @@ callbacks.add('afterSaveMessage', function(message, room) { let extraData; if (message.file) { - message = normalizeMessageFileUpload(message); + message = Promise.await(normalizeMessageFileUpload(message)); const { fileUpload, rid, u: { _id: userId } = {} } = message; extraData = Object.assign({}, { rid, userId, fileUpload }); } diff --git a/app/livechat/server/statistics/LivechatAgentActivityMonitor.js b/app/livechat/server/statistics/LivechatAgentActivityMonitor.js index 1066e112f027e..2c894afe4f1f9 100644 --- a/app/livechat/server/statistics/LivechatAgentActivityMonitor.js +++ b/app/livechat/server/statistics/LivechatAgentActivityMonitor.js @@ -3,7 +3,8 @@ import { Meteor } from 'meteor/meteor'; import { SyncedCron } from 'meteor/littledata:synced-cron'; import { callbacks } from '../../../callbacks/server'; -import { LivechatAgentActivity, Sessions, Users } from '../../../models/server'; +import { LivechatAgentActivity, Users } from '../../../models/server'; +import { Sessions } from '../../../models/server/raw'; const formatDate = (dateTime = new Date()) => ({ date: parseInt(moment(dateTime).format('YYYYMMDD')), @@ -12,7 +13,6 @@ const formatDate = (dateTime = new Date()) => ({ export class LivechatAgentActivityMonitor { constructor() { this._started = false; - this._handleMeteorConnection = this._handleMeteorConnection.bind(this); this._handleAgentStatusChanged = this._handleAgentStatusChanged.bind(this); this._handleUserStatusLivechatChanged = this._handleUserStatusLivechatChanged.bind(this); this._name = 'Livechat Agent Activity Monitor'; @@ -41,7 +41,7 @@ export class LivechatAgentActivityMonitor { return; } this._startMonitoring(); - Meteor.onConnection(this._handleMeteorConnection); + Meteor.onConnection((connection) => this._handleMeteorConnection(connection)); callbacks.add('livechat.agentStatusChanged', this._handleAgentStatusChanged); callbacks.add('livechat.setUserStatusLivechat', this._handleUserStatusLivechatChanged); this._started = true; @@ -75,12 +75,12 @@ export class LivechatAgentActivityMonitor { } } - _handleMeteorConnection(connection) { + async _handleMeteorConnection(connection) { if (!this.isRunning()) { return; } - const session = Sessions.findOne({ sessionId: connection.id }); + const session = await Sessions.findOne({ sessionId: connection.id }); if (!session) { return; } diff --git a/app/meteor-accounts-saml/server/lib/SAML.ts b/app/meteor-accounts-saml/server/lib/SAML.ts index 5de64393352a5..a0c60ff1d3a59 100644 --- a/app/meteor-accounts-saml/server/lib/SAML.ts +++ b/app/meteor-accounts-saml/server/lib/SAML.ts @@ -8,7 +8,8 @@ import fiber from 'fibers'; import { escapeRegExp, escapeHTML } from '@rocket.chat/string-helpers'; import { settings } from '../../../settings/server'; -import { Users, Rooms, CredentialTokens } from '../../../models/server'; +import { Users, Rooms } from '../../../models/server'; +import { CredentialTokens } from '../../../models/server/raw'; import { IUser } from '../../../../definition/IUser'; import { IIncomingMessage } from '../../../../definition/IIncomingMessage'; import { saveUserIdentity, createRoom, generateUsernameSuggestion, addUserToRoom } from '../../../lib/server/functions'; @@ -55,20 +56,20 @@ export class SAML { } } - public static hasCredential(credentialToken: string): boolean { - return CredentialTokens.findOneById(credentialToken) != null; + public static async hasCredential(credentialToken: string): Promise { + return await CredentialTokens.findOneNotExpiredById(credentialToken) != null; } - public static retrieveCredential(credentialToken: string): Record | undefined { + public static async retrieveCredential(credentialToken: string): Promise | undefined> { // The credentialToken in all these functions corresponds to SAMLs inResponseTo field and is mandatory to check. - const data = CredentialTokens.findOneById(credentialToken); + const data = await CredentialTokens.findOneNotExpiredById(credentialToken); if (data) { return data.userInfo; } } - public static storeCredential(credentialToken: string, loginResult: object): void { - CredentialTokens.create(credentialToken, loginResult); + public static async storeCredential(credentialToken: string, loginResult: {profile: Record}): Promise { + await CredentialTokens.create(credentialToken, loginResult); } public static insertOrUpdateSAMLUser(userObject: ISAMLUser): {userId: string; token: string} { @@ -380,7 +381,7 @@ export class SAML { private static processValidateAction(req: IIncomingMessage, res: ServerResponse, service: IServiceProviderOptions, _samlObject: ISAMLAction): void { const serviceProvider = new SAMLServiceProvider(service); SAMLUtils.relayState = req.body.RelayState; - serviceProvider.validateResponse(req.body.SAMLResponse, (err, profile/* , loggedOut*/) => { + serviceProvider.validateResponse(req.body.SAMLResponse, async (err, profile/* , loggedOut*/) => { try { if (err) { SAMLUtils.error(err); @@ -400,7 +401,7 @@ export class SAML { profile, }; - this.storeCredential(credentialToken, loginResult); + await this.storeCredential(credentialToken, loginResult); const url = `${ Meteor.absoluteUrl('home') }?saml_idp_credentialToken=${ credentialToken }`; res.writeHead(302, { Location: url, diff --git a/app/meteor-accounts-saml/server/loginHandler.ts b/app/meteor-accounts-saml/server/loginHandler.ts index 6b73c7f386ec3..edb58716d974d 100644 --- a/app/meteor-accounts-saml/server/loginHandler.ts +++ b/app/meteor-accounts-saml/server/loginHandler.ts @@ -17,7 +17,7 @@ Accounts.registerLoginHandler('saml', function(loginRequest) { return undefined; } - const loginResult = SAML.retrieveCredential(loginRequest.credentialToken); + const loginResult = Promise.await(SAML.retrieveCredential(loginRequest.credentialToken)); SAMLUtils.log({ msg: 'RESULT', loginResult }); if (!loginResult) { diff --git a/app/metrics/server/lib/collectMetrics.js b/app/metrics/server/lib/collectMetrics.js index 98267f9ca855f..f72437df2f047 100644 --- a/app/metrics/server/lib/collectMetrics.js +++ b/app/metrics/server/lib/collectMetrics.js @@ -10,7 +10,7 @@ import { Facts } from 'meteor/facts-base'; import { Info, getOplogInfo } from '../../../utils/server'; import { getControl } from '../../../../server/lib/migrations'; import { settings } from '../../../settings/server'; -import { Statistics } from '../../../models/server'; +import { Statistics } from '../../../models/server/raw'; import { SystemLogger } from '../../../../server/lib/logger/system'; import { metrics } from './metrics'; import { getAppsStatistics } from '../../../statistics/server/lib/getAppsStatistics'; @@ -42,7 +42,7 @@ const setPrometheusData = async () => { const oplogQueue = getOplogInfo().mongo._oplogHandle?._entryQueue?.length || 0; metrics.oplogQueue.set(oplogQueue); - const statistics = Statistics.findLast(); + const statistics = await Statistics.findLast(); if (!statistics) { return; } diff --git a/app/models/server/index.js b/app/models/server/index.js index 8c9094d654ef9..5507cf53f0aa9 100644 --- a/app/models/server/index.js +++ b/app/models/server/index.js @@ -1,31 +1,11 @@ import { Base } from './models/_Base'; import { BaseDb } from './models/_BaseDb'; -import Avatars from './models/Avatars'; -import ExportOperations from './models/ExportOperations'; import Messages from './models/Messages'; -import Reports from './models/Reports'; import Rooms from './models/Rooms'; import Settings from './models/Settings'; import Subscriptions from './models/Subscriptions'; -import Uploads from './models/Uploads'; -import UserDataFiles from './models/UserDataFiles'; import Users from './models/Users'; -import Sessions from './models/Sessions'; -import Statistics from './models/Statistics'; -import Permissions from './models/Permissions'; -import Roles from './models/Roles'; -import CustomSounds from './models/CustomSounds'; -import CustomUserStatus from './models/CustomUserStatus'; import Imports from './models/Imports'; -import Integrations from './models/Integrations'; -import IntegrationHistory from './models/IntegrationHistory'; -import Invites from './models/Invites'; -import CredentialTokens from './models/CredentialTokens'; -import EmojiCustom from './models/EmojiCustom'; -import OAuthApps from './models/OAuthApps'; -import OEmbedCache from './models/OEmbedCache'; -import SmarshHistory from './models/SmarshHistory'; -import WebdavAccounts from './models/WebdavAccounts'; import LivechatCustomField from './models/LivechatCustomField'; import LivechatDepartment from './models/LivechatDepartment'; import LivechatDepartmentAgents from './models/LivechatDepartmentAgents'; @@ -35,50 +15,24 @@ import LivechatTrigger from './models/LivechatTrigger'; import LivechatVisitors from './models/LivechatVisitors'; import LivechatAgentActivity from './models/LivechatAgentActivity'; import LivechatInquiry from './models/LivechatInquiry'; -import ReadReceipts from './models/ReadReceipts'; import LivechatExternalMessage from './models/LivechatExternalMessages'; import OmnichannelQueue from './models/OmnichannelQueue'; -import Analytics from './models/Analytics'; -import EmailInbox from './models/EmailInbox'; import ImportData from './models/ImportData'; export { AppsLogsModel } from './models/apps-logs-model'; export { AppsPersistenceModel } from './models/apps-persistence-model'; export { AppsModel } from './models/apps-model'; -export { FederationDNSCache } from './models/FederationDNSCache'; export { FederationRoomEvents } from './models/FederationRoomEvents'; -export { FederationKeys } from './models/FederationKeys'; -export { FederationServers } from './models/FederationServers'; export { Base, BaseDb, - Avatars, - ExportOperations, Messages, - Reports, Rooms, Settings, Subscriptions, - Uploads, - UserDataFiles, Users, - Sessions, - Statistics, - Permissions, - Roles, - CustomSounds, - CustomUserStatus, Imports, - Integrations, - IntegrationHistory, - Invites, - CredentialTokens, - EmojiCustom, - OAuthApps, - OEmbedCache, - SmarshHistory, - WebdavAccounts, LivechatCustomField, LivechatDepartment, LivechatDepartmentAgents, @@ -87,11 +41,8 @@ export { LivechatTrigger, LivechatVisitors, LivechatAgentActivity, - ReadReceipts, LivechatExternalMessage, LivechatInquiry, - Analytics, OmnichannelQueue, - EmailInbox, ImportData, }; diff --git a/app/models/server/models/Analytics.js b/app/models/server/models/Analytics.js deleted file mode 100644 index c521fda8923ed..0000000000000 --- a/app/models/server/models/Analytics.js +++ /dev/null @@ -1,11 +0,0 @@ -import { Base } from './_Base'; - -export class Analytics extends Base { - constructor() { - super('analytics'); - this.tryEnsureIndex({ date: 1 }); - this.tryEnsureIndex({ 'room._id': 1, date: 1 }, { unique: true }); - } -} - -export default new Analytics(); diff --git a/app/models/server/models/Avatars.js b/app/models/server/models/Avatars.js deleted file mode 100644 index b0e7e8cb5da1e..0000000000000 --- a/app/models/server/models/Avatars.js +++ /dev/null @@ -1,92 +0,0 @@ -import _ from 'underscore'; -import s from 'underscore.string'; -import { InstanceStatus } from 'meteor/konecty:multiple-instances-status'; - -import { Base } from './_Base'; - -export class Avatars extends Base { - constructor() { - super('avatars'); - - this.model.before.insert((userId, doc) => { - doc.instanceId = InstanceStatus.id(); - }); - - this.tryEnsureIndex({ name: 1 }, { sparse: true }); - this.tryEnsureIndex({ rid: 1 }, { sparse: true }); - } - - insertAvatarFileInit(name, userId, store, file, extra) { - const fileData = { - _id: name, - name, - userId, - store, - complete: false, - uploading: true, - progress: 0, - extension: s.strRightBack(file.name, '.'), - uploadedAt: new Date(), - }; - - _.extend(fileData, file, extra); - - return this.insertOrUpsert(fileData); - } - - updateFileComplete(fileId, userId, file) { - if (!fileId) { - return; - } - - const filter = { - _id: fileId, - userId, - }; - - const update = { - $set: { - complete: true, - uploading: false, - progress: 1, - }, - }; - - update.$set = _.extend(file, update.$set); - - if (this.model.direct && this.model.direct.update) { - return this.model.direct.update(filter, update); - } - return this.update(filter, update); - } - - findOneByName(name) { - return this.findOne({ name }); - } - - findOneByRoomId(rid) { - return this.findOne({ rid }); - } - - updateFileNameById(fileId, name) { - const filter = { _id: fileId }; - const update = { - $set: { - name, - }, - }; - if (this.model.direct && this.model.direct.update) { - return this.model.direct.update(filter, update); - } - return this.update(filter, update); - } - - deleteFile(fileId) { - if (this.model.direct && this.model.direct.remove) { - return this.model.direct.remove({ _id: fileId }); - } - return this.remove({ _id: fileId }); - } -} - -export default new Avatars(); diff --git a/app/models/server/models/CredentialTokens.js b/app/models/server/models/CredentialTokens.js deleted file mode 100644 index 7659538e032eb..0000000000000 --- a/app/models/server/models/CredentialTokens.js +++ /dev/null @@ -1,32 +0,0 @@ -import { Base } from './_Base'; - -export class CredentialTokens extends Base { - constructor() { - super('credential_tokens'); - - this.tryEnsureIndex({ expireAt: 1 }, { sparse: 1, expireAfterSeconds: 0 }); - } - - create(_id, userInfo) { - const validForMilliseconds = 60000; // Valid for 60 seconds - const token = { - _id, - userInfo, - expireAt: new Date(Date.now() + validForMilliseconds), - }; - - this.insert(token); - return token; - } - - findOneById(_id) { - const query = { - _id, - expireAt: { $gt: new Date() }, - }; - - return this.findOne(query); - } -} - -export default new CredentialTokens(); diff --git a/app/models/server/models/CustomSounds.js b/app/models/server/models/CustomSounds.js deleted file mode 100644 index b9971b9542298..0000000000000 --- a/app/models/server/models/CustomSounds.js +++ /dev/null @@ -1,56 +0,0 @@ -import { Base } from './_Base'; - -class CustomSounds extends Base { - constructor() { - super('custom_sounds'); - - this.tryEnsureIndex({ name: 1 }); - } - - // find one - findOneById(_id, options) { - return this.findOne(_id, options); - } - - // find - findByName(name, options) { - const query = { - name, - }; - - return this.find(query, options); - } - - findByNameExceptId(name, except, options) { - const query = { - _id: { $nin: [except] }, - name, - }; - - return this.find(query, options); - } - - // update - setName(_id, name) { - const update = { - $set: { - name, - }, - }; - - return this.update({ _id }, update); - } - - // INSERT - create(data) { - return this.insert(data); - } - - - // REMOVE - removeById(_id) { - return this.remove(_id); - } -} - -export default new CustomSounds(); diff --git a/app/models/server/models/CustomUserStatus.js b/app/models/server/models/CustomUserStatus.js deleted file mode 100644 index eb3a586da6ba1..0000000000000 --- a/app/models/server/models/CustomUserStatus.js +++ /dev/null @@ -1,71 +0,0 @@ -import { Base } from './_Base'; - -class CustomUserStatus extends Base { - constructor() { - super('custom_user_status'); - - this.tryEnsureIndex({ name: 1 }); - } - - // find one - findOneById(_id, options) { - return this.findOne(_id, options); - } - - // find one by name - findOneByName(name, options) { - return this.findOne({ name }, options); - } - - // find - findByName(name, options) { - const query = { - name, - }; - - return this.find(query, options); - } - - findByNameExceptId(name, except, options) { - const query = { - _id: { $nin: [except] }, - name, - }; - - return this.find(query, options); - } - - // update - setName(_id, name) { - const update = { - $set: { - name, - }, - }; - - return this.update({ _id }, update); - } - - setStatusType(_id, statusType) { - const update = { - $set: { - statusType, - }, - }; - - return this.update({ _id }, update); - } - - // INSERT - create(data) { - return this.insert(data); - } - - - // REMOVE - removeById(_id) { - return this.remove(_id); - } -} - -export default new CustomUserStatus(); diff --git a/app/models/server/models/EmailInbox.js b/app/models/server/models/EmailInbox.js deleted file mode 100644 index 490628be33837..0000000000000 --- a/app/models/server/models/EmailInbox.js +++ /dev/null @@ -1,27 +0,0 @@ -import { Base } from './_Base'; - -export class EmailInbox extends Base { - constructor() { - super('email_inbox'); - - this.tryEnsureIndex({ email: 1 }, { unique: true }); - } - - findOneById(_id, options) { - return this.findOne(_id, options); - } - - create(data) { - return this.insert(data); - } - - updateById(_id, data) { - return this.update({ _id }, data); - } - - removeById(_id) { - return this.remove(_id); - } -} - -export default new EmailInbox(); diff --git a/app/models/server/models/EmojiCustom.js b/app/models/server/models/EmojiCustom.js deleted file mode 100644 index d0cd7d7bc4cba..0000000000000 --- a/app/models/server/models/EmojiCustom.js +++ /dev/null @@ -1,91 +0,0 @@ -import { Base } from './_Base'; - -class EmojiCustom extends Base { - constructor() { - super('custom_emoji'); - - this.tryEnsureIndex({ name: 1 }); - this.tryEnsureIndex({ aliases: 1 }); - this.tryEnsureIndex({ extension: 1 }); - } - - // find one - findOneById(_id, options) { - return this.findOne(_id, options); - } - - // find - findByNameOrAlias(emojiName, options) { - let name = emojiName; - - if (typeof emojiName === 'string') { - name = emojiName.replace(/:/g, ''); - } - - const query = { - $or: [ - { name }, - { aliases: name }, - ], - }; - - return this.find(query, options); - } - - findByNameOrAliasExceptID(name, except, options) { - const query = { - _id: { $nin: [except] }, - $or: [ - { name }, - { aliases: name }, - ], - }; - - return this.find(query, options); - } - - - // update - setName(_id, name) { - const update = { - $set: { - name, - }, - }; - - return this.update({ _id }, update); - } - - setAliases(_id, aliases) { - const update = { - $set: { - aliases, - }, - }; - - return this.update({ _id }, update); - } - - setExtension(_id, extension) { - const update = { - $set: { - extension, - }, - }; - - return this.update({ _id }, update); - } - - // INSERT - create(data) { - return this.insert(data); - } - - - // REMOVE - removeById(_id) { - return this.remove(_id); - } -} - -export default new EmojiCustom(); diff --git a/app/models/server/models/ExportOperations.js b/app/models/server/models/ExportOperations.js deleted file mode 100644 index fb70d38925cab..0000000000000 --- a/app/models/server/models/ExportOperations.js +++ /dev/null @@ -1,108 +0,0 @@ -import _ from 'underscore'; - -import { Base } from './_Base'; - -export class ExportOperations extends Base { - constructor() { - super('export_operations'); - - this.tryEnsureIndex({ userId: 1 }); - this.tryEnsureIndex({ status: 1 }); - } - - // FIND - findById(id) { - const query = { _id: id }; - - return this.find(query); - } - - findLastOperationByUser(userId, fullExport = false, options = {}) { - const query = { - userId, - fullExport, - }; - - options.sort = { createdAt: -1 }; - return this.findOne(query, options); - } - - findPendingByUser(userId, options) { - const query = { - userId, - status: { - $nin: ['completed', 'skipped'], - }, - }; - - return this.find(query, options); - } - - findAllPending(options) { - const query = { - status: { $nin: ['completed', 'skipped'] }, - }; - - return this.find(query, options); - } - - findOnePending(options) { - const query = { - status: { $nin: ['completed', 'skipped'] }, - }; - - return this.findOne(query, options); - } - - findAllPendingBeforeMyRequest(requestDay, options) { - const query = { - status: { $nin: ['completed', 'skipped'] }, - createdAt: { $lt: requestDay }, - }; - - return this.find(query, options); - } - - // UPDATE - updateOperation(data) { - const update = { - $set: { - roomList: data.roomList, - status: data.status, - fileList: data.fileList, - generatedFile: data.generatedFile, - fileId: data.fileId, - userNameTable: data.userNameTable, - userData: data.userData, - generatedUserFile: data.generatedUserFile, - generatedAvatar: data.generatedAvatar, - exportPath: data.exportPath, - assetsPath: data.assetsPath, - }, - }; - - return this.update(data._id, update); - } - - - // INSERT - create(data) { - const exportOperation = { - createdAt: new Date(), - }; - - _.extend(exportOperation, data); - - this.insert(exportOperation); - - return exportOperation._id; - } - - - // REMOVE - removeById(_id) { - return this.remove(_id); - } -} - -export default new ExportOperations(); diff --git a/app/models/server/models/FederationDNSCache.js b/app/models/server/models/FederationDNSCache.js deleted file mode 100644 index 155deed53b956..0000000000000 --- a/app/models/server/models/FederationDNSCache.js +++ /dev/null @@ -1,13 +0,0 @@ -import { Base } from './_Base'; - -class FederationDNSCacheModel extends Base { - constructor() { - super('federation_dns_cache'); - } - - findOneByDomain(domain) { - return this.findOne({ domain }); - } -} - -export const FederationDNSCache = new FederationDNSCacheModel(); diff --git a/app/models/server/models/FederationKeys.js b/app/models/server/models/FederationKeys.js deleted file mode 100644 index 188f7cdc434e4..0000000000000 --- a/app/models/server/models/FederationKeys.js +++ /dev/null @@ -1,58 +0,0 @@ -import NodeRSA from 'node-rsa'; - -import { Base } from './_Base'; - -class FederationKeysModel extends Base { - constructor() { - super('federation_keys'); - } - - getKey(type) { - const keyResource = this.findOne({ type }); - - if (!keyResource) { return null; } - - return keyResource.key; - } - - loadKey(keyData, type) { - return new NodeRSA(keyData, `pkcs8-${ type }-pem`); - } - - generateKeys() { - const key = new NodeRSA({ b: 512 }); - - key.generateKeyPair(); - - this.update({ type: 'private' }, { type: 'private', key: key.exportKey('pkcs8-private-pem').replace(/\n|\r/g, '') }, { upsert: true }); - - this.update({ type: 'public' }, { type: 'public', key: key.exportKey('pkcs8-public-pem').replace(/\n|\r/g, '') }, { upsert: true }); - - return { - privateKey: this.getPrivateKey(), - publicKey: this.getPublicKey(), - }; - } - - getPrivateKey() { - const keyData = this.getKey('private'); - - return keyData && this.loadKey(keyData, 'private'); - } - - getPrivateKeyString() { - return this.getKey('private'); - } - - getPublicKey() { - const keyData = this.getKey('public'); - - return keyData && this.loadKey(keyData, 'public'); - } - - getPublicKeyString() { - return this.getKey('public'); - } -} - -export const FederationKeys = new FederationKeysModel(); diff --git a/app/models/server/models/FederationServers.js b/app/models/server/models/FederationServers.js deleted file mode 100644 index 9daf20d5a1284..0000000000000 --- a/app/models/server/models/FederationServers.js +++ /dev/null @@ -1,26 +0,0 @@ -import { Base } from './_Base'; -import { Users } from '../raw'; - -class FederationServersModel extends Base { - constructor() { - super('federation_servers'); - - this.tryEnsureIndex({ domain: 1 }); - } - - async refreshServers() { - const domains = await Users.getDistinctFederationDomains(); - - domains.forEach((domain) => { - this.update({ domain }, { - $setOnInsert: { - domain, - }, - }, { upsert: true }); - }); - - this.remove({ domain: { $nin: domains } }); - } -} - -export const FederationServers = new FederationServersModel(); diff --git a/app/models/server/models/InstanceStatus.js b/app/models/server/models/InstanceStatus.js deleted file mode 100644 index 344381e442663..0000000000000 --- a/app/models/server/models/InstanceStatus.js +++ /dev/null @@ -1,7 +0,0 @@ -import { InstanceStatus } from 'meteor/konecty:multiple-instances-status'; - -import { Base } from './_Base'; - -export class InstanceStatusModel extends Base {} - -export default new InstanceStatusModel(InstanceStatus.getCollection(), { preventSetUpdatedAt: true }); diff --git a/app/models/server/models/IntegrationHistory.js b/app/models/server/models/IntegrationHistory.js deleted file mode 100644 index 817deae0d789a..0000000000000 --- a/app/models/server/models/IntegrationHistory.js +++ /dev/null @@ -1,43 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { Base } from './_Base'; - -export class IntegrationHistory extends Base { - constructor() { - super('integration_history'); - } - - findByType(type, options) { - if (type !== 'outgoing-webhook' || type !== 'incoming-webhook') { - throw new Meteor.Error('invalid-integration-type'); - } - - return this.find({ type }, options); - } - - findByIntegrationId(id, options) { - return this.find({ 'integration._id': id }, options); - } - - findByIntegrationIdAndCreatedBy(id, creatorId, options) { - return this.find({ 'integration._id': id, 'integration._createdBy._id': creatorId }, options); - } - - findOneByIntegrationIdAndHistoryId(integrationId, historyId) { - return this.findOne({ 'integration._id': integrationId, _id: historyId }); - } - - findByEventName(event, options) { - return this.find({ event }, options); - } - - findFailed(options) { - return this.find({ error: true }, options); - } - - removeByIntegrationId(integrationId) { - return this.remove({ 'integration._id': integrationId }); - } -} - -export default new IntegrationHistory(); diff --git a/app/models/server/models/Integrations.js b/app/models/server/models/Integrations.js deleted file mode 100644 index ffbf40c1dcce4..0000000000000 --- a/app/models/server/models/Integrations.js +++ /dev/null @@ -1,31 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { Base } from './_Base'; - -export class Integrations extends Base { - constructor() { - super('integrations'); - - this.tryEnsureIndex({ type: 1 }); - } - - findByType(type, options) { - if (type !== 'webhook-incoming' && type !== 'webhook-outgoing') { - throw new Meteor.Error('invalid-type-to-find'); - } - - return this.find({ type }, options); - } - - disableByUserId(userId) { - return this.update({ userId }, { $set: { enabled: false } }, { multi: true }); - } - - updateRoomName(oldRoomName, newRoomName) { - const hashedOldRoomName = `#${ oldRoomName }`; - const hashedNewRoomName = `#${ newRoomName }`; - return this.update({ channel: hashedOldRoomName }, { $set: { 'channel.$': hashedNewRoomName } }, { multi: true }); - } -} - -export default new Integrations(); diff --git a/app/models/server/models/Invites.js b/app/models/server/models/Invites.js deleted file mode 100644 index 517c1780f0ba1..0000000000000 --- a/app/models/server/models/Invites.js +++ /dev/null @@ -1,51 +0,0 @@ -import { Base } from './_Base'; - -class Invites extends Base { - constructor() { - super('invites'); - } - - findOneByUserRoomMaxUsesAndExpiration(userId, rid, maxUses, daysToExpire) { - const query = { - rid, - userId, - days: daysToExpire, - maxUses, - }; - - if (daysToExpire > 0) { - query.expires = { - $gt: new Date(), - }; - } - - if (maxUses > 0) { - query.uses = { - $lt: maxUses, - }; - } - - return this.findOne(query); - } - - // INSERT - create(data) { - return this.insert(data); - } - - // REMOVE - removeById(_id) { - return this.remove({ _id }); - } - - // UPDATE - increaseUsageById(_id, uses = 1) { - return this.update({ _id }, { - $inc: { - uses, - }, - }); - } -} - -export default new Invites(); diff --git a/app/models/server/models/NotificationQueue.js b/app/models/server/models/NotificationQueue.js deleted file mode 100644 index 32eb7524c2c29..0000000000000 --- a/app/models/server/models/NotificationQueue.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Base } from './_Base'; - -export class NotificationQueue extends Base { - constructor() { - super('notification_queue'); - this.tryEnsureIndex({ uid: 1 }); - this.tryEnsureIndex({ ts: 1 }, { expireAfterSeconds: 2 * 60 * 60 }); - this.tryEnsureIndex({ schedule: 1 }, { sparse: true }); - this.tryEnsureIndex({ sending: 1 }, { sparse: true }); - this.tryEnsureIndex({ error: 1 }, { sparse: true }); - } -} - -export default new NotificationQueue(); diff --git a/app/models/server/models/OAuthApps.js b/app/models/server/models/OAuthApps.js deleted file mode 100644 index 6aedffb63ae07..0000000000000 --- a/app/models/server/models/OAuthApps.js +++ /dev/null @@ -1,9 +0,0 @@ -import { Base } from './_Base'; - -export class OAuthApps extends Base { - constructor() { - super('oauth_apps'); - } -} - -export default new OAuthApps(); diff --git a/app/models/server/models/OEmbedCache.js b/app/models/server/models/OEmbedCache.js deleted file mode 100644 index db4383b9cd34c..0000000000000 --- a/app/models/server/models/OEmbedCache.js +++ /dev/null @@ -1,39 +0,0 @@ -import { Base } from './_Base'; - -export class OEmbedCache extends Base { - constructor() { - super('oembed_cache'); - this.tryEnsureIndex({ updatedAt: 1 }); - } - - // FIND ONE - findOneById(_id, options) { - const query = { - _id, - }; - return this.findOne(query, options); - } - - // INSERT - createWithIdAndData(_id, data) { - const record = { - _id, - data, - updatedAt: new Date(), - }; - record._id = this.insert(record); - return record; - } - - // REMOVE - removeAfterDate(date) { - const query = { - updatedAt: { - $lte: date, - }, - }; - return this.remove(query); - } -} - -export default new OEmbedCache(); diff --git a/app/models/server/models/Permissions.js b/app/models/server/models/Permissions.js deleted file mode 100644 index 009f29d37f9df..0000000000000 --- a/app/models/server/models/Permissions.js +++ /dev/null @@ -1,49 +0,0 @@ -import { Base } from './_Base'; - -export class Permissions extends Base { - // FIND - findByRole(role, options) { - const query = { - roles: role, - }; - - return this.find(query, options); - } - - findOneById(_id) { - return this.findOne({ _id }); - } - - createOrUpdate(name, roles) { - const exists = this.findOne({ - _id: name, - roles, - }, { fields: { _id: 1 } }); - - if (exists) { - return exists._id; - } - - this.upsert({ _id: name }, { $set: { roles } }); - } - - create(name, roles) { - const exists = this.findOneById(name, { fields: { _id: 1 } }); - - if (exists) { - return exists._id; - } - - this.upsert({ _id: name }, { $set: { roles } }); - } - - addRole(permission, role) { - this.update({ _id: permission, roles: { $ne: role } }, { $addToSet: { roles: role } }); - } - - removeRole(permission, role) { - this.update({ _id: permission, roles: role }, { $pull: { roles: role } }); - } -} - -export default new Permissions('permissions'); diff --git a/app/models/server/models/ReadReceipts.js b/app/models/server/models/ReadReceipts.js deleted file mode 100644 index d830f400669f7..0000000000000 --- a/app/models/server/models/ReadReceipts.js +++ /dev/null @@ -1,25 +0,0 @@ -import { Base } from './_Base'; - -export class ReadReceipts extends Base { - constructor(...args) { - super(...args); - - this.tryEnsureIndex({ - roomId: 1, - userId: 1, - messageId: 1, - }, { - unique: 1, - }); - - this.tryEnsureIndex({ - messageId: 1, - }); - } - - findByMessageId(messageId) { - return this.find({ messageId }); - } -} - -export default new ReadReceipts('message_read_receipt'); diff --git a/app/models/server/models/Reports.js b/app/models/server/models/Reports.js deleted file mode 100644 index 4d2ab019f19b7..0000000000000 --- a/app/models/server/models/Reports.js +++ /dev/null @@ -1,23 +0,0 @@ -import _ from 'underscore'; - -import { Base } from './_Base'; - -export class Reports extends Base { - constructor() { - super('reports'); - } - - createWithMessageDescriptionAndUserId(message, description, userId, extraData) { - const record = { - message, - description, - ts: new Date(), - userId, - }; - _.extend(record, extraData); - record._id = this.insert(record); - return record; - } -} - -export default new Reports(); diff --git a/app/models/server/models/Roles.js b/app/models/server/models/Roles.js deleted file mode 100644 index e7576191d0709..0000000000000 --- a/app/models/server/models/Roles.js +++ /dev/null @@ -1,134 +0,0 @@ -import { Base } from './_Base'; -import * as Models from '..'; - - -export class Roles extends Base { - constructor(...args) { - super(...args); - this.tryEnsureIndex({ name: 1 }); - this.tryEnsureIndex({ scope: 1 }); - } - - findUsersInRole(name, scope, options) { - const role = this.findOneByName(name); - const roleScope = (role && role.scope) || 'Users'; - const model = Models[roleScope]; - - return model && model.findUsersInRoles && model.findUsersInRoles(name, scope, options); - } - - isUserInRoles(userId, roles, scope) { - roles = [].concat(roles); - return roles.some((roleName) => { - const role = this.findOneByName(roleName); - const roleScope = (role && role.scope) || 'Users'; - const model = Models[roleScope]; - - return model && model.isUserInRole && model.isUserInRole(userId, roleName, scope); - }); - } - - updateById(_id, name, scope, description, mandatory2fa) { - const queryData = { - name, - scope, - description, - mandatory2fa, - }; - - this.upsert({ _id }, { $set: queryData }); - } - - createWithRandomId(name, scope = 'Users', description = '', protectedRole = true, mandatory2fa = false) { - const role = { - name, - scope, - description, - protected: protectedRole, - mandatory2fa, - }; - - return this.insert(role); - } - - createOrUpdate(name, scope = 'Users', description = '', protectedRole = true, mandatory2fa = false) { - const queryData = { - name, - scope, - description, - protected: protectedRole, - mandatory2fa, - }; - - this.upsert({ _id: name }, { $set: queryData }); - } - - addUserRoles(userId, roles, scope) { - roles = [].concat(roles); - for (const roleName of roles) { - const role = this.findOneByName(roleName); - const roleScope = (role && role.scope) || 'Users'; - const model = Models[roleScope]; - - model && model.addRolesByUserId && model.addRolesByUserId(userId, roleName, scope); - } - return true; - } - - removeUserRoles(userId, roles, scope) { - roles = [].concat(roles); - for (const roleName of roles) { - const role = this.findOneByName(roleName); - const roleScope = (role && role.scope) || 'Users'; - const model = Models[roleScope]; - - model && model.removeRolesByUserId && model.removeRolesByUserId(userId, roleName, scope); - } - return true; - } - - findOneByIdOrName(_idOrName, options) { - const query = { - $or: [{ - _id: _idOrName, - }, { - name: _idOrName, - }], - }; - - return this.findOne(query, options); - } - - findOneByName(name, options) { - const query = { - name, - }; - - return this.findOne(query, options); - } - - findByUpdatedDate(updatedAfterDate, options) { - const query = { - _updatedAt: { $gte: new Date(updatedAfterDate) }, - }; - - return this.find(query, options); - } - - canAddUserToRole(uid, roleName, scope) { - const role = this.findOne({ name: roleName }, { fields: { scope: 1 } }); - if (!role) { - return false; - } - - const model = Models[role.scope]; - if (!model) { - return; - } - - const user = model.isUserInRoleScope(uid, scope); - return !!user; - } -} - -export default new Roles('roles'); diff --git a/app/models/server/models/Rooms.js b/app/models/server/models/Rooms.js index d884208317fc3..460bed352f07f 100644 --- a/app/models/server/models/Rooms.js +++ b/app/models/server/models/Rooms.js @@ -5,7 +5,6 @@ import { escapeRegExp } from '@rocket.chat/string-helpers'; import { Base } from './_Base'; import Messages from './Messages'; import Subscriptions from './Subscriptions'; -import { getValidRoomName } from '../../../utils'; export class Rooms extends Base { constructor(...args) { @@ -335,6 +334,7 @@ export class Rooms extends Base { let channelName = s.trim(name); try { // TODO evaluate if this function call should be here + const { getValidRoomName } = import('../../../utils/lib/getValidRoomName'); channelName = getValidRoomName(channelName, null, { allowDuplicates: true }); } catch (e) { console.error(e); diff --git a/app/models/server/models/ServerEvents.ts b/app/models/server/models/ServerEvents.ts deleted file mode 100644 index 09b17ac510676..0000000000000 --- a/app/models/server/models/ServerEvents.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Base } from './_Base'; - -export class ServerEvents extends Base { - constructor() { - super('server_events'); - this.tryEnsureIndex({ t: 1, ip: 1, ts: -1 }); - this.tryEnsureIndex({ t: 1, 'u.username': 1, ts: -1 }); - } -} - -export default new ServerEvents(); diff --git a/app/models/server/models/Sessions.mocks.js b/app/models/server/models/Sessions.mocks.js deleted file mode 100644 index ac4b22bf7780b..0000000000000 --- a/app/models/server/models/Sessions.mocks.js +++ /dev/null @@ -1,16 +0,0 @@ -import mock from 'mock-require'; - -mock('./_Base', { - Base: class Base { - model = { - rawDatabase() { - return { - collection() {}, - options: {}, - }; - }, - } - - tryEnsureIndex() {} - }, -}); diff --git a/app/models/server/models/SmarshHistory.js b/app/models/server/models/SmarshHistory.js deleted file mode 100644 index 9b2b7abc50433..0000000000000 --- a/app/models/server/models/SmarshHistory.js +++ /dev/null @@ -1,9 +0,0 @@ -import { Base } from './_Base'; - -export class SmarshHistory extends Base { - constructor() { - super('smarsh_history'); - } -} - -export default new SmarshHistory(); diff --git a/app/models/server/models/Statistics.js b/app/models/server/models/Statistics.js deleted file mode 100644 index 014f8c8180d2a..0000000000000 --- a/app/models/server/models/Statistics.js +++ /dev/null @@ -1,28 +0,0 @@ -import { Base } from './_Base'; - -export class Statistics extends Base { - constructor() { - super('statistics'); - - this.tryEnsureIndex({ createdAt: -1 }); - } - - // FIND ONE - findOneById(_id, options) { - const query = { _id }; - return this.findOne(query, options); - } - - findLast() { - const options = { - sort: { - createdAt: -1, - }, - limit: 1, - }; - const records = this.find({}, options).fetch(); - return records && records[0]; - } -} - -export default new Statistics(); diff --git a/app/models/server/models/Uploads.js b/app/models/server/models/Uploads.js deleted file mode 100644 index ce56ea6d0c0f6..0000000000000 --- a/app/models/server/models/Uploads.js +++ /dev/null @@ -1,146 +0,0 @@ -import _ from 'underscore'; -import s from 'underscore.string'; -import { InstanceStatus } from 'meteor/konecty:multiple-instances-status'; -import { escapeRegExp } from '@rocket.chat/string-helpers'; - -import { Base } from './_Base'; - -const fillTypeGroup = (fileData) => { - if (!fileData.type) { - return; - } - - fileData.typeGroup = fileData.type.split('/').shift(); -}; - -export class Uploads extends Base { - constructor() { - super('uploads'); - - this.model.before.insert((userId, doc) => { - doc.instanceId = InstanceStatus.id(); - }); - - this.tryEnsureIndex({ rid: 1 }); - this.tryEnsureIndex({ uploadedAt: 1 }); - this.tryEnsureIndex({ typeGroup: 1 }); - } - - findNotHiddenFilesOfRoom(roomId, searchText, fileType, limit) { - const fileQuery = { - rid: roomId, - complete: true, - uploading: false, - _hidden: { - $ne: true, - }, - }; - - if (searchText) { - fileQuery.name = { $regex: new RegExp(escapeRegExp(searchText), 'i') }; - } - - if (fileType && fileType !== 'all') { - fileQuery.typeGroup = fileType; - } - - const fileOptions = { - limit, - sort: { - uploadedAt: -1, - }, - fields: { - _id: 1, - userId: 1, - rid: 1, - name: 1, - description: 1, - type: 1, - url: 1, - uploadedAt: 1, - typeGroup: 1, - }, - }; - - return this.find(fileQuery, fileOptions); - } - - insert(fileData, ...args) { - fillTypeGroup(fileData); - return super.insert(fileData, ...args); - } - - update(filter, update, ...args) { - if (update.$set) { - fillTypeGroup(update.$set); - } else if (update.type) { - fillTypeGroup(update); - } - - return super.update(filter, update, ...args); - } - - insertFileInit(userId, store, file, extra) { - const fileData = { - userId, - store, - complete: false, - uploading: true, - progress: 0, - extension: s.strRightBack(file.name, '.'), - uploadedAt: new Date(), - }; - - _.extend(fileData, file, extra); - - if (this.model.direct && this.model.direct.insert != null) { - fillTypeGroup(fileData); - file = this.model.direct.insert(fileData); - } else { - file = this.insert(fileData); - } - - return file; - } - - updateFileComplete(fileId, userId, file) { - let result; - if (!fileId) { - return; - } - - const filter = { - _id: fileId, - userId, - }; - - const update = { - $set: { - complete: true, - uploading: false, - progress: 1, - }, - }; - - update.$set = _.extend(file, update.$set); - - if (this.model.direct && this.model.direct.update != null) { - fillTypeGroup(update.$set); - - result = this.model.direct.update(filter, update); - } else { - result = this.update(filter, update); - } - - return result; - } - - deleteFile(fileId) { - if (this.model.direct && this.model.direct.remove != null) { - return this.model.direct.remove({ _id: fileId }); - } - return this.remove({ _id: fileId }); - } -} - -export default new Uploads(); diff --git a/app/models/server/models/UserDataFiles.js b/app/models/server/models/UserDataFiles.js deleted file mode 100644 index a877188ff03bc..0000000000000 --- a/app/models/server/models/UserDataFiles.js +++ /dev/null @@ -1,44 +0,0 @@ -import _ from 'underscore'; - -import { Base } from './_Base'; - -export class UserDataFiles extends Base { - constructor() { - super('user_data_files'); - - this.tryEnsureIndex({ userId: 1 }); - } - - // FIND - findById(id) { - const query = { _id: id }; - return this.find(query); - } - - findLastFileByUser(userId, options = {}) { - const query = { - userId, - }; - - options.sort = { _updatedAt: -1 }; - return this.findOne(query, options); - } - - // INSERT - create(data) { - const userDataFile = { - createdAt: new Date(), - }; - - _.extend(userDataFile, data); - - return this.insert(userDataFile); - } - - // REMOVE - removeById(_id) { - return this.remove(_id); - } -} - -export default new UserDataFiles(); diff --git a/app/models/server/models/UsersSessions.js b/app/models/server/models/UsersSessions.js deleted file mode 100644 index 43aec902d3432..0000000000000 --- a/app/models/server/models/UsersSessions.js +++ /dev/null @@ -1,7 +0,0 @@ -import { UsersSessions } from 'meteor/konecty:user-presence'; - -import { Base } from './_Base'; - -export class UsersSessionsModel extends Base {} - -export default new UsersSessionsModel(UsersSessions, { preventSetUpdatedAt: true }); diff --git a/app/models/server/models/WebdavAccounts.js b/app/models/server/models/WebdavAccounts.js deleted file mode 100644 index 09df0b64a3a69..0000000000000 --- a/app/models/server/models/WebdavAccounts.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Webdav Accounts model - */ -import { Base } from './_Base'; - -export class WebdavAccounts extends Base { - constructor() { - super('webdav_accounts'); - - this.tryEnsureIndex({ user_id: 1 }); - } - - findWithUserId(user_id, options) { - const query = { user_id }; - return this.find(query, options); - } - - removeByUserAndId(_id, user_id) { - return this.remove({ _id, user_id }); - } - - removeById(_id) { - return this.remove({ _id }); - } -} - -export default new WebdavAccounts(); diff --git a/app/models/server/models/_BaseDb.js b/app/models/server/models/_BaseDb.js index 6c9473c81fb84..f72bb6c845577 100644 --- a/app/models/server/models/_BaseDb.js +++ b/app/models/server/models/_BaseDb.js @@ -30,31 +30,14 @@ const actions = { d: 'remove', }; -export class BaseDb extends EventEmitter { - constructor(model, baseModel, options = {}) { +export class BaseDbWatch extends EventEmitter { + constructor(collectionName) { super(); - - if (Match.test(model, String)) { - this.name = model; - this.collectionName = this.baseName + this.name; - this.model = new Mongo.Collection(this.collectionName); - } else { - this.name = model._name; - this.collectionName = this.name; - this.model = model; - } - - this.baseModel = baseModel; - - this.preventSetUpdatedAt = !!options.preventSetUpdatedAt; - - this.wrapModel(); + this.collectionName = collectionName; if (!process.env.DISABLE_DB_WATCH) { this.initDbWatch(); } - - this.tryEnsureIndex({ _updatedAt: 1 }, options._updatedAtIndexOptions); } initDbWatch() { @@ -97,6 +80,104 @@ export class BaseDb extends EventEmitter { } } + processOplogRecord({ id, op }) { + const action = actions[op.op]; + metrics.oplog.inc({ + collection: this.collectionName, + op: action, + }); + + if (action === 'insert') { + this.emit('change', { + action, + clientAction: 'inserted', + id: op.o._id, + data: op.o, + oplog: true, + }); + return; + } + + if (action === 'update') { + if (!op.o.$set && !op.o.$unset) { + this.emit('change', { + action, + clientAction: 'updated', + id, + data: op.o, + oplog: true, + }); + return; + } + + const diff = {}; + if (op.o.$set) { + for (const key in op.o.$set) { + if (op.o.$set.hasOwnProperty(key)) { + diff[key] = op.o.$set[key]; + } + } + } + const unset = {}; + if (op.o.$unset) { + for (const key in op.o.$unset) { + if (op.o.$unset.hasOwnProperty(key)) { + diff[key] = undefined; + unset[key] = 1; + } + } + } + + this.emit('change', { + action, + clientAction: 'updated', + id, + diff, + unset, + oplog: true, + }); + return; + } + + if (action === 'remove') { + this.emit('change', { + action, + clientAction: 'removed', + id, + oplog: true, + }); + } + } +} + + +export class BaseDb extends BaseDbWatch { + constructor(model, baseModel, options = {}) { + const collectionName = Match.test(model, String) ? baseName + model : model._name; + + super(collectionName); + + this.collectionName = collectionName; + + if (Match.test(model, String)) { + this.name = model; + this.collectionName = this.baseName + this.name; + this.model = new Mongo.Collection(this.collectionName); + } else { + this.name = model._name; + this.collectionName = this.name; + this.model = model; + } + + this.baseModel = baseModel; + + this.preventSetUpdatedAt = !!options.preventSetUpdatedAt; + + this.wrapModel(); + + this.tryEnsureIndex({ _updatedAt: 1 }, options._updatedAtIndexOptions); + } + get baseName() { return baseName; } @@ -204,75 +285,6 @@ export class BaseDb extends EventEmitter { ); } - processOplogRecord({ id, op }) { - const action = actions[op.op]; - metrics.oplog.inc({ - collection: this.collectionName, - op: action, - }); - - if (action === 'insert') { - this.emit('change', { - action, - clientAction: 'inserted', - id: op.o._id, - data: op.o, - oplog: true, - }); - return; - } - - if (action === 'update') { - if (!op.o.$set && !op.o.$unset) { - this.emit('change', { - action, - clientAction: 'updated', - id, - data: op.o, - oplog: true, - }); - return; - } - - const diff = {}; - if (op.o.$set) { - for (const key in op.o.$set) { - if (op.o.$set.hasOwnProperty(key)) { - diff[key] = op.o.$set[key]; - } - } - } - const unset = {}; - if (op.o.$unset) { - for (const key in op.o.$unset) { - if (op.o.$unset.hasOwnProperty(key)) { - diff[key] = undefined; - unset[key] = 1; - } - } - } - - this.emit('change', { - action, - clientAction: 'updated', - id, - diff, - unset, - oplog: true, - }); - return; - } - - if (action === 'remove') { - this.emit('change', { - action, - clientAction: 'removed', - id, - oplog: true, - }); - } - } - insert(record, ...args) { this.setUpdatedAt(record); diff --git a/app/models/server/raw/Analytics.js b/app/models/server/raw/Analytics.js deleted file mode 100644 index 81e0ad335c26f..0000000000000 --- a/app/models/server/raw/Analytics.js +++ /dev/null @@ -1,151 +0,0 @@ -import { Random } from 'meteor/random'; - -import { BaseRaw } from './BaseRaw'; -import Analytics from '../models/Analytics'; -import { readSecondaryPreferred } from '../../../../server/database/readSecondaryPreferred'; - -export class AnalyticsRaw extends BaseRaw { - saveMessageSent({ room, date }) { - return this.update({ date, 'room._id': room._id, type: 'messages' }, { - $set: { - room: { _id: room._id, name: room.fname || room.name, t: room.t, usernames: room.usernames || [] }, - }, - $setOnInsert: { - _id: Random.id(), - date, - type: 'messages', - }, - $inc: { messages: 1 }, - }, { upsert: true }); - } - - saveUserData({ date }) { - return this.update({ date, type: 'users' }, { - $setOnInsert: { - _id: Random.id(), - date, - type: 'users', - }, - $inc: { users: 1 }, - }, { upsert: true }); - } - - saveMessageDeleted({ room, date }) { - return this.update({ date, 'room._id': room._id }, { - $inc: { messages: -1 }, - }); - } - - getMessagesSentTotalByDate({ start, end, options = {} }) { - const params = [ - { - $match: { - type: 'messages', - date: { $gte: start, $lte: end }, - }, - }, - { - $group: { - _id: '$date', - messages: { $sum: '$messages' }, - }, - }, - ]; - if (options.sort) { - params.push({ $sort: options.sort }); - } - if (options.count) { - params.push({ $limit: options.count }); - } - return this.col.aggregate(params).toArray(); - } - - getMessagesOrigin({ start, end }) { - const params = [ - { - $match: { - type: 'messages', - date: { $gte: start, $lte: end }, - }, - }, - { - $group: { - _id: { t: '$room.t' }, - messages: { $sum: '$messages' }, - }, - }, - { - $project: { - _id: 0, - t: '$_id.t', - messages: 1, - }, - }, - ]; - return this.col.aggregate(params).toArray(); - } - - getMostPopularChannelsByMessagesSentQuantity({ start, end, options = {} }) { - const params = [ - { - $match: { - type: 'messages', - date: { $gte: start, $lte: end }, - }, - }, - { - $group: { - _id: { t: '$room.t', name: '$room.name', usernames: '$room.usernames' }, - messages: { $sum: '$messages' }, - }, - }, - { - $project: { - _id: 0, - t: '$_id.t', - name: '$_id.name', - usernames: '$_id.usernames', - messages: 1, - }, - }, - ]; - if (options.sort) { - params.push({ $sort: options.sort }); - } - if (options.count) { - params.push({ $limit: options.count }); - } - return this.col.aggregate(params).toArray(); - } - - getTotalOfRegisteredUsersByDate({ start, end, options = {} }) { - const params = [ - { - $match: { - type: 'users', - date: { $gte: start, $lte: end }, - }, - }, - { - $group: { - _id: '$date', - users: { $sum: '$users' }, - }, - }, - ]; - if (options.sort) { - params.push({ $sort: options.sort }); - } - if (options.count) { - params.push({ $limit: options.count }); - } - return this.col.aggregate(params).toArray(); - } - - findByTypeBeforeDate({ type, date }) { - return this.find({ type, date: { $lte: date } }); - } -} - -const db = Analytics.model.rawDatabase(); -export default new AnalyticsRaw(db.collection(Analytics.model._name, { readPreference: readSecondaryPreferred(db) })); diff --git a/app/models/server/raw/Analytics.ts b/app/models/server/raw/Analytics.ts new file mode 100644 index 0000000000000..943274853746f --- /dev/null +++ b/app/models/server/raw/Analytics.ts @@ -0,0 +1,146 @@ +import { Random } from 'meteor/random'; +import { AggregationCursor, Cursor, SortOptionObject, UpdateWriteOpResult } from 'mongodb'; + +import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { IAnalytic } from '../../../../definition/IAnalytic'; +import { IRoom } from '../../../../definition/IRoom'; + +type T = IAnalytic; + +export class AnalyticsRaw extends BaseRaw { + protected indexes: IndexSpecification[] = [ + { key: { date: 1 } }, + { key: { 'room._id': 1, date: 1 }, unique: true }, + ]; + + saveMessageSent({ room, date }: { room: IRoom; date: IAnalytic['date'] }): Promise { + return this.updateMany({ date, 'room._id': room._id, type: 'messages' }, { + $set: { + room: { + _id: room._id, + name: room.fname || room.name, + t: room.t, + usernames: room.usernames || [], + }, + }, + $setOnInsert: { + _id: Random.id(), + date, + type: 'messages', + }, + $inc: { messages: 1 }, + }, { upsert: true }); + } + + saveUserData({ date }: { date: IAnalytic['date'] }): Promise { + return this.updateMany({ date, type: 'users' }, { + $setOnInsert: { + _id: Random.id(), + date, + type: 'users', + }, + $inc: { users: 1 }, + }, { upsert: true }); + } + + saveMessageDeleted({ room, date }: { room: { _id: string }; date: IAnalytic['date'] }): Promise { + return this.updateMany({ date, 'room._id': room._id }, { + $inc: { messages: -1 }, + }); + } + + getMessagesSentTotalByDate({ start, end, options = {} }: { start: IAnalytic['date']; end: IAnalytic['date']; options?: { sort?: SortOptionObject; count?: number } }): AggregationCursor { + return this.col.aggregate([ + { + $match: { + type: 'messages', + date: { $gte: start, $lte: end }, + }, + }, + { + $group: { + _id: '$date', + messages: { $sum: '$messages' }, + }, + }, + ...options.sort ? [{ $sort: options.sort }] : [], + ...options.count ? [{ $limit: options.count }] : [], + ]); + } + + getMessagesOrigin({ start, end }: { start: IAnalytic['date']; end: IAnalytic['date'] }): AggregationCursor { + const params = [ + { + $match: { + type: 'messages', + date: { $gte: start, $lte: end }, + }, + }, + { + $group: { + _id: { t: '$room.t' }, + messages: { $sum: '$messages' }, + }, + }, + { + $project: { + _id: 0, + t: '$_id.t', + messages: 1, + }, + }, + ]; + return this.col.aggregate(params); + } + + getMostPopularChannelsByMessagesSentQuantity({ start, end, options = {} }: { start: IAnalytic['date']; end: IAnalytic['date']; options?: { sort?: SortOptionObject; count?: number } }): AggregationCursor { + return this.col.aggregate([ + { + $match: { + type: 'messages', + date: { $gte: start, $lte: end }, + }, + }, + { + $group: { + _id: { t: '$room.t', name: '$room.name', usernames: '$room.usernames' }, + messages: { $sum: '$messages' }, + }, + }, + { + $project: { + _id: 0, + t: '$_id.t', + name: '$_id.name', + usernames: '$_id.usernames', + messages: 1, + }, + }, + ...options.sort ? [{ $sort: options.sort }] : [], + ...options.count ? [{ $limit: options.count }] : [], + ]); + } + + getTotalOfRegisteredUsersByDate({ start, end, options = {} }: { start: IAnalytic['date']; end: IAnalytic['date']; options?: { sort?: SortOptionObject; count?: number } }): AggregationCursor { + return this.col.aggregate([ + { + $match: { + type: 'users', + date: { $gte: start, $lte: end }, + }, + }, + { + $group: { + _id: '$date', + users: { $sum: '$users' }, + }, + }, + ...options.sort ? [{ $sort: options.sort }] : [], + ...options.count ? [{ $limit: options.count }] : [], + ]); + } + + findByTypeBeforeDate({ type, date }: { type: T['type']; date: T['date'] }): Cursor { + return this.find({ type, date: { $lte: date } }); + } +} diff --git a/app/models/server/raw/Avatars.ts b/app/models/server/raw/Avatars.ts new file mode 100644 index 0000000000000..cc9c5939f3b32 --- /dev/null +++ b/app/models/server/raw/Avatars.ts @@ -0,0 +1,73 @@ +import { DeleteWriteOpResultObject, UpdateWriteOpResult } from 'mongodb'; + +import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { IAvatar as T } from '../../../../definition/IAvatar'; + +export class AvatarsRaw extends BaseRaw { + protected indexes: IndexSpecification[] = [ + { key: { name: 1 }, sparse: true }, + { key: { rid: 1 }, sparse: true }, + ]; + + insertAvatarFileInit(name: string, userId: string, store: string, file: {name: string}, extra: object): Promise { + const fileData = { + name, + userId, + store, + complete: false, + uploading: true, + progress: 0, + extension: file.name.split('.').pop(), + uploadedAt: new Date(), + }; + + Object.assign(fileData, file, extra); + + return this.updateOne({ _id: name }, fileData, { upsert: true }); + } + + updateFileComplete(fileId: string, userId: string, file: object): Promise | undefined { + if (!fileId) { + return; + } + + const filter = { + _id: fileId, + userId, + }; + + const update = { + $set: { + complete: true, + uploading: false, + progress: 1, + }, + }; + + update.$set = Object.assign(file, update.$set); + + return this.updateOne(filter, update); + } + + async findOneByName(name: string): Promise { + return this.findOne({ name }); + } + + async findOneByRoomId(rid: string): Promise { + return this.findOne({ rid }); + } + + async updateFileNameById(fileId: string, name: string): Promise { + const filter = { _id: fileId }; + const update = { + $set: { + name, + }, + }; + return this.updateOne(filter, update); + } + + async deleteFile(fileId: string): Promise { + return this.deleteOne({ _id: fileId }); + } +} diff --git a/app/models/server/raw/BaseRaw.ts b/app/models/server/raw/BaseRaw.ts index 602d8a62542a7..500fe55d6e1a0 100644 --- a/app/models/server/raw/BaseRaw.ts +++ b/app/models/server/raw/BaseRaw.ts @@ -1,10 +1,14 @@ import { Collection, CollectionInsertOneOptions, + CommonOptions, Cursor, DeleteWriteOpResultObject, FilterQuery, + FindAndModifyWriteOpResultObject, + FindOneAndUpdateOption, FindOneOptions, + IndexSpecification, InsertOneWriteOpResult, InsertWriteOpResult, ObjectID, @@ -21,6 +25,10 @@ import { import { setUpdatedAt } from '../lib/setUpdatedAt'; +export { + IndexSpecification, +} from 'mongodb'; + // [extracted from @types/mongo] TypeScript Omit (Exclude to be specific) does not work for objects with an "any" indexed type, and breaks discriminated unions type EnhancedOmit = string | number extends keyof T ? T // T has indexed type e.g. { _id: string; [k: string]: any; } or it is "any" @@ -37,7 +45,7 @@ type ExtractIdType = TSchema extends { _id: infer U } // user has defin : U : ObjectId; -type ModelOptionalId = EnhancedOmit & { _id?: ExtractIdType }; +export type ModelOptionalId = EnhancedOmit & { _id?: ExtractIdType }; // InsertionModel forces both _id and _updatedAt to be optional, regardless of how they are declared in T export type InsertionModel = EnhancedOmit, '_updatedAt'> & { _updatedAt?: Date }; @@ -58,13 +66,43 @@ const warnFields = process.env.NODE_ENV !== 'production' export class BaseRaw = undefined> implements IBaseRaw { public readonly defaultFields: C; + protected indexes?: IndexSpecification[]; + protected name: string; + private preventSetUpdatedAt: boolean; + constructor( public readonly col: Collection, public readonly trash?: Collection, + options?: { preventSetUpdatedAt?: boolean }, ) { this.name = this.col.collectionName.replace(baseName, ''); + + if (this.indexes?.length) { + this.col.createIndexes(this.indexes); + } + + this.preventSetUpdatedAt = options?.preventSetUpdatedAt ?? false; + } + + private doNotMixInclusionAndExclusionFields(options: FindOneOptions = {}): FindOneOptions { + const optionsDef = this.ensureDefaultFields(options); + if (optionsDef?.projection === undefined) { + return optionsDef; + } + + const projection: Record = optionsDef?.projection; + const keys = Object.keys(projection); + const removeKeys = keys.filter((key) => projection[key] === 0); + if (keys.length > removeKeys.length) { + removeKeys.forEach((key) => delete projection[key]); + } + + return { + ...optionsDef, + projection, + }; } private ensureDefaultFields(options?: undefined): C extends void ? undefined : WithoutProjection>; @@ -78,26 +116,32 @@ export class BaseRaw = undefined> implements IBase return options; } - const { fields, ...rest } = options || {}; + const { fields: deprecatedFields, projection, ...rest } = options || {}; - if (fields) { + if (deprecatedFields) { warnFields('Using \'fields\' in models is deprecated.', options); } + const fields = { ...deprecatedFields, ...projection }; + return { projection: this.defaultFields, - ...fields && { projection: fields }, + ...fields && Object.values(fields).length && { projection: fields }, ...rest, }; } + public findOneAndUpdate(query: FilterQuery, update: UpdateQuery | T, options?: FindOneAndUpdateOption): Promise> { + return this.col.findOneAndUpdate(query, update, options); + } + async findOneById(_id: string, options?: WithoutProjection> | undefined): Promise; async findOneById

(_id: string, options: FindOneOptions

): Promise

; async findOneById

(_id: string, options?: any): Promise { const query = { _id } as FilterQuery; - const optionsDef = this.ensureDefaultFields(options); + const optionsDef = this.doNotMixInclusionAndExclusionFields(options); return this.col.findOne(query, optionsDef); } @@ -110,13 +154,13 @@ export class BaseRaw = undefined> implements IBase async findOne

(query: FilterQuery | string = {}, options?: any): Promise { const q = typeof query === 'string' ? { _id: query } as FilterQuery : query; - const optionsDef = this.ensureDefaultFields(options); + const optionsDef = this.doNotMixInclusionAndExclusionFields(options); return this.col.findOne(q, optionsDef); } - findUsersInRoles(): void { - throw new Error('[overwrite-function] You must overwrite this function in the extended classes'); - } + // findUsersInRoles(): void { + // throw new Error('[overwrite-function] You must overwrite this function in the extended classes'); + // } find(query?: FilterQuery): Cursor>; @@ -125,22 +169,22 @@ export class BaseRaw = undefined> implements IBase find

(query: FilterQuery, options: FindOneOptions

): Cursor

; find

(query: FilterQuery | undefined = {}, options?: any): Cursor

| Cursor { - const optionsDef = this.ensureDefaultFields(options); + const optionsDef = this.doNotMixInclusionAndExclusionFields(options); return this.col.find(query, optionsDef); } update(filter: FilterQuery, update: UpdateQuery | Partial, options?: UpdateOneOptions & { multi?: boolean }): Promise { - setUpdatedAt(update); + this.setUpdatedAt(update); return this.col.update(filter, update, options); } updateOne(filter: FilterQuery, update: UpdateQuery | Partial, options?: UpdateOneOptions & { multi?: boolean }): Promise { - setUpdatedAt(update); + this.setUpdatedAt(update); return this.col.updateOne(filter, update, options); } updateMany(filter: FilterQuery, update: UpdateQuery | Partial, options?: UpdateManyOptions): Promise { - setUpdatedAt(update); + this.setUpdatedAt(update); return this.col.updateMany(filter, update, options); } @@ -150,7 +194,7 @@ export class BaseRaw = undefined> implements IBase const oid = new ObjectID(); return { _id: oid.toHexString(), ...doc }; } - setUpdatedAt(doc); + this.setUpdatedAt(doc); return doc; }); @@ -164,7 +208,7 @@ export class BaseRaw = undefined> implements IBase doc = { _id: oid.toHexString(), ...doc }; } - setUpdatedAt(doc); + this.setUpdatedAt(doc); // TODO reavaluate following type casting return this.col.insertOne(doc as unknown as OptionalId, options); @@ -175,6 +219,15 @@ export class BaseRaw = undefined> implements IBase return this.col.deleteOne(query); } + deleteOne(filter: FilterQuery, options?: CommonOptions & { bypassDocumentValidation?: boolean }): Promise { + return this.col.deleteOne(filter, options); + } + + deleteMany(filter: FilterQuery, options?: CommonOptions): Promise { + return this.col.deleteMany(filter, options); + } + + // Trash trashFind

(query: FilterQuery, options: FindOneOptions

): Cursor

| undefined { if (!this.trash) { @@ -214,4 +267,29 @@ export class BaseRaw = undefined> implements IBase } return trash.findOne(query, options); } + + private setUpdatedAt(record: UpdateQuery | InsertionModel): void { + if (this.preventSetUpdatedAt) { + return; + } + setUpdatedAt(record); + } + + trashFindDeletedAfter

(deletedAt: Date, query: FilterQuery = {}, options?: any): Cursor { + const q = { + __collection__: this.name, + _deletedAt: { + $gt: deletedAt, + }, + ...query, + } as FilterQuery; + + const { trash } = this; + + if (!trash) { + throw new Error('Trash is not enabled for this collection'); + } + + return trash.find(q, options); + } } diff --git a/app/models/server/raw/CredentialTokens.ts b/app/models/server/raw/CredentialTokens.ts new file mode 100644 index 0000000000000..eb6db2786682f --- /dev/null +++ b/app/models/server/raw/CredentialTokens.ts @@ -0,0 +1,29 @@ +import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { ICredentialToken as T } from '../../../../definition/ICredentialToken'; + +export class CredentialTokensRaw extends BaseRaw { + protected indexes: IndexSpecification[] = [ + { key: { expireAt: 1 }, sparse: true, expireAfterSeconds: 0 }, + ] + + async create(_id: string, userInfo: T['userInfo']): Promise { + const validForMilliseconds = 60000; // Valid for 60 seconds + const token = { + _id, + userInfo, + expireAt: new Date(Date.now() + validForMilliseconds), + }; + + await this.insertOne(token); + return token; + } + + findOneNotExpiredById(_id: string): Promise { + const query = { + _id, + expireAt: { $gt: new Date() }, + }; + + return this.findOne(query); + } +} diff --git a/app/models/server/raw/CustomSounds.js b/app/models/server/raw/CustomSounds.js deleted file mode 100644 index 54e96f0645129..0000000000000 --- a/app/models/server/raw/CustomSounds.js +++ /dev/null @@ -1,5 +0,0 @@ -import { BaseRaw } from './BaseRaw'; - -export class CustomSoundsRaw extends BaseRaw { - -} diff --git a/app/models/server/raw/CustomSounds.ts b/app/models/server/raw/CustomSounds.ts new file mode 100644 index 0000000000000..c46b7f4b41411 --- /dev/null +++ b/app/models/server/raw/CustomSounds.ts @@ -0,0 +1,44 @@ +import { Cursor, FindOneOptions, InsertOneWriteOpResult, UpdateWriteOpResult, WithId, WithoutProjection } from 'mongodb'; + +import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { ICustomSound as T } from '../../../../definition/ICustomSound'; + +export class CustomSoundsRaw extends BaseRaw { + protected indexes: IndexSpecification[] = [ + { key: { name: 1 } }, + ] + + // find + findByName(name: string, options: WithoutProjection>): Cursor { + const query = { + name, + }; + + return this.find(query, options); + } + + findByNameExceptId(name: string, except: string, options: WithoutProjection>): Cursor { + const query = { + _id: { $nin: [except] }, + name, + }; + + return this.find(query, options); + } + + // update + setName(_id: string, name: string): Promise { + const update = { + $set: { + name, + }, + }; + + return this.updateOne({ _id }, update); + } + + // INSERT + create(data: T): Promise>> { + return this.insertOne(data); + } +} diff --git a/app/models/server/raw/CustomUserStatus.js b/app/models/server/raw/CustomUserStatus.js deleted file mode 100644 index 0ffc78d4b3961..0000000000000 --- a/app/models/server/raw/CustomUserStatus.js +++ /dev/null @@ -1,5 +0,0 @@ -import { BaseRaw } from './BaseRaw'; - -export class CustomUserStatusRaw extends BaseRaw { - -} diff --git a/app/models/server/raw/CustomUserStatus.ts b/app/models/server/raw/CustomUserStatus.ts new file mode 100644 index 0000000000000..ad1d3df1ea10b --- /dev/null +++ b/app/models/server/raw/CustomUserStatus.ts @@ -0,0 +1,59 @@ +import { Cursor, FindOneOptions, InsertOneWriteOpResult, UpdateWriteOpResult, WithId, WithoutProjection } from 'mongodb'; + +import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { ICustomUserStatus as T } from '../../../../definition/ICustomUserStatus'; + +export class CustomUserStatusRaw extends BaseRaw { + protected indexes: IndexSpecification[] = [ + { key: { name: 1 } }, + ] + + // find one by name + async findOneByName(name: string, options: WithoutProjection>): Promise { + return this.findOne({ name }, options); + } + + // find + findByName(name: string, options: WithoutProjection>): Cursor { + const query = { + name, + }; + + return this.find(query, options); + } + + findByNameExceptId(name: string, except: string, options: WithoutProjection>): Cursor { + const query = { + _id: { $nin: [except] }, + name, + }; + + return this.find(query, options); + } + + // update + setName(_id: string, name: string): Promise { + const update = { + $set: { + name, + }, + }; + + return this.updateOne({ _id }, update); + } + + setStatusType(_id: string, statusType: string): Promise { + const update = { + $set: { + statusType, + }, + }; + + return this.updateOne({ _id }, update); + } + + // INSERT + create(data: T): Promise>> { + return this.insertOne(data); + } +} diff --git a/app/models/server/raw/EmailInbox.ts b/app/models/server/raw/EmailInbox.ts index 1d8d008242fa8..53b88792392f0 100644 --- a/app/models/server/raw/EmailInbox.ts +++ b/app/models/server/raw/EmailInbox.ts @@ -1,6 +1,8 @@ -import { BaseRaw } from './BaseRaw'; +import { BaseRaw, IndexSpecification } from './BaseRaw'; import { IEmailInbox } from '../../../../definition/IEmailInbox'; export class EmailInboxRaw extends BaseRaw { - // + protected indexes: IndexSpecification[] = [ + { key: { email: 1 }, unique: true }, + ] } diff --git a/app/models/server/raw/EmailMessageHistory.ts b/app/models/server/raw/EmailMessageHistory.ts index 9201d1b3a344c..89c54e079ec0f 100644 --- a/app/models/server/raw/EmailMessageHistory.ts +++ b/app/models/server/raw/EmailMessageHistory.ts @@ -1,10 +1,15 @@ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { IndexSpecification, InsertOneWriteOpResult, WithId } from 'mongodb'; + import { BaseRaw } from './BaseRaw'; -import { IEmailMessageHistory } from '../../../../definition/IEmailMessageHistory'; +import { IEmailMessageHistory as T } from '../../../../definition/IEmailMessageHistory'; + +export class EmailMessageHistoryRaw extends BaseRaw { + protected indexes: IndexSpecification[] = [ + { key: { createdAt: 1 }, expireAfterSeconds: 60 * 60 * 24 }, + ] -export class EmailMessageHistoryRaw extends BaseRaw { - insertOne({ _id, email }: IEmailMessageHistory) { - return this.col.insertOne({ + async create({ _id, email }: T): Promise>> { + return this.insertOne({ _id, email, createdAt: new Date(), diff --git a/app/models/server/raw/EmojiCustom.js b/app/models/server/raw/EmojiCustom.js deleted file mode 100644 index 80b81d41958b2..0000000000000 --- a/app/models/server/raw/EmojiCustom.js +++ /dev/null @@ -1,5 +0,0 @@ -import { BaseRaw } from './BaseRaw'; - -export class EmojiCustomRaw extends BaseRaw { - -} diff --git a/app/models/server/raw/EmojiCustom.ts b/app/models/server/raw/EmojiCustom.ts new file mode 100644 index 0000000000000..82f5f22fc97e2 --- /dev/null +++ b/app/models/server/raw/EmojiCustom.ts @@ -0,0 +1,79 @@ +import { Cursor, FindOneOptions, InsertOneWriteOpResult, UpdateWriteOpResult, WithId, WithoutProjection } from 'mongodb'; + +import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { IEmojiCustom as T } from '../../../../definition/IEmojiCustom'; + +export class EmojiCustomRaw extends BaseRaw { + protected indexes: IndexSpecification[] = [ + { key: { name: 1 } }, + { key: { aliases: 1 } }, + { key: { extension: 1 } }, + ] + + // find + findByNameOrAlias(emojiName: string, options: WithoutProjection>): Cursor { + let name = emojiName; + + if (typeof emojiName === 'string') { + name = emojiName.replace(/:/g, ''); + } + + const query = { + $or: [ + { name }, + { aliases: name }, + ], + }; + + return this.find(query, options); + } + + findByNameOrAliasExceptID(name: string, except: string, options: WithoutProjection>): Cursor { + const query = { + _id: { $nin: [except] }, + $or: [ + { name }, + { aliases: name }, + ], + }; + + return this.find(query, options); + } + + + // update + setName(_id: string, name: string): Promise { + const update = { + $set: { + name, + }, + }; + + return this.updateOne({ _id }, update); + } + + setAliases(_id: string, aliases: string): Promise { + const update = { + $set: { + aliases, + }, + }; + + return this.updateOne({ _id }, update); + } + + setExtension(_id: string, extension: string): Promise { + const update = { + $set: { + extension, + }, + }; + + return this.updateOne({ _id }, update); + } + + // INSERT + create(data: T): Promise>> { + return this.insertOne(data); + } +} diff --git a/app/models/server/raw/ExportOperations.ts b/app/models/server/raw/ExportOperations.ts new file mode 100644 index 0000000000000..d470722d28000 --- /dev/null +++ b/app/models/server/raw/ExportOperations.ts @@ -0,0 +1,68 @@ +import { Cursor, UpdateWriteOpResult } from 'mongodb'; + +import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { IExportOperation } from '../../../../definition/IExportOperation'; + +type T = IExportOperation; + +export class ExportOperationsRaw extends BaseRaw { + protected indexes: IndexSpecification[] = [ + { key: { userId: 1 } }, + { key: { status: 1 } }, + ] + + findOnePending(): Promise { + const query = { + status: { $nin: ['completed', 'skipped'] }, + }; + + return this.findOne(query); + } + + async create(data: T): Promise { + const result = await this.insertOne({ + ...data, + createdAt: new Date(), + }); + + return result.insertedId; + } + + findLastOperationByUser(userId: string, fullExport = false): Promise { + const query = { + userId, + fullExport, + }; + + return this.findOne(query, { sort: { createdAt: -1 } }); + } + + findAllPendingBeforeMyRequest(requestDay: Date): Cursor { + const query = { + status: { $nin: ['completed', 'skipped'] }, + createdAt: { $lt: requestDay }, + }; + + return this.find(query); + } + + updateOperation(data: T): Promise { + const update = { + $set: { + roomList: data.roomList, + status: data.status, + fileList: data.fileList, + generatedFile: data.generatedFile, + fileId: data.fileId, + userNameTable: data.userNameTable, + userData: data.userData, + generatedUserFile: data.generatedUserFile, + generatedAvatar: data.generatedAvatar, + exportPath: data.exportPath, + assetsPath: data.assetsPath, + }, + }; + + return this.updateOne({ _id: data._id }, update); + } +} diff --git a/app/models/server/raw/FederationKeys.ts b/app/models/server/raw/FederationKeys.ts new file mode 100644 index 0000000000000..7ac06051e4fa5 --- /dev/null +++ b/app/models/server/raw/FederationKeys.ts @@ -0,0 +1,65 @@ +import NodeRSA from 'node-rsa'; + +import { BaseRaw } from './BaseRaw'; + +type T = { + type: 'private' | 'public'; + key: string; +}; + +export class FederationKeysRaw extends BaseRaw { + async getKey(type: T['type']): Promise { + const keyResource = await this.findOne({ type }); + + if (!keyResource) { return null; } + + return keyResource.key; + } + + loadKey(keyData: NodeRSA.Key, type: T['type']): NodeRSA { + return new NodeRSA(keyData, `pkcs8-${ type }-pem`); + } + + async generateKeys(): Promise<{ privateKey: '' | NodeRSA | null; publicKey: '' | NodeRSA | null }> { + const key = new NodeRSA({ b: 512 }); + + key.generateKeyPair(); + + await this.deleteMany({}); + + await this.insertOne({ + type: 'private', + key: key.exportKey('pkcs8-private-pem').replace(/\n|\r/g, ''), + }); + + await this.insertOne({ + type: 'public', + key: key.exportKey('pkcs8-public-pem').replace(/\n|\r/g, ''), + }); + + return { + privateKey: await this.getPrivateKey(), + publicKey: await this.getPublicKey(), + }; + } + + async getPrivateKey(): Promise<'' | NodeRSA | null> { + const keyData = await this.getKey('private'); + + return keyData && this.loadKey(keyData, 'private'); + } + + getPrivateKeyString(): Promise { + return this.getKey('private'); + } + + async getPublicKey(): Promise<'' | NodeRSA | null> { + const keyData = await this.getKey('public'); + + return keyData && this.loadKey(keyData, 'public'); + } + + getPublicKeyString(): Promise { + return this.getKey('public'); + } +} diff --git a/app/models/server/raw/FederationServers.ts b/app/models/server/raw/FederationServers.ts new file mode 100644 index 0000000000000..c559b64137701 --- /dev/null +++ b/app/models/server/raw/FederationServers.ts @@ -0,0 +1,29 @@ +import { UpdateWriteOpResult } from 'mongodb'; + +import { Users } from './index'; +import { IFederationServer } from '../../../../definition/Federation'; +import { BaseRaw, IndexSpecification } from './BaseRaw'; + +export class FederationServersRaw extends BaseRaw { + protected indexes: IndexSpecification[] = [ + { key: { domain: 1 } }, + ] + + saveDomain(domain: string): Promise { + return this.updateOne({ domain }, { + $setOnInsert: { + domain, + }, + }, { upsert: true }); + } + + async refreshServers(): Promise { + const domains = await Users.getDistinctFederationDomains(); + + for await (const domain of domains) { + await this.saveDomain(domain); + } + + await this.deleteMany({ domain: { $nin: domains } }); + } +} diff --git a/app/models/server/raw/IntegrationHistory.ts b/app/models/server/raw/IntegrationHistory.ts index 53f7167db7962..923c11c6257e7 100644 --- a/app/models/server/raw/IntegrationHistory.ts +++ b/app/models/server/raw/IntegrationHistory.ts @@ -1,4 +1,12 @@ import { BaseRaw } from './BaseRaw'; import { IIntegrationHistory } from '../../../../definition/IIntegrationHistory'; -export class IntegrationHistoryRaw extends BaseRaw {} +export class IntegrationHistoryRaw extends BaseRaw { + removeByIntegrationId(integrationId: string): ReturnType['deleteMany']> { + return this.deleteMany({ 'integration._id': integrationId }); + } + + findOneByIntegrationIdAndHistoryId(integrationId: string, historyId: string): Promise { + return this.findOne({ 'integration._id': integrationId, _id: historyId }); + } +} diff --git a/app/models/server/raw/Integrations.js b/app/models/server/raw/Integrations.js deleted file mode 100644 index ab8e01a5ebae0..0000000000000 --- a/app/models/server/raw/Integrations.js +++ /dev/null @@ -1,14 +0,0 @@ -import { BaseRaw } from './BaseRaw'; - -export class IntegrationsRaw extends BaseRaw { - findOneByIdAndCreatedByIfExists({ _id, createdBy }) { - const query = { - _id, - }; - if (createdBy) { - query['_createdBy._id'] = createdBy; - } - - return this.findOne(query); - } -} diff --git a/app/models/server/raw/Integrations.ts b/app/models/server/raw/Integrations.ts new file mode 100644 index 0000000000000..521507d3bfeca --- /dev/null +++ b/app/models/server/raw/Integrations.ts @@ -0,0 +1,36 @@ +import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { IIntegration } from '../../../../definition/IIntegration'; + +export class IntegrationsRaw extends BaseRaw { + protected indexes: IndexSpecification[] = [ + { key: { type: 1 } }, + ] + + findOneByUrl(url: string): Promise { + return this.findOne({ url }); + } + + updateRoomName(oldRoomName: string, newRoomName: string): ReturnType['updateMany']> { + const hashedOldRoomName = `#${ oldRoomName }`; + const hashedNewRoomName = `#${ newRoomName }`; + + return this.updateMany({ + channel: hashedOldRoomName, + }, { + $set: { + 'channel.$': hashedNewRoomName, + }, + }); + } + + findOneByIdAndCreatedByIfExists({ _id, createdBy }: { _id: IIntegration['_id']; createdBy: IIntegration['_createdBy'] }): Promise { + return this.findOne({ + _id, + ...createdBy && { '_createdBy._id': createdBy }, + }); + } + + disableByUserId(userId: string): ReturnType['updateMany']> { + return this.updateMany({ userId }, { $set: { enabled: false } }); + } +} diff --git a/app/models/server/raw/Invites.ts b/app/models/server/raw/Invites.ts new file mode 100644 index 0000000000000..84d21e4e3e818 --- /dev/null +++ b/app/models/server/raw/Invites.ts @@ -0,0 +1,27 @@ +import type { UpdateWriteOpResult } from 'mongodb'; + +import { BaseRaw } from './BaseRaw'; +import { IInvite } from '../../../../definition/IInvite'; + +type T = IInvite; + +export class InvitesRaw extends BaseRaw { + findOneByUserRoomMaxUsesAndExpiration(userId: string, rid: string, maxUses: number, daysToExpire: number): Promise { + return this.findOne({ + rid, + userId, + days: daysToExpire, + maxUses, + ...daysToExpire > 0 ? { expires: { $gt: new Date() } } : {}, + ...maxUses > 0 ? { uses: { $lt: maxUses } } : {}, + }); + } + + increaseUsageById(_id: string, uses = 1): Promise { + return this.updateOne({ _id }, { + $inc: { + uses, + }, + }); + } +} diff --git a/app/models/server/raw/NotificationQueue.ts b/app/models/server/raw/NotificationQueue.ts index 9aedb96809028..cf80da0b747d8 100644 --- a/app/models/server/raw/NotificationQueue.ts +++ b/app/models/server/raw/NotificationQueue.ts @@ -1,25 +1,27 @@ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -import { - Collection, - ObjectId, -} from 'mongodb'; +import { UpdateWriteOpResult } from 'mongodb'; -import { BaseRaw } from './BaseRaw'; +import { BaseRaw, IndexSpecification } from './BaseRaw'; import { INotification } from '../../../../definition/INotification'; export class NotificationQueueRaw extends BaseRaw { - public readonly col!: Collection; + protected indexes: IndexSpecification[] = [ + { key: { uid: 1 } }, + { key: { ts: 1 }, expireAfterSeconds: 2 * 60 * 60 }, + { key: { schedule: 1 }, sparse: true }, + { key: { sending: 1 }, sparse: true }, + { key: { error: 1 }, sparse: true }, + ]; - unsetSendingById(_id: string) { - return this.col.updateOne({ _id }, { + unsetSendingById(_id: string): Promise { + return this.updateOne({ _id }, { $unset: { sending: 1, }, }); } - setErrorById(_id: string, error: any) { - return this.col.updateOne({ + setErrorById(_id: string, error: any): Promise { + return this.updateOne({ _id, }, { $set: { @@ -31,12 +33,8 @@ export class NotificationQueueRaw extends BaseRaw { }); } - removeById(_id: string) { - return this.col.deleteOne({ _id }); - } - - clearScheduleByUserId(uid: string) { - return this.col.updateMany({ + clearScheduleByUserId(uid: string): Promise { + return this.updateMany({ uid, schedule: { $exists: true }, }, { @@ -47,7 +45,7 @@ export class NotificationQueueRaw extends BaseRaw { } async clearQueueByUserId(uid: string): Promise { - const op = await this.col.deleteMany({ + const op = await this.deleteMany({ uid, }); @@ -83,11 +81,4 @@ export class NotificationQueueRaw extends BaseRaw { return result.value; } - - insertOne(data: Omit) { - return this.col.insertOne({ - _id: new ObjectId().toHexString(), - ...data, - }); - } } diff --git a/app/models/server/raw/OAuthApps.js b/app/models/server/raw/OAuthApps.js deleted file mode 100644 index 68c77a772cdda..0000000000000 --- a/app/models/server/raw/OAuthApps.js +++ /dev/null @@ -1,14 +0,0 @@ -import { BaseRaw } from './BaseRaw'; - -export class OAuthAppsRaw extends BaseRaw { - findOneAuthAppByIdOrClientId({ clientId, appId }) { - const query = {}; - if (clientId) { - query.clientId = clientId; - } - if (appId) { - query._id = appId; - } - return this.findOne(query); - } -} diff --git a/app/models/server/raw/OAuthApps.ts b/app/models/server/raw/OAuthApps.ts new file mode 100644 index 0000000000000..f70d88616b347 --- /dev/null +++ b/app/models/server/raw/OAuthApps.ts @@ -0,0 +1,11 @@ +import { IOAuthApps as T } from '../../../../definition/IOAuthApps'; +import { BaseRaw } from './BaseRaw'; + +export class OAuthAppsRaw extends BaseRaw { + findOneAuthAppByIdOrClientId({ clientId, appId }: {clientId: string; appId: string}): ReturnType['findOne']> { + return this.findOne({ + ...appId && { _id: appId }, + ...clientId && { clientId }, + }); + } +} diff --git a/app/models/server/raw/OEmbedCache.ts b/app/models/server/raw/OEmbedCache.ts new file mode 100644 index 0000000000000..586fb1d7040ca --- /dev/null +++ b/app/models/server/raw/OEmbedCache.ts @@ -0,0 +1,31 @@ +import { DeleteWriteOpResultObject } from 'mongodb'; + +import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { IOEmbedCache } from '../../../../definition/IOEmbedCache'; + +type T = IOEmbedCache; + +export class OEmbedCacheRaw extends BaseRaw { + protected indexes: IndexSpecification[] = [ + { key: { updatedAt: 1 } }, + ] + + async createWithIdAndData(_id: string, data: any): Promise { + const record = { + _id, + data, + updatedAt: new Date(), + }; + record._id = (await this.insertOne(record)).insertedId; + return record; + } + + removeAfterDate(date: Date): Promise { + const query = { + updatedAt: { + $lte: date, + }, + }; + return this.deleteMany(query); + } +} diff --git a/app/models/server/raw/Permissions.ts b/app/models/server/raw/Permissions.ts index d5321c82c80b0..0c5ae1f8533e4 100644 --- a/app/models/server/raw/Permissions.ts +++ b/app/models/server/raw/Permissions.ts @@ -2,4 +2,35 @@ import { BaseRaw } from './BaseRaw'; import { IPermission } from '../../../../definition/IPermission'; export class PermissionsRaw extends BaseRaw { + async createOrUpdate(name: string, roles: string[]): Promise { + const exists = await this.findOne>({ + _id: name, + roles, + }, { fields: { _id: 1 } }); + + if (exists) { + return exists._id; + } + + return this.update({ _id: name }, { $set: { roles } }, { upsert: true }).then((result) => result.result._id); + } + + async create(id: string, roles: string[]): Promise { + const exists = await this.findOneById>(id, { fields: { _id: 1 } }); + + if (exists) { + return exists._id; + } + + return this.update({ _id: id }, { $set: { roles } }, { upsert: true }).then((result) => result.result._id); + } + + + async addRole(permission: string, role: string): Promise { + await this.update({ _id: permission, roles: { $ne: role } }, { $addToSet: { roles: role } }); + } + + async removeRole(permission: string, role: string): Promise { + await this.update({ _id: permission, roles: role }, { $pull: { roles: role } }); + } } diff --git a/app/models/server/raw/ReadReceipts.ts b/app/models/server/raw/ReadReceipts.ts new file mode 100644 index 0000000000000..12763332ca3e2 --- /dev/null +++ b/app/models/server/raw/ReadReceipts.ts @@ -0,0 +1,15 @@ +import { Cursor } from 'mongodb'; + +import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { ReadReceipt } from '../../../../definition/ReadReceipt'; + +export class ReadReceiptsRaw extends BaseRaw { + protected indexes: IndexSpecification[] = [ + { key: { roomId: 1, userId: 1, messageId: 1 }, unique: true }, + { key: { messageId: 1 } }, + ]; + + findByMessageId(messageId: string): Cursor { + return this.find({ messageId }); + } +} diff --git a/app/models/server/raw/Reports.ts b/app/models/server/raw/Reports.ts new file mode 100644 index 0000000000000..9b47fdb8fe9f2 --- /dev/null +++ b/app/models/server/raw/Reports.ts @@ -0,0 +1,15 @@ +import { BaseRaw } from './BaseRaw'; +import { IReport } from '../../../../definition/IReport'; +import { IMessage } from '../../../../definition/IMessage'; + +export class ReportsRaw extends BaseRaw { + createWithMessageDescriptionAndUserId(message: IMessage, description: string, userId: string): ReturnType['insertOne']> { + const record: Pick = { + message, + description, + ts: new Date(), + userId, + }; + return this.insertOne(record); + } +} diff --git a/app/models/server/raw/Roles.js b/app/models/server/raw/Roles.js deleted file mode 100644 index 7e06551fde567..0000000000000 --- a/app/models/server/raw/Roles.js +++ /dev/null @@ -1,31 +0,0 @@ -import { BaseRaw } from './BaseRaw'; - -export class RolesRaw extends BaseRaw { - constructor(col, trash, models) { - super(col, trash); - - this.models = models; - } - - async isUserInRoles(userId, roles, scope) { - if (!Array.isArray(roles)) { - roles = [roles]; - } - - for (let i = 0, total = roles.length; i < total; i++) { - const roleName = roles[i]; - - // eslint-disable-next-line no-await-in-loop - const role = await this.findOne({ name: roleName }, { scope: 1 }); - const roleScope = (role && role.scope) || 'Users'; - const model = this.models[roleScope]; - - // eslint-disable-next-line no-await-in-loop - const permitted = await (model && model.isUserInRole && model.isUserInRole(userId, roleName, scope)); - if (permitted) { - return true; - } - } - return false; - } -} diff --git a/app/models/server/raw/Roles.ts b/app/models/server/raw/Roles.ts new file mode 100644 index 0000000000000..db7913eb7e2a4 --- /dev/null +++ b/app/models/server/raw/Roles.ts @@ -0,0 +1,200 @@ +import type { Collection, Cursor, FilterQuery, FindOneOptions, InsertOneWriteOpResult, UpdateWriteOpResult, WithId, WithoutProjection } from 'mongodb'; + +import { IRole, IUser } from '../../../../definition/IUser'; +import { BaseRaw } from './BaseRaw'; +import { SubscriptionsRaw } from './Subscriptions'; +import { UsersRaw } from './Users'; + +type ScopedModelRoles = { + Subscriptions: SubscriptionsRaw; + Users: UsersRaw; +} + +export class RolesRaw extends BaseRaw { + constructor(public readonly col: Collection, + private readonly models: ScopedModelRoles, public readonly trash?: Collection) { + super(col, trash); + } + + + findByUpdatedDate

(updatedAfterDate: Date, options: FindOneOptions

): Cursor

| Cursor { + const query = { + _updatedAt: { $gte: new Date(updatedAfterDate) }, + }; + + return this.find(query, options); + } + + + createOrUpdate(name: IRole['name'], scope: 'Users' | 'Subscriptions' = 'Users', description = '', protectedRole = true, mandatory2fa = false): Promise { + const queryData = { + name, + scope, + description, + protected: protectedRole, + mandatory2fa, + }; + + return this.updateOne({ _id: name }, { $set: queryData }, { upsert: true }); + } + + async addUserRoles(userId: IUser['_id'], roles: IRole['_id'][], scope?: string): Promise { + if (!Array.isArray(roles)) { + roles = [roles]; + process.env.NODE_ENV === 'development' && console.warn('[WARN] RolesRaw.addUserRoles: roles should be an array'); + } + + for await (const name of roles) { + const role = await this.findOne({ name }, { scope: 1 } as FindOneOptions); + + if (!role) { + process.env.NODE_ENV === 'development' && console.warn(`[WARN] RolesRaw.addUserRoles: role: ${ name } not found`); + continue; + } + switch (role.scope) { + case 'Subscriptions': + await this.models.Subscriptions.addRolesByUserId(userId, [name], scope); + break; + case 'Users': + default: + await this.models.Users.addRolesByUserId(userId, [name]); + } + } + return true; + } + + + async isUserInRoles(userId: IUser['_id'], roles: IRole['_id'][], scope?: string): Promise { + if (!Array.isArray(roles)) { // TODO: remove this check + roles = [roles]; + process.env.NODE_ENV === 'development' && console.warn('[WARN] RolesRaw.isUserInRoles: roles should be an array'); + } + + for await (const roleName of roles) { + const role = await this.findOne({ name: roleName }, { scope: 1 } as FindOneOptions); + + if (!role) { + continue; + } + + switch (role.scope) { + case 'Subscriptions': + if (await this.models.Subscriptions.isUserInRole(userId, roleName, scope)) { + return true; + } + break; + case 'Users': + default: + if (await this.models.Users.isUserInRole(userId, roleName)) { + return true; + } + } + } + return false; + } + + async removeUserRoles(userId: IUser['_id'], roles: IRole['_id'][], scope?: string): Promise { + if (!Array.isArray(roles)) { // TODO: remove this check + roles = [roles]; + process.env.NODE_ENV === 'development' && console.warn('[WARN] RolesRaw.removeUserRoles: roles should be an array'); + } + for await (const roleName of roles) { + const role = await this.findOne({ name: roleName }, { scope: 1 } as FindOneOptions); + + if (!role) { + continue; + } + + switch (role.scope) { + case 'Subscriptions': + scope && await this.models.Subscriptions.removeRolesByUserId(userId, [roleName], scope); + break; + case 'Users': + default: + await this.models.Users.removeRolesByUserId(userId, [roleName]); + } + } + return true; + } + + async findOneByIdOrName(_idOrName: IRole['_id'] | IRole['name'], options?: undefined): Promise; + + async findOneByIdOrName(_idOrName: IRole['_id'] | IRole['name'], options: WithoutProjection>): Promise; + + async findOneByIdOrName

(_idOrName: IRole['_id'] | IRole['name'], options: FindOneOptions

): Promise

; + + findOneByIdOrName

(_idOrName: IRole['_id'] | IRole['name'], options?: any): Promise { + const query: FilterQuery = { + $or: [{ + _id: _idOrName, + }, { + name: _idOrName, + }], + }; + + return this.findOne(query, options); + } + + updateById(_id: IRole['_id'], name: IRole['name'], scope: IRole['scope'], description: IRole['description'], mandatory2fa: IRole['mandatory2fa']): Promise { + const queryData = { + name, + scope, + description, + mandatory2fa, + }; + + return this.updateOne({ _id }, { $set: queryData }, { upsert: true }); + } + + + findUsersInRole(name: IRole['name'], scope?: string): Promise>; + + findUsersInRole(name: IRole['name'], scope: string | undefined, options: WithoutProjection>): Promise>; + + findUsersInRole

(name: IRole['name'], scope: string | undefined, options: FindOneOptions

): Promise>; + + async findUsersInRole

(name: IRole['name'], scope: string | undefined, options?: any | undefined): Promise | Cursor

> { + const role = await this.findOne({ name }, { scope: 1 } as FindOneOptions); + + if (!role) { + throw new Error('RolesRaw.findUsersInRole: role not found'); + } + + switch (role.scope) { + case 'Subscriptions': + return this.models.Subscriptions.findUsersInRoles([name], scope, options); + case 'Users': + default: + return this.models.Users.findUsersInRoles([name], options); + } + } + + + createWithRandomId(name: IRole['name'], scope: 'Users' | 'Subscriptions' = 'Users', description = '', protectedRole = true, mandatory2fa = false): Promise>> { + const role = { + name, + scope, + description, + protected: protectedRole, + mandatory2fa, + }; + + return this.insertOne(role); + } + + + async canAddUserToRole(uid: IUser['_id'], name: IRole['name'], scope?: string): Promise { + const role = await this.findOne({ name }, { fields: { scope: 1 } } as FindOneOptions); + if (!role) { + return false; + } + + switch (role.scope) { + case 'Subscriptions': + return this.models.Subscriptions.isUserInRoleScope(uid, scope); + case 'Users': + default: + return this.models.Users.isUserInRoleScope(uid); + } + } +} diff --git a/app/models/server/raw/ServerEvents.ts b/app/models/server/raw/ServerEvents.ts index f36b44983e193..1bb1342ed8850 100644 --- a/app/models/server/raw/ServerEvents.ts +++ b/app/models/server/raw/ServerEvents.ts @@ -1,38 +1,28 @@ -import { Collection, ObjectId } from 'mongodb'; - -import { BaseRaw } from './BaseRaw'; +import { BaseRaw, IndexSpecification } from './BaseRaw'; import { IServerEvent, IServerEventType } from '../../../../definition/IServerEvent'; -import { IUser } from '../../../../definition/IUser'; export class ServerEventsRaw extends BaseRaw { - public readonly col!: Collection; - - async insertOne(data: Omit): Promise { - if (data.u) { - data.u = { _id: data.u._id, username: data.u.username } as IUser; - } - return this.col.insertOne({ - _id: new ObjectId().toHexString(), - ...data, - }); - } + protected indexes: IndexSpecification[] = [ + { key: { t: 1, ip: 1, ts: -1 } }, + { key: { t: 1, 'u.username': 1, ts: -1 } }, + ] async findLastFailedAttemptByIp(ip: string): Promise { - return this.col.findOne({ + return this.findOne({ ip, t: IServerEventType.FAILED_LOGIN_ATTEMPT, }, { sort: { ts: -1 } }); } async findLastFailedAttemptByUsername(username: string): Promise { - return this.col.findOne({ + return this.findOne({ 'u.username': username, t: IServerEventType.FAILED_LOGIN_ATTEMPT, }, { sort: { ts: -1 } }); } async countFailedAttemptsByUsernameSince(username: string, since: Date): Promise { - return this.col.find({ + return this.find({ 'u.username': username, t: IServerEventType.FAILED_LOGIN_ATTEMPT, ts: { @@ -42,7 +32,7 @@ export class ServerEventsRaw extends BaseRaw { } countFailedAttemptsByIpSince(ip: string, since: Date): Promise { - return this.col.find({ + return this.find({ ip, t: IServerEventType.FAILED_LOGIN_ATTEMPT, ts: { @@ -52,14 +42,14 @@ export class ServerEventsRaw extends BaseRaw { } countFailedAttemptsByIp(ip: string): Promise { - return this.col.find({ + return this.find({ ip, t: IServerEventType.FAILED_LOGIN_ATTEMPT, }).count(); } countFailedAttemptsByUsername(username: string): Promise { - return this.col.find({ + return this.find({ 'u.username': username, t: IServerEventType.FAILED_LOGIN_ATTEMPT, }).count(); diff --git a/app/models/server/raw/Sessions.js b/app/models/server/raw/Sessions.js deleted file mode 100644 index 965604fcd0b54..0000000000000 --- a/app/models/server/raw/Sessions.js +++ /dev/null @@ -1,285 +0,0 @@ -import { BaseRaw } from './BaseRaw'; -import Sessions from '../models/Sessions'; - -const matchBasedOnDate = (start, end) => { - if (start.year === end.year && start.month === end.month) { - return { - year: start.year, - month: start.month, - day: { $gte: start.day, $lte: end.day }, - }; - } - - if (start.year === end.year) { - return { - year: start.year, - $and: [{ - $or: [{ - month: { $gt: start.month }, - }, { - month: start.month, - day: { $gte: start.day }, - }], - }, { - $or: [{ - month: { $lt: end.month }, - }, { - month: end.month, - day: { $lte: end.day }, - }], - }], - }; - } - - return { - $and: [{ - $or: [{ - year: { $gt: start.year }, - }, { - year: start.year, - month: { $gt: start.month }, - }, { - year: start.year, - month: start.month, - day: { $gte: start.day }, - }], - }, { - $or: [{ - year: { $lt: end.year }, - }, { - year: end.year, - month: { $lt: end.month }, - }, { - year: end.year, - month: end.month, - day: { $lte: end.day }, - }], - }], - }; -}; - -const getGroupSessionsByHour = (_id) => { - const isOpenSession = { $not: ['$session.closedAt'] }; - const isAfterLoginAt = { $gte: ['$range', { $hour: '$session.loginAt' }] }; - const isBeforeClosedAt = { $lte: ['$range', { $hour: '$session.closedAt' }] }; - - const listGroup = { - $group: { - _id, - usersList: { - $addToSet: { - $cond: [ - { - $or: [ - { $and: [isOpenSession, isAfterLoginAt] }, - { $and: [isAfterLoginAt, isBeforeClosedAt] }, - ], - }, - '$session.userId', - '$$REMOVE', - ], - }, - }, - }, - }; - - const countGroup = { - $addFields: { - users: { $size: '$usersList' }, - }, - }; - - return { listGroup, countGroup }; -}; - -const getSortByFullDate = () => ({ - year: -1, - month: -1, - day: -1, -}); - -const getProjectionByFullDate = () => ({ - day: '$_id.day', - month: '$_id.month', - year: '$_id.year', -}); - -export class SessionsRaw extends BaseRaw { - getActiveUsersBetweenDates({ start, end }) { - return this.col.aggregate([ - { - $match: { - ...matchBasedOnDate(start, end), - type: 'user_daily', - }, - }, - { - $group: { - _id: '$userId', - }, - }, - ]).toArray(); - } - - async findLastLoginByIp(ip) { - return (await this.col.find({ - ip, - }, { - sort: { loginAt: -1 }, - limit: 1, - }).toArray())[0]; - } - - getActiveUsersOfPeriodByDayBetweenDates({ start, end }) { - return this.col.aggregate([ - { - $match: { - ...matchBasedOnDate(start, end), - type: 'user_daily', - mostImportantRole: { $ne: 'anonymous' }, - }, - }, - { - $group: { - _id: { - day: '$day', - month: '$month', - year: '$year', - userId: '$userId', - }, - }, - }, - { - $group: { - _id: { - day: '$_id.day', - month: '$_id.month', - year: '$_id.year', - }, - usersList: { - $addToSet: '$_id.userId', - }, - users: { $sum: 1 }, - }, - }, - { - $project: { - _id: 0, - ...getProjectionByFullDate(), - usersList: 1, - users: 1, - }, - }, - { - $sort: { - ...getSortByFullDate(), - }, - }, - ]).toArray(); - } - - getBusiestTimeWithinHoursPeriod({ start, end, groupSize }) { - const match = { - $match: { - type: 'computed-session', - loginAt: { $gte: start, $lte: end }, - }, - }; - const rangeProject = { - $project: { - range: { - $range: [0, 24, groupSize], - }, - session: '$$ROOT', - }, - }; - const unwind = { - $unwind: '$range', - }; - const groups = getGroupSessionsByHour('$range'); - const presentationProject = { - $project: { - _id: 0, - hour: '$_id', - users: 1, - }, - }; - const sort = { - $sort: { - hour: -1, - }, - }; - return this.col.aggregate([match, rangeProject, unwind, groups.listGroup, groups.countGroup, presentationProject, sort]).toArray(); - } - - getTotalOfSessionsByDayBetweenDates({ start, end }) { - return this.col.aggregate([ - { - $match: { - ...matchBasedOnDate(start, end), - type: 'user_daily', - mostImportantRole: { $ne: 'anonymous' }, - }, - }, - { - $group: { - _id: { year: '$year', month: '$month', day: '$day' }, - users: { $sum: 1 }, - }, - }, - { - $project: { - _id: 0, - ...getProjectionByFullDate(), - users: 1, - }, - }, - { - $sort: { - ...getSortByFullDate(), - }, - }, - ]).toArray(); - } - - getTotalOfSessionByHourAndDayBetweenDates({ start, end }) { - const match = { - $match: { - type: 'computed-session', - loginAt: { $gte: start, $lte: end }, - }, - }; - const rangeProject = { - $project: { - range: { - $range: [ - { $hour: '$loginAt' }, - { $sum: [{ $ifNull: [{ $hour: '$closedAt' }, 23] }, 1] }], - }, - session: '$$ROOT', - }, - - }; - const unwind = { - $unwind: '$range', - }; - const groups = getGroupSessionsByHour({ range: '$range', day: '$session.day', month: '$session.month', year: '$session.year' }); - const presentationProject = { - $project: { - _id: 0, - hour: '$_id.range', - ...getProjectionByFullDate(), - users: 1, - }, - }; - const sort = { - $sort: { - ...getSortByFullDate(), - hour: -1, - }, - }; - return this.col.aggregate([match, rangeProject, unwind, groups.listGroup, groups.countGroup, presentationProject, sort]).toArray(); - } -} - -export default new SessionsRaw(Sessions.model.rawCollection()); diff --git a/app/models/server/models/Sessions.tests.js b/app/models/server/raw/Sessions.tests.js similarity index 99% rename from app/models/server/models/Sessions.tests.js rename to app/models/server/raw/Sessions.tests.js index ba0b94c608925..7dd157e9e6819 100644 --- a/app/models/server/models/Sessions.tests.js +++ b/app/models/server/raw/Sessions.tests.js @@ -4,8 +4,6 @@ import assert from 'assert'; import { MongoMemoryServer } from 'mongodb-memory-server'; -import './Sessions.mocks.js'; - const { MongoClient } = require('mongodb'); const { aggregates } = require('./Sessions'); diff --git a/app/models/server/models/Sessions.js b/app/models/server/raw/Sessions.ts similarity index 50% rename from app/models/server/models/Sessions.js rename to app/models/server/raw/Sessions.ts index ffb43d6566a5e..86e97ebe76a70 100644 --- a/app/models/server/models/Sessions.js +++ b/app/models/server/raw/Sessions.ts @@ -1,8 +1,118 @@ -import { Base } from './_Base'; -import { readSecondaryPreferred } from '../../../../server/database/readSecondaryPreferred'; +import { AggregationCursor, BulkWriteOperation, BulkWriteOpResultObject, Collection, IndexSpecification, UpdateWriteOpResult, FilterQuery } from 'mongodb'; + +import { ISession as T } from '../../../../definition/ISession'; +import { BaseRaw, ModelOptionalId } from './BaseRaw'; + +type DestructuredDate = {year: number; month: number; day: number}; +type DestructuredDateWithType = {year: number; month: number; day: number; type?: 'month' | 'week'}; +type DestructuredRange = {start: DestructuredDate; end: DestructuredDate}; +type FullReturn = { year: number; month: number; day: number; data: T[] }; + +const matchBasedOnDate = (start: DestructuredDate, end: DestructuredDate): FilterQuery => { + if (start.year === end.year && start.month === end.month) { + return { + year: start.year, + month: start.month, + day: { $gte: start.day, $lte: end.day }, + }; + } + + if (start.year === end.year) { + return { + year: start.year, + $and: [{ + $or: [{ + month: { $gt: start.month }, + }, { + month: start.month, + day: { $gte: start.day }, + }], + }, { + $or: [{ + month: { $lt: end.month }, + }, { + month: end.month, + day: { $lte: end.day }, + }], + }], + }; + } + + return { + $and: [{ + $or: [{ + year: { $gt: start.year }, + }, { + year: start.year, + month: { $gt: start.month }, + }, { + year: start.year, + month: start.month, + day: { $gte: start.day }, + }], + }, { + $or: [{ + year: { $lt: end.year }, + }, { + year: end.year, + month: { $lt: end.month }, + }, { + year: end.year, + month: end.month, + day: { $lte: end.day }, + }], + }], + }; +}; + +const getGroupSessionsByHour = (_id: { range: string; day: string; month: string; year: string } | string): {listGroup: object; countGroup: object} => { + const isOpenSession = { $not: ['$session.closedAt'] }; + const isAfterLoginAt = { $gte: ['$range', { $hour: '$session.loginAt' }] }; + const isBeforeClosedAt = { $lte: ['$range', { $hour: '$session.closedAt' }] }; + + const listGroup = { + $group: { + _id, + usersList: { + $addToSet: { + $cond: [ + { + $or: [ + { $and: [isOpenSession, isAfterLoginAt] }, + { $and: [isAfterLoginAt, isBeforeClosedAt] }, + ], + }, + '$session.userId', + '$$REMOVE', + ], + }, + }, + }, + }; + + const countGroup = { + $addFields: { + users: { $size: '$usersList' }, + }, + }; + + return { listGroup, countGroup }; +}; + +const getSortByFullDate = (): { year: number; month: number; day: number } => ({ + year: -1, + month: -1, + day: -1, +}); + +const getProjectionByFullDate = (): { day: string; month: string; year: string } => ({ + day: '$_id.day', + month: '$_id.month', + year: '$_id.year', +}); export const aggregates = { - dailySessionsOfYesterday(collection, { year, month, day }) { + dailySessionsOfYesterday(collection: Collection, { year, month, day }: DestructuredDate): AggregationCursor { return collection.aggregate([{ $match: { userId: { $exists: true }, @@ -91,7 +201,7 @@ export const aggregates = { }], { allowDiskUse: true }); }, - getUniqueUsersOfYesterday(collection, { year, month, day }) { + async getUniqueUsersOfYesterday(collection: Collection, { year, month, day }: DestructuredDate): Promise { return collection.aggregate([{ $match: { year, @@ -153,7 +263,7 @@ export const aggregates = { }]).toArray(); }, - getUniqueUsersOfLastMonthOrWeek(collection, { year, month, day, type = 'month' }) { + async getUniqueUsersOfLastMonthOrWeek(collection: Collection, { year, month, day, type = 'month' }: DestructuredDateWithType): Promise { return collection.aggregate([{ $match: { type: 'user_daily', @@ -223,7 +333,7 @@ export const aggregates = { }], { allowDiskUse: true }).toArray(); }, - getMatchOfLastMonthOrWeek({ year, month, day, type = 'month' }) { + getMatchOfLastMonthOrWeek({ year, month, day, type = 'month' }: DestructuredDateWithType): FilterQuery { let startOfPeriod; if (type === 'month') { @@ -298,7 +408,7 @@ export const aggregates = { }; }, - getUniqueDevicesOfLastMonthOrWeek(collection, { year, month, day, type = 'month' }) { + async getUniqueDevicesOfLastMonthOrWeek(collection: Collection, { year, month, day, type = 'month' }: DestructuredDateWithType): Promise { return collection.aggregate([{ $match: { type: 'user_daily', @@ -336,7 +446,7 @@ export const aggregates = { }], { allowDiskUse: true }).toArray(); }, - getUniqueDevicesOfYesterday(collection, { year, month, day }) { + getUniqueDevicesOfYesterday(collection: Collection, { year, month, day }: DestructuredDate): Promise { return collection.aggregate([{ $match: { year, @@ -376,7 +486,7 @@ export const aggregates = { }]).toArray(); }, - getUniqueOSOfLastMonthOrWeek(collection, { year, month, day, type = 'month' }) { + getUniqueOSOfLastMonthOrWeek(collection: Collection, { year, month, day, type = 'month' }: DestructuredDateWithType): Promise { return collection.aggregate([{ $match: { type: 'user_daily', @@ -415,7 +525,7 @@ export const aggregates = { }], { allowDiskUse: true }).toArray(); }, - getUniqueOSOfYesterday(collection, { year, month, day }) { + getUniqueOSOfYesterday(collection: Collection, { year, month, day }: DestructuredDate): Promise { return collection.aggregate([{ $match: { year, @@ -457,25 +567,208 @@ export const aggregates = { }, }; -export class Sessions extends Base { - constructor(...args) { - super(...args); - - this.tryEnsureIndex({ instanceId: 1, sessionId: 1, year: 1, month: 1, day: 1 }); - this.tryEnsureIndex({ instanceId: 1, sessionId: 1, userId: 1 }); - this.tryEnsureIndex({ instanceId: 1, sessionId: 1 }); - this.tryEnsureIndex({ sessionId: 1 }); - this.tryEnsureIndex({ userId: 1 }); - this.tryEnsureIndex({ year: 1, month: 1, day: 1, type: 1 }); - this.tryEnsureIndex({ type: 1 }); - this.tryEnsureIndex({ ip: 1, loginAt: 1 }); - this.tryEnsureIndex({ _computedAt: 1 }, { expireAfterSeconds: 60 * 60 * 24 * 45 }); - - const db = this.model.rawDatabase(); - this.secondaryCollection = db.collection(this.model._name, { readPreference: readSecondaryPreferred(db) }); +export class SessionsRaw extends BaseRaw { + protected indexes: IndexSpecification[] = [ + { key: { instanceId: 1, sessionId: 1, year: 1, month: 1, day: 1 } }, + { key: { instanceId: 1, sessionId: 1, userId: 1 } }, + { key: { instanceId: 1, sessionId: 1 } }, + { key: { sessionId: 1 } }, + { key: { userId: 1 } }, + { key: { year: 1, month: 1, day: 1, type: 1 } }, + { key: { type: 1 } }, + { key: { ip: 1, loginAt: 1 } }, + { key: { _computedAt: 1 }, expireAfterSeconds: 60 * 60 * 24 * 45 }, + ] + + private secondaryCollection: Collection; + + constructor( + public readonly col: Collection, + public readonly colSecondary: Collection, + public readonly trash?: Collection, + ) { + super(col, trash); + + this.secondaryCollection = colSecondary; + } + + async getActiveUsersBetweenDates({ start, end }: DestructuredRange): Promise { + return this.col.aggregate([ + { + $match: { + ...matchBasedOnDate(start, end), + type: 'user_daily', + }, + }, + { + $group: { + _id: '$userId', + }, + }, + ]).toArray(); + } + + async findLastLoginByIp(ip: string): Promise { + return this.findOne({ + ip, + }, { + sort: { loginAt: -1 }, + limit: 1, + }); + } + + async getActiveUsersOfPeriodByDayBetweenDates({ start, end }: DestructuredRange): Promise { + return this.col.aggregate([ + { + $match: { + ...matchBasedOnDate(start, end), + type: 'user_daily', + mostImportantRole: { $ne: 'anonymous' }, + }, + }, + { + $group: { + _id: { + day: '$day', + month: '$month', + year: '$year', + userId: '$userId', + }, + }, + }, + { + $group: { + _id: { + day: '$_id.day', + month: '$_id.month', + year: '$_id.year', + }, + usersList: { + $addToSet: '$_id.userId', + }, + users: { $sum: 1 }, + }, + }, + { + $project: { + _id: 0, + ...getProjectionByFullDate(), + usersList: 1, + users: 1, + }, + }, + { + $sort: { + ...getSortByFullDate(), + }, + }, + ]).toArray(); + } + + async getBusiestTimeWithinHoursPeriod({ start, end, groupSize }: DestructuredRange & {groupSize: number}): Promise { + const match = { + $match: { + type: 'computed-session', + loginAt: { $gte: start, $lte: end }, + }, + }; + const rangeProject = { + $project: { + range: { + $range: [0, 24, groupSize], + }, + session: '$$ROOT', + }, + }; + const unwind = { + $unwind: '$range', + }; + const groups = getGroupSessionsByHour('$range'); + const presentationProject = { + $project: { + _id: 0, + hour: '$_id', + users: 1, + }, + }; + const sort = { + $sort: { + hour: -1, + }, + }; + return this.col.aggregate([match, rangeProject, unwind, groups.listGroup, groups.countGroup, presentationProject, sort]).toArray(); } - getUniqueUsersOfYesterday() { + async getTotalOfSessionsByDayBetweenDates({ start, end }: DestructuredRange): Promise { + return this.col.aggregate([ + { + $match: { + ...matchBasedOnDate(start, end), + type: 'user_daily', + mostImportantRole: { $ne: 'anonymous' }, + }, + }, + { + $group: { + _id: { year: '$year', month: '$month', day: '$day' }, + users: { $sum: 1 }, + }, + }, + { + $project: { + _id: 0, + ...getProjectionByFullDate(), + users: 1, + }, + }, + { + $sort: { + ...getSortByFullDate(), + }, + }, + ]).toArray(); + } + + async getTotalOfSessionByHourAndDayBetweenDates({ start, end }: DestructuredRange): Promise { + const match = { + $match: { + type: 'computed-session', + loginAt: { $gte: start, $lte: end }, + }, + }; + const rangeProject = { + $project: { + range: { + $range: [ + { $hour: '$loginAt' }, + { $sum: [{ $ifNull: [{ $hour: '$closedAt' }, 23] }, 1] }], + }, + session: '$$ROOT', + }, + + }; + const unwind = { + $unwind: '$range', + }; + const groups = getGroupSessionsByHour({ range: '$range', day: '$session.day', month: '$session.month', year: '$session.year' }); + const presentationProject = { + $project: { + _id: 0, + hour: '$_id.range', + ...getProjectionByFullDate(), + users: 1, + }, + }; + const sort = { + $sort: { + ...getSortByFullDate(), + hour: -1, + }, + }; + return this.col.aggregate([match, rangeProject, unwind, groups.listGroup, groups.countGroup, presentationProject, sort]).toArray(); + } + + async getUniqueUsersOfYesterday(): Promise { const date = new Date(); date.setDate(date.getDate() - 1); @@ -487,11 +780,11 @@ export class Sessions extends Base { year, month, day, - data: Promise.await(aggregates.getUniqueUsersOfYesterday(this.secondaryCollection, { year, month, day })), + data: await aggregates.getUniqueUsersOfYesterday(this.secondaryCollection, { year, month, day }), }; } - getUniqueUsersOfLastMonth() { + async getUniqueUsersOfLastMonth(): Promise { const date = new Date(); date.setDate(date.getDate() - 1); @@ -503,11 +796,11 @@ export class Sessions extends Base { year, month, day, - data: Promise.await(aggregates.getUniqueUsersOfLastMonthOrWeek(this.secondaryCollection, { year, month, day })), + data: await aggregates.getUniqueUsersOfLastMonthOrWeek(this.secondaryCollection, { year, month, day }), }; } - getUniqueUsersOfLastWeek() { + async getUniqueUsersOfLastWeek(): Promise { const date = new Date(); date.setDate(date.getDate() - 1); @@ -519,11 +812,11 @@ export class Sessions extends Base { year, month, day, - data: Promise.await(aggregates.getUniqueUsersOfLastMonthOrWeek(this.secondaryCollection, { year, month, day, type: 'week' })), + data: await aggregates.getUniqueUsersOfLastMonthOrWeek(this.secondaryCollection, { year, month, day, type: 'week' }), }; } - getUniqueDevicesOfYesterday() { + async getUniqueDevicesOfYesterday(): Promise { const date = new Date(); date.setDate(date.getDate() - 1); @@ -535,11 +828,11 @@ export class Sessions extends Base { year, month, day, - data: Promise.await(aggregates.getUniqueDevicesOfYesterday(this.secondaryCollection, { year, month, day })), + data: await aggregates.getUniqueDevicesOfYesterday(this.secondaryCollection, { year, month, day }), }; } - getUniqueDevicesOfLastMonth() { + async getUniqueDevicesOfLastMonth(): Promise { const date = new Date(); date.setDate(date.getDate() - 1); @@ -551,11 +844,11 @@ export class Sessions extends Base { year, month, day, - data: Promise.await(aggregates.getUniqueDevicesOfLastMonthOrWeek(this.secondaryCollection, { year, month, day })), + data: await aggregates.getUniqueDevicesOfLastMonthOrWeek(this.secondaryCollection, { year, month, day }), }; } - getUniqueDevicesOfLastWeek() { + async getUniqueDevicesOfLastWeek(): Promise { const date = new Date(); date.setDate(date.getDate() - 1); @@ -567,11 +860,11 @@ export class Sessions extends Base { year, month, day, - data: Promise.await(aggregates.getUniqueDevicesOfLastMonthOrWeek(this.secondaryCollection, { year, month, day, type: 'week' })), + data: await aggregates.getUniqueDevicesOfLastMonthOrWeek(this.secondaryCollection, { year, month, day, type: 'week' }), }; } - getUniqueOSOfYesterday() { + async getUniqueOSOfYesterday(): Promise { const date = new Date(); date.setDate(date.getDate() - 1); @@ -583,11 +876,11 @@ export class Sessions extends Base { year, month, day, - data: Promise.await(aggregates.getUniqueOSOfYesterday(this.secondaryCollection, { year, month, day })), + data: await aggregates.getUniqueOSOfYesterday(this.secondaryCollection, { year, month, day }), }; } - getUniqueOSOfLastMonth() { + async getUniqueOSOfLastMonth(): Promise { const date = new Date(); date.setDate(date.getDate() - 1); @@ -599,11 +892,11 @@ export class Sessions extends Base { year, month, day, - data: Promise.await(aggregates.getUniqueOSOfLastMonthOrWeek(this.secondaryCollection, { year, month, day })), + data: await aggregates.getUniqueOSOfLastMonthOrWeek(this.secondaryCollection, { year, month, day }), }; } - getUniqueOSOfLastWeek() { + async getUniqueOSOfLastWeek(): Promise { const date = new Date(); date.setDate(date.getDate() - 1); @@ -615,11 +908,11 @@ export class Sessions extends Base { year, month, day, - data: Promise.await(aggregates.getUniqueOSOfLastMonthOrWeek(this.secondaryCollection, { year, month, day, type: 'week' })), + data: await aggregates.getUniqueOSOfLastMonthOrWeek(this.secondaryCollection, { year, month, day, type: 'week' }), }; } - createOrUpdate(data = {}) { + async createOrUpdate(data: T): Promise { const { year, month, day, sessionId, instanceId } = data; if (!year || !month || !day || !sessionId || !instanceId) { @@ -628,19 +921,19 @@ export class Sessions extends Base { const now = new Date(); - return this.upsert({ instanceId, sessionId, year, month, day }, { + return this.updateOne({ instanceId, sessionId, year, month, day }, { $set: data, $setOnInsert: { createdAt: now, }, - }); + }, { upsert: true }); } - closeByInstanceIdAndSessionId(instanceId, sessionId) { + async closeByInstanceIdAndSessionId(instanceId: string, sessionId: string): Promise { const query = { instanceId, sessionId, - closedAt: { $exists: 0 }, + closedAt: { $exists: false }, }; const closeTime = new Date(); @@ -651,27 +944,27 @@ export class Sessions extends Base { }, }; - return this.update(query, update); + return this.updateOne(query, update); } - updateActiveSessionsByDateAndInstanceIdAndIds({ year, month, day } = {}, instanceId, sessions, data = {}) { + async updateActiveSessionsByDateAndInstanceIdAndIds({ year, month, day }: Partial = {}, instanceId: string, sessions: string[], data = {}): Promise { const query = { instanceId, year, month, day, sessionId: { $in: sessions }, - closedAt: { $exists: 0 }, + closedAt: { $exists: false }, }; const update = { $set: data, }; - return this.update(query, update, { multi: true }); + return this.updateMany(query, update); } - logoutByInstanceIdAndSessionIdAndUserId(instanceId, sessionId, userId) { + async logoutByInstanceIdAndSessionIdAndUserId(instanceId: string, sessionId: string, userId: string): Promise { const query = { instanceId, sessionId, @@ -686,15 +979,15 @@ export class Sessions extends Base { }, }; - return this.update(query, update, { multi: true }); + return this.updateMany(query, update); } - createBatch(sessions) { + async createBatch(sessions: ModelOptionalId[]): Promise { if (!sessions || sessions.length === 0) { return; } - const ops = []; + const ops: BulkWriteOperation[] = []; sessions.forEach((doc) => { const { year, month, day, sessionId, instanceId } = doc; delete doc._id; @@ -710,8 +1003,6 @@ export class Sessions extends Base { }); }); - return this.model.rawCollection().bulkWrite(ops, { ordered: false }); + return this.col.bulkWrite(ops, { ordered: false }); } } - -export default new Sessions('sessions'); diff --git a/app/models/server/raw/Settings.ts b/app/models/server/raw/Settings.ts index dd475ed9a1312..2d3a11b2ccea7 100644 --- a/app/models/server/raw/Settings.ts +++ b/app/models/server/raw/Settings.ts @@ -1,18 +1,29 @@ -import { Cursor, WriteOpResult } from 'mongodb'; +import { Cursor, FilterQuery, UpdateQuery, WriteOpResult } from 'mongodb'; import { BaseRaw } from './BaseRaw'; -import { ISetting } from '../../../../definition/ISetting'; +import { ISetting, ISettingColor, ISettingSelectOption } from '../../../../definition/ISetting'; -type T = ISetting; -export class SettingsRaw extends BaseRaw { +export class SettingsRaw extends BaseRaw { async getValueById(_id: string): Promise { const setting = await this.findOne>({ _id }, { projection: { value: 1 } }); return setting?.value; } - findOneNotHiddenById(_id: string): Promise { + findNotHidden({ updatedAfter }: { updatedAfter?: Date } = {}): Cursor { + const query: FilterQuery = { + hidden: { $ne: true }, + }; + + if (updatedAfter) { + query._updatedAt = { $gt: updatedAfter }; + } + + return this.find(query); + } + + findOneNotHiddenById(_id: string): Promise { const query = { _id, hidden: { $ne: true }, @@ -21,7 +32,7 @@ export class SettingsRaw extends BaseRaw { return this.findOne(query); } - findByIds(_id: string[] | string = []): Cursor { + findByIds(_id: string[] | string = []): Cursor { if (typeof _id === 'string') { _id = [_id]; } @@ -35,7 +46,50 @@ export class SettingsRaw extends BaseRaw { return this.find(query); } - updateValueById(_id: string, value: any): Promise { + updateValueById(_id: string, value: T): Promise { + const query = { + blocked: { $ne: true }, + value: { $ne: value }, + _id, + }; + + const update = { + $set: { + value, + }, + }; + + return this.update(query, update); + } + + updateOptionsById(_id: ISetting['_id'], options: UpdateQuery['$set']): Promise { + const query = { + blocked: { $ne: true }, + _id, + }; + + const update = { $set: options }; + + return this.update(query, update); + } + + updateValueNotHiddenById(_id: ISetting['_id'], value: T): Promise { + const query = { + _id, + hidden: { $ne: true }, + blocked: { $ne: true }, + }; + + const update = { + $set: { + value, + }, + }; + + return this.update(query, update); + } + + updateValueAndEditorById(_id: ISetting['_id'], value: T, editor: ISettingColor['editor']): Promise { const query = { blocked: { $ne: true }, value: { $ne: value }, @@ -45,9 +99,58 @@ export class SettingsRaw extends BaseRaw { const update = { $set: { value, + editor, }, }; return this.update(query, update); } + + findNotHiddenPublic(ids: ISetting['_id'][] = []): Cursor< T extends ISettingColor ? Pick : Pick> { + const filter: FilterQuery = { + hidden: { $ne: true }, + public: true, + }; + + if (ids.length > 0) { + filter._id = { $in: ids }; + } + + return this.find(filter, { fields: { _id: 1, value: 1, editor: 1, enterprise: 1, invalidValue: 1, modules: 1, requiredOnWizard: 1 } }) as any; + } + + findSetupWizardSettings(): Cursor { + return this.find({ wizard: { $exists: true } }); + } + + addOptionValueById(_id: ISetting['_id'], option: ISettingSelectOption): Promise { + const query = { + blocked: { $ne: true }, + _id, + }; + + const { key, i18nLabel } = option; + const update = { + $addToSet: { + values: { + key, + i18nLabel, + }, + }, + }; + + return this.update(query, update); + } + + findNotHiddenPublicUpdatedAfter(updatedAt: Date): Cursor { + const filter = { + hidden: { $ne: true }, + public: true, + _updatedAt: { + $gt: updatedAt, + }, + }; + + return this.find(filter, { projection: { _id: 1, value: 1, editor: 1, enterprise: 1, invalidValue: 1, modules: 1, requiredOnWizard: 1 } }); + } } diff --git a/app/models/server/raw/SmarshHistory.ts b/app/models/server/raw/SmarshHistory.ts new file mode 100644 index 0000000000000..70c2e3df482d7 --- /dev/null +++ b/app/models/server/raw/SmarshHistory.ts @@ -0,0 +1,8 @@ +import { BaseRaw } from './BaseRaw'; +import { ISmarshHistory } from '../../../../definition/ISmarshHistory'; + +type T = ISmarshHistory; + +export class SmarshHistoryRaw extends BaseRaw { + +} diff --git a/app/models/server/raw/Statistics.js b/app/models/server/raw/Statistics.js deleted file mode 100644 index 15b3cf39404a0..0000000000000 --- a/app/models/server/raw/Statistics.js +++ /dev/null @@ -1,14 +0,0 @@ -import { BaseRaw } from './BaseRaw'; - -export class StatisticsRaw extends BaseRaw { - async findLast() { - const options = { - sort: { - createdAt: -1, - }, - limit: 1, - }; - const records = await this.find({}, options).toArray(); - return records && records[0]; - } -} diff --git a/app/models/server/raw/Statistics.ts b/app/models/server/raw/Statistics.ts new file mode 100644 index 0000000000000..b3b915a9ebcea --- /dev/null +++ b/app/models/server/raw/Statistics.ts @@ -0,0 +1,21 @@ +import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { IStatistic } from '../../../../definition/IStatistic'; + +type T = IStatistic; + +export class StatisticsRaw extends BaseRaw { + protected indexes: IndexSpecification[] = [ + { key: { createdAt: -1 } }, + ] + + async findLast(): Promise { + const options = { + sort: { + createdAt: -1, + }, + limit: 1, + }; + const records = await this.find({}, options).toArray(); + return records && records[0]; + } +} diff --git a/app/models/server/raw/Subscriptions.ts b/app/models/server/raw/Subscriptions.ts index 544af563af91a..4a02f61857694 100644 --- a/app/models/server/raw/Subscriptions.ts +++ b/app/models/server/raw/Subscriptions.ts @@ -1,10 +1,20 @@ -import { FindOneOptions, Cursor, UpdateQuery, FilterQuery } from 'mongodb'; +import { FindOneOptions, Cursor, UpdateQuery, FilterQuery, UpdateWriteOpResult, Collection, WithoutProjection } from 'mongodb'; +import { compact } from 'lodash'; import { BaseRaw } from './BaseRaw'; import { ISubscription } from '../../../../definition/ISubscription'; +import { IRole, IUser } from '../../../../definition/IUser'; +import { IRoom } from '../../../../definition/IRoom'; +import { UsersRaw } from './Users'; type T = ISubscription; export class SubscriptionsRaw extends BaseRaw { + constructor(public readonly col: Collection, + private readonly models: { Users: UsersRaw }, + public readonly trash?: Collection) { + super(col, trash); + } + findOneByRoomIdAndUserId(rid: string, uid: string, options: FindOneOptions = {}): Promise { const query = { rid, @@ -47,7 +57,7 @@ export class SubscriptionsRaw extends BaseRaw { return cursor.count(); } - async isUserInRole(uid: string, roleName: string, rid: string): Promise { + async isUserInRole(uid: IUser['_id'], roleName: IRole['name'], rid?: IRoom['_id']): Promise { if (rid == null) { return null; } @@ -80,4 +90,77 @@ export class SubscriptionsRaw extends BaseRaw { return this.update(query, update, options); } + + removeRolesByUserId(uid: IUser['_id'], roles: IRole['name'][], rid: IRoom['_id']): Promise { + const query = { + 'u._id': uid, + rid, + }; + + const update = { + $pullAll: { + roles, + }, + }; + + return this.updateOne(query, update); + } + + + findUsersInRoles(name: IRole['name'][], rid: string | undefined): Promise>; + + findUsersInRoles(name: IRole['name'][], rid: string | undefined, options: WithoutProjection>): Promise>; + + findUsersInRoles

(name: IRole['name'][], rid: string | undefined, options: FindOneOptions

): Promise>; + + async findUsersInRoles

(roles: IRole['name'][], rid: IRoom['_id'] | undefined, options?: FindOneOptions

): Promise> { + const query = { + roles: { $in: roles }, + ...rid && { rid }, + }; + + const subscriptions = await this.find(query).toArray(); + + const users = compact(subscriptions.map((subscription) => subscription.u?._id).filter(Boolean)); + + return !options ? this.models.Users.find({ _id: { $in: users } }) : this.models.Users.find({ _id: { $in: users } } as FilterQuery, options); + } + + + addRolesByUserId(uid: IUser['_id'], roles: IRole['name'][], rid?: IRoom['_id']): Promise { + if (!Array.isArray(roles)) { + roles = [roles]; + process.env.NODE_ENV === 'development' && console.warn('[WARN] Subscriptions.addRolesByUserId: roles should be an array'); + } + + const query = { + 'u._id': uid, + rid, + }; + + const update = { + $addToSet: { + roles: { $each: roles }, + }, + }; + + return this.updateOne(query, update); + } + + async isUserInRoleScope(uid: IUser['_id'], rid?: IRoom['_id']): Promise { + const query = { + 'u._id': uid, + rid, + }; + + if (!rid) { + return false; + } + const options = { + fields: { _id: 1 }, + }; + + const found = await this.findOne(query, options); + return !!found; + } } diff --git a/app/models/server/raw/Uploads.ts b/app/models/server/raw/Uploads.ts new file mode 100644 index 0000000000000..ad2fd67247c91 --- /dev/null +++ b/app/models/server/raw/Uploads.ts @@ -0,0 +1,116 @@ +// TODO: Lib imports should not exists inside the raw models +import { escapeRegExp } from '@rocket.chat/string-helpers'; +import { CollectionInsertOneOptions, Cursor, DeleteWriteOpResultObject, FilterQuery, InsertOneWriteOpResult, UpdateOneOptions, UpdateQuery, UpdateWriteOpResult, WithId, WriteOpResult } from 'mongodb'; + +import { BaseRaw, IndexSpecification, InsertionModel } from './BaseRaw'; +import { IUpload as T } from '../../../../definition/IUpload'; + +const fillTypeGroup = (fileData: Partial): void => { + if (!fileData.type) { + return; + } + + fileData.typeGroup = fileData.type.split('/').shift(); +}; + +export class UploadsRaw extends BaseRaw { + protected indexes: IndexSpecification[] = [ + { key: { rid: 1 } }, + { key: { uploadedAt: 1 } }, + { key: { typeGroup: 1 } }, + ] + + findNotHiddenFilesOfRoom(roomId: string, searchText: string, fileType: string, limit: number): Cursor { + const fileQuery = { + rid: roomId, + complete: true, + uploading: false, + _hidden: { + $ne: true, + }, + + ...searchText && { name: { $regex: new RegExp(escapeRegExp(searchText), 'i') } }, + ...fileType && fileType !== 'all' && { typeGroup: fileType }, + }; + + const fileOptions = { + limit, + sort: { + uploadedAt: -1, + }, + projection: { + _id: 1, + userId: 1, + rid: 1, + name: 1, + description: 1, + type: 1, + url: 1, + uploadedAt: 1, + typeGroup: 1, + }, + }; + + return this.find(fileQuery, fileOptions); + } + + insert(fileData: InsertionModel, options?: CollectionInsertOneOptions): Promise>> { + fillTypeGroup(fileData); + return super.insertOne(fileData, options); + } + + update(filter: FilterQuery, update: UpdateQuery | Partial, options?: UpdateOneOptions & { multi?: boolean }): Promise { + if ('$set' in update && update.$set) { + fillTypeGroup(update.$set); + } else if ('type' in update && update.type) { + fillTypeGroup(update); + } + + return super.update(filter, update, options); + } + + async insertFileInit(userId: string, store: string, file: {name: string}, extra: object): Promise>> { + const fileData = { + userId, + store, + complete: false, + uploading: true, + progress: 0, + extension: file.name.split('.').pop(), + uploadedAt: new Date(), + ...file, + ...extra, + }; + + fillTypeGroup(fileData); + return this.insert(fileData); + } + + async updateFileComplete(fileId: string, userId: string, file: object): Promise { + if (!fileId) { + return; + } + + const filter = { + _id: fileId, + userId, + }; + + const update = { + $set: { + complete: true, + uploading: false, + progress: 1, + }, + }; + + update.$set = Object.assign(file, update.$set); + + fillTypeGroup(update.$set); + return this.updateOne(filter, update); + } + + async deleteFile(fileId: string): Promise { + return this.deleteOne({ _id: fileId }); + } +} diff --git a/app/models/server/raw/UserDataFiles.ts b/app/models/server/raw/UserDataFiles.ts new file mode 100644 index 0000000000000..684135c9d57a3 --- /dev/null +++ b/app/models/server/raw/UserDataFiles.ts @@ -0,0 +1,29 @@ +import { FindOneOptions, InsertOneWriteOpResult, WithId, WithoutProjection } from 'mongodb'; + +import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { IUserDataFile as T } from '../../../../definition/IUserDataFile'; + +export class UserDataFilesRaw extends BaseRaw { + protected indexes: IndexSpecification[] = [ + { key: { userId: 1 } }, + ] + + findLastFileByUser(userId: string, options: WithoutProjection> = {}): Promise { + const query = { + userId, + }; + + options.sort = { _updatedAt: -1 }; + return this.findOne(query, options); + } + + // INSERT + create(data: T): Promise>> { + const userDataFile = { + createdAt: new Date(), + ...data, + }; + + return this.insertOne(userDataFile); + } +} diff --git a/app/models/server/raw/Users.js b/app/models/server/raw/Users.js index 6de2fb2f7e56d..0d6ce77ed78ca 100644 --- a/app/models/server/raw/Users.js +++ b/app/models/server/raw/Users.js @@ -11,6 +11,24 @@ export class UsersRaw extends BaseRaw { }; } + addRolesByUserId(uid, roles) { + if (!Array.isArray(roles)) { + roles = [roles]; + process.env.NODE_ENV === 'development' && console.warn('[WARN] Users.addRolesByUserId: roles should be an array'); + } + + const query = { + _id: uid, + }; + + const update = { + $addToSet: { + roles: { $each: roles }, + }, + }; + return this.updateOne(query, update); + } + findUsersInRoles(roles, scope, options) { roles = [].concat(roles); @@ -706,4 +724,31 @@ export class UsersRaw extends BaseRaw { $pullAll: { __rooms: rids }, }, { multi: true }); } + + removeRolesByUserId(uid, roles) { + const query = { + _id: uid, + }; + + const update = { + $pullAll: { + roles, + }, + }; + + return this.updateOne(query, update); + } + + async isUserInRoleScope(uid) { + const query = { + _id: uid, + }; + + const options = { + fields: { _id: 1 }, + }; + + const found = await this.findOne(query, options); + return !!found; + } } diff --git a/app/models/server/raw/UsersSessions.ts b/app/models/server/raw/UsersSessions.ts index b89feaa9deb3f..3560f1e175d1a 100644 --- a/app/models/server/raw/UsersSessions.ts +++ b/app/models/server/raw/UsersSessions.ts @@ -1,4 +1,16 @@ import { BaseRaw } from './BaseRaw'; import { IUserSession } from '../../../../definition/IUserSession'; -export class UsersSessionsRaw extends BaseRaw {} +export class UsersSessionsRaw extends BaseRaw { + clearConnectionsFromInstanceId(instanceId: string[]): ReturnType['updateMany']> { + return this.col.updateMany({}, { + $pull: { + connections: { + instanceId: { + $nin: instanceId, + }, + }, + }, + }); + } +} diff --git a/app/models/server/raw/WebdavAccounts.js b/app/models/server/raw/WebdavAccounts.js deleted file mode 100644 index bcd87761c2674..0000000000000 --- a/app/models/server/raw/WebdavAccounts.js +++ /dev/null @@ -1,8 +0,0 @@ -import { BaseRaw } from './BaseRaw'; - -export class WebdavAccountsRaw extends BaseRaw { - findWithUserId(user_id, options) { - const query = { user_id }; - return this.find(query, options); - } -} diff --git a/app/models/server/raw/WebdavAccounts.ts b/app/models/server/raw/WebdavAccounts.ts new file mode 100644 index 0000000000000..c189cb7e3799b --- /dev/null +++ b/app/models/server/raw/WebdavAccounts.ts @@ -0,0 +1,46 @@ +/* eslint-disable @typescript-eslint/camelcase */ +/** + * Webdav Accounts model + */ +import type { Collection, FindOneOptions, Cursor, DeleteWriteOpResultObject } from 'mongodb'; + +import { BaseRaw } from './BaseRaw'; +import { IWebdavAccount } from '../../../../definition/IWebdavAccount'; + +type T = IWebdavAccount; + +export class WebdavAccountsRaw extends BaseRaw { + constructor( + public readonly col: Collection, + public readonly trash?: Collection, + ) { + super(col, trash); + + this.col.createIndex({ user_id: 1 }); + } + + findOneByIdAndUserId(_id: string, user_id: string, options: FindOneOptions): Promise { + return this.findOne({ _id, user_id }, options); + } + + findOneByUserIdServerUrlAndUsername({ + user_id, + server_url, + username, + }: { + user_id: string; + server_url: string; + username: string; + }, options: FindOneOptions): Promise { + return this.findOne({ user_id, server_url, username }, options); + } + + findWithUserId(user_id: string, options: FindOneOptions): Cursor { + const query = { user_id }; + return this.find(query, options); + } + + removeByUserAndId(_id: string, user_id: string): Promise { + return this.deleteOne({ _id, user_id }); + } +} diff --git a/app/models/server/raw/_Users.d.ts b/app/models/server/raw/_Users.d.ts new file mode 100644 index 0000000000000..891392ee3f7e4 --- /dev/null +++ b/app/models/server/raw/_Users.d.ts @@ -0,0 +1,14 @@ +import { UpdateWriteOpResult } from 'mongodb'; + +import { IRole, IUser } from '../../../../definition/IUser'; +import { BaseRaw } from './BaseRaw'; + +export interface IUserRaw extends BaseRaw { + isUserInRole(uid: IUser['_id'], name: IRole['name']): Promise; + removeRolesByUserId(uid: IUser['_id'], roles: IRole['name'][]): Promise; + findUsersInRoles(roles: IRole['name'][]): Promise; + addRolesByUserId(uid: IUser['_id'], roles: IRole['name'][]): Promise; + isUserInRoleScope(uid: IUser['_id']): Promise; + new(...args: any): IUser; +} +export const UsersRaw: IUserRaw; diff --git a/app/models/server/raw/index.ts b/app/models/server/raw/index.ts index 605218c636bea..34789c62ce2fe 100644 --- a/app/models/server/raw/index.ts +++ b/app/models/server/raw/index.ts @@ -1,83 +1,81 @@ -import PermissionsModel from '../models/Permissions'; +import { MongoInternals } from 'meteor/mongo'; + +import { AvatarsRaw } from './Avatars'; +import { AnalyticsRaw } from './Analytics'; +import { api } from '../../../../server/sdk/api'; +import { BaseDbWatch, trash } from '../models/_BaseDb'; +import { CredentialTokensRaw } from './CredentialTokens'; +import { CustomSoundsRaw } from './CustomSounds'; +import { CustomUserStatusRaw } from './CustomUserStatus'; +import { EmailInboxRaw } from './EmailInbox'; +import { EmailMessageHistoryRaw } from './EmailMessageHistory'; +import { EmojiCustomRaw } from './EmojiCustom'; +import { ExportOperationsRaw } from './ExportOperations'; +import { FederationKeysRaw } from './FederationKeys'; +import { FederationServersRaw } from './FederationServers'; +import { ImportDataRaw } from './ImportData'; +import { initWatchers } from '../../../../server/modules/watchers/watchers.module'; +import { InstanceStatusRaw } from './InstanceStatus'; +import { IntegrationHistoryRaw } from './IntegrationHistory'; +import { IntegrationsRaw } from './Integrations'; +import { InvitesRaw } from './Invites'; +import { LivechatAgentActivityRaw } from './LivechatAgentActivity'; +import { LivechatBusinessHoursRaw } from './LivechatBusinessHours'; +import { LivechatCustomFieldRaw } from './LivechatCustomField'; +import { LivechatDepartmentAgentsRaw } from './LivechatDepartmentAgents'; +import { LivechatDepartmentRaw } from './LivechatDepartment'; +import { LivechatExternalMessageRaw } from './LivechatExternalMessages'; +import { LivechatInquiryRaw } from './LivechatInquiry'; +import { LivechatRoomsRaw } from './LivechatRooms'; +import { LivechatTriggerRaw } from './LivechatTrigger'; +import { LivechatVisitorsRaw } from './LivechatVisitors'; +import { LoginServiceConfigurationRaw } from './LoginServiceConfiguration'; +import { MessagesRaw } from './Messages'; +import { NotificationQueueRaw } from './NotificationQueue'; +import { OAuthAppsRaw } from './OAuthApps'; +import { OEmbedCacheRaw } from './OEmbedCache'; +import { OmnichannelQueueRaw } from './OmnichannelQueue'; import { PermissionsRaw } from './Permissions'; -import RolesModel from '../models/Roles'; +import { readSecondaryPreferred } from '../../../../server/database/readSecondaryPreferred'; +import { ReadReceiptsRaw } from './ReadReceipts'; +import { ReportsRaw } from './Reports'; import { RolesRaw } from './Roles'; -import SubscriptionsModel from '../models/Subscriptions'; -import { SubscriptionsRaw } from './Subscriptions'; -import SettingsModel from '../models/Settings'; +import { RoomsRaw } from './Rooms'; +import { ServerEventsRaw } from './ServerEvents'; +import { SessionsRaw } from './Sessions'; import { SettingsRaw } from './Settings'; -import UsersModel from '../models/Users'; +import { SmarshHistoryRaw } from './SmarshHistory'; +import { StatisticsRaw } from './Statistics'; +import { SubscriptionsRaw } from './Subscriptions'; import { UsersRaw } from './Users'; -import SessionsModel from '../models/Sessions'; -import { SessionsRaw } from './Sessions'; -import RoomsModel from '../models/Rooms'; -import { RoomsRaw } from './Rooms'; +import { UsersSessionsRaw } from './UsersSessions'; +import { UserDataFilesRaw } from './UserDataFiles'; +import { UploadsRaw } from './Uploads'; +import { WebdavAccountsRaw } from './WebdavAccounts'; +import ImportDataModel from '../models/ImportData'; +import LivechatAgentActivityModel from '../models/LivechatAgentActivity'; +import LivechatBusinessHoursModel from '../models/LivechatBusinessHours'; import LivechatCustomFieldModel from '../models/LivechatCustomField'; -import { LivechatCustomFieldRaw } from './LivechatCustomField'; -import LivechatTriggerModel from '../models/LivechatTrigger'; -import { LivechatTriggerRaw } from './LivechatTrigger'; -import LivechatDepartmentModel from '../models/LivechatDepartment'; -import { LivechatDepartmentRaw } from './LivechatDepartment'; import LivechatDepartmentAgentsModel from '../models/LivechatDepartmentAgents'; -import { LivechatDepartmentAgentsRaw } from './LivechatDepartmentAgents'; -import LivechatRoomsModel from '../models/LivechatRooms'; -import { LivechatRoomsRaw } from './LivechatRooms'; -import MessagesModel from '../models/Messages'; -import { MessagesRaw } from './Messages'; +import LivechatDepartmentModel from '../models/LivechatDepartment'; import LivechatExternalMessagesModel from '../models/LivechatExternalMessages'; -import { LivechatExternalMessageRaw } from './LivechatExternalMessages'; -import LivechatVisitorsModel from '../models/LivechatVisitors'; -import { LivechatVisitorsRaw } from './LivechatVisitors'; import LivechatInquiryModel from '../models/LivechatInquiry'; -import { LivechatInquiryRaw } from './LivechatInquiry'; -import IntegrationsModel from '../models/Integrations'; -import { IntegrationsRaw } from './Integrations'; -import EmojiCustomModel from '../models/EmojiCustom'; -import { EmojiCustomRaw } from './EmojiCustom'; -import WebdavAccountsModel from '../models/WebdavAccounts'; -import { WebdavAccountsRaw } from './WebdavAccounts'; -import OAuthAppsModel from '../models/OAuthApps'; -import { OAuthAppsRaw } from './OAuthApps'; -import CustomSoundsModel from '../models/CustomSounds'; -import { CustomSoundsRaw } from './CustomSounds'; -import CustomUserStatusModel from '../models/CustomUserStatus'; -import { CustomUserStatusRaw } from './CustomUserStatus'; -import LivechatAgentActivityModel from '../models/LivechatAgentActivity'; -import { LivechatAgentActivityRaw } from './LivechatAgentActivity'; -import StatisticsModel from '../models/Statistics'; -import { StatisticsRaw } from './Statistics'; -import NotificationQueueModel from '../models/NotificationQueue'; -import { NotificationQueueRaw } from './NotificationQueue'; -import LivechatBusinessHoursModel from '../models/LivechatBusinessHours'; -import { LivechatBusinessHoursRaw } from './LivechatBusinessHours'; -import ServerEventModel from '../models/ServerEvents'; -import { UsersSessionsRaw } from './UsersSessions'; -import UsersSessionsModel from '../models/UsersSessions'; -import { ServerEventsRaw } from './ServerEvents'; -import { trash } from '../models/_BaseDb'; +import LivechatRoomsModel from '../models/LivechatRooms'; +import LivechatTriggerModel from '../models/LivechatTrigger'; +import LivechatVisitorsModel from '../models/LivechatVisitors'; import LoginServiceConfigurationModel from '../models/LoginServiceConfiguration'; -import { LoginServiceConfigurationRaw } from './LoginServiceConfiguration'; -import { InstanceStatusRaw } from './InstanceStatus'; -import InstanceStatusModel from '../models/InstanceStatus'; -import { IntegrationHistoryRaw } from './IntegrationHistory'; -import IntegrationHistoryModel from '../models/IntegrationHistory'; +import MessagesModel from '../models/Messages'; import OmnichannelQueueModel from '../models/OmnichannelQueue'; -import { OmnichannelQueueRaw } from './OmnichannelQueue'; -import EmailInboxModel from '../models/EmailInbox'; -import { EmailInboxRaw } from './EmailInbox'; -import EmailMessageHistoryModel from '../models/EmailMessageHistory'; -import { EmailMessageHistoryRaw } from './EmailMessageHistory'; -import { api } from '../../../../server/sdk/api'; -import { initWatchers } from '../../../../server/modules/watchers/watchers.module'; -import ImportDataModel from '../models/ImportData'; -import { ImportDataRaw } from './ImportData'; +import RoomsModel from '../models/Rooms'; +import SettingsModel from '../models/Settings'; +import SubscriptionsModel from '../models/Subscriptions'; +import UsersModel from '../models/Users'; const trashCollection = trash.rawCollection(); -export const Permissions = new PermissionsRaw(PermissionsModel.model.rawCollection(), trashCollection); -export const Subscriptions = new SubscriptionsRaw(SubscriptionsModel.model.rawCollection(), trashCollection); -export const Settings = new SettingsRaw(SettingsModel.model.rawCollection(), trashCollection); export const Users = new UsersRaw(UsersModel.model.rawCollection(), trashCollection); +export const Subscriptions = new SubscriptionsRaw(SubscriptionsModel.model.rawCollection(), { Users }, trashCollection); +export const Settings = new SettingsRaw(SettingsModel.model.rawCollection(), trashCollection); export const Rooms = new RoomsRaw(RoomsModel.model.rawCollection(), trashCollection); export const LivechatCustomField = new LivechatCustomFieldRaw(LivechatCustomFieldModel.model.rawCollection(), trashCollection); export const LivechatTrigger = new LivechatTriggerRaw(LivechatTriggerModel.model.rawCollection(), trashCollection); @@ -88,44 +86,56 @@ export const Messages = new MessagesRaw(MessagesModel.model.rawCollection(), tra export const LivechatExternalMessage = new LivechatExternalMessageRaw(LivechatExternalMessagesModel.model.rawCollection(), trashCollection); export const LivechatVisitors = new LivechatVisitorsRaw(LivechatVisitorsModel.model.rawCollection(), trashCollection); export const LivechatInquiry = new LivechatInquiryRaw(LivechatInquiryModel.model.rawCollection(), trashCollection); -export const Integrations = new IntegrationsRaw(IntegrationsModel.model.rawCollection(), trashCollection); -export const EmojiCustom = new EmojiCustomRaw(EmojiCustomModel.model.rawCollection(), trashCollection); -export const WebdavAccounts = new WebdavAccountsRaw(WebdavAccountsModel.model.rawCollection(), trashCollection); -export const OAuthApps = new OAuthAppsRaw(OAuthAppsModel.model.rawCollection(), trashCollection); -export const CustomSounds = new CustomSoundsRaw(CustomSoundsModel.model.rawCollection(), trashCollection); -export const CustomUserStatus = new CustomUserStatusRaw(CustomUserStatusModel.model.rawCollection(), trashCollection); export const LivechatAgentActivity = new LivechatAgentActivityRaw(LivechatAgentActivityModel.model.rawCollection(), trashCollection); -export const Statistics = new StatisticsRaw(StatisticsModel.model.rawCollection(), trashCollection); -export const NotificationQueue = new NotificationQueueRaw(NotificationQueueModel.model.rawCollection(), trashCollection); export const LivechatBusinessHours = new LivechatBusinessHoursRaw(LivechatBusinessHoursModel.model.rawCollection(), trashCollection); -export const ServerEvents = new ServerEventsRaw(ServerEventModel.model.rawCollection(), trashCollection); -export const Roles = new RolesRaw(RolesModel.model.rawCollection(), trashCollection, { Users, Subscriptions }); -export const UsersSessions = new UsersSessionsRaw(UsersSessionsModel.model.rawCollection(), trashCollection); +// export const Roles = new RolesRaw(RolesModel.model.rawCollection(), { Users, Subscriptions }, trashCollection); export const LoginServiceConfiguration = new LoginServiceConfigurationRaw(LoginServiceConfigurationModel.model.rawCollection(), trashCollection); -export const InstanceStatus = new InstanceStatusRaw(InstanceStatusModel.model.rawCollection(), trashCollection); -export const IntegrationHistory = new IntegrationHistoryRaw(IntegrationHistoryModel.model.rawCollection(), trashCollection); -export const Sessions = new SessionsRaw(SessionsModel.model.rawCollection(), trashCollection); export const OmnichannelQueue = new OmnichannelQueueRaw(OmnichannelQueueModel.model.rawCollection(), trashCollection); -export const EmailInbox = new EmailInboxRaw(EmailInboxModel.model.rawCollection(), trashCollection); -export const EmailMessageHistory = new EmailMessageHistoryRaw(EmailMessageHistoryModel.model.rawCollection(), trashCollection); export const ImportData = new ImportDataRaw(ImportDataModel.model.rawCollection(), trashCollection); +const { db } = MongoInternals.defaultRemoteCollectionDriver().mongo; +const prefix = 'rocketchat_'; + +export const Avatars = new AvatarsRaw(db.collection(`${ prefix }avatars`), trashCollection); +export const Analytics = new AnalyticsRaw(db.collection(`${ prefix }analytics`, { readPreference: readSecondaryPreferred(db) }), trashCollection); +export const CustomSounds = new CustomSoundsRaw(db.collection(`${ prefix }custom_sounds`), trashCollection); +export const CustomUserStatus = new CustomUserStatusRaw(db.collection(`${ prefix }custom_user_status`), trashCollection); +export const CredentialTokens = new CredentialTokensRaw(db.collection(`${ prefix }credential_tokens`), trashCollection); +export const EmailInbox = new EmailInboxRaw(db.collection(`${ prefix }email_inbox`), trashCollection); +export const EmailMessageHistory = new EmailMessageHistoryRaw(db.collection(`${ prefix }email_message_history`), trashCollection); +export const EmojiCustom = new EmojiCustomRaw(db.collection(`${ prefix }custom_emoji`), trashCollection); +export const ExportOperations = new ExportOperationsRaw(db.collection(`${ prefix }export_operations`), trashCollection); +export const FederationKeys = new FederationKeysRaw(db.collection(`${ prefix }federation_keys`), trashCollection); +export const FederationServers = new FederationServersRaw(db.collection(`${ prefix }federation_servers`), trashCollection); +export const InstanceStatus = new InstanceStatusRaw(db.collection('instances'), trashCollection, { preventSetUpdatedAt: true }); +export const Integrations = new IntegrationsRaw(db.collection(`${ prefix }integrations`), trashCollection); +export const IntegrationHistory = new IntegrationHistoryRaw(db.collection(`${ prefix }integration_history`), trashCollection); +export const Invites = new InvitesRaw(db.collection(`${ prefix }invites`), trashCollection); +export const NotificationQueue = new NotificationQueueRaw(db.collection(`${ prefix }notification_queue`), trashCollection); +export const OAuthApps = new OAuthAppsRaw(db.collection(`${ prefix }oauth_apps`), trashCollection); +export const OEmbedCache = new OEmbedCacheRaw(db.collection(`${ prefix }oembed_cache`), trashCollection); +export const Permissions = new PermissionsRaw(db.collection(`${ prefix }permissions`), trashCollection); +export const ReadReceipts = new ReadReceiptsRaw(db.collection(`${ prefix }read_receipts`), trashCollection); +export const Reports = new ReportsRaw(db.collection(`${ prefix }reports`), trashCollection); +export const ServerEvents = new ServerEventsRaw(db.collection(`${ prefix }server_events`), trashCollection); +export const Sessions = new SessionsRaw(db.collection(`${ prefix }sessions`), db.collection(`${ prefix }sessions`, { readPreference: readSecondaryPreferred(db) }), trashCollection); +export const Roles = new RolesRaw(db.collection(`${ prefix }roles`), { Users, Subscriptions }, trashCollection); +export const SmarshHistory = new SmarshHistoryRaw(db.collection(`${ prefix }smarsh_history`), trashCollection); +export const Statistics = new StatisticsRaw(db.collection(`${ prefix }statistics`), trashCollection); +export const UsersSessions = new UsersSessionsRaw(db.collection('usersSessions'), trashCollection, { preventSetUpdatedAt: true }); +export const UserDataFiles = new UserDataFilesRaw(db.collection(`${ prefix }user_data_files`), trashCollection); +export const Uploads = new UploadsRaw(db.collection(`${ prefix }uploads`), trashCollection); +export const WebdavAccounts = new WebdavAccountsRaw(db.collection(`${ prefix }webdav_accounts`), trashCollection); + const map = { [Messages.col.collectionName]: MessagesModel, [Users.col.collectionName]: UsersModel, [Subscriptions.col.collectionName]: SubscriptionsModel, [Settings.col.collectionName]: SettingsModel, - [Roles.col.collectionName]: RolesModel, - [Permissions.col.collectionName]: PermissionsModel, [LivechatInquiry.col.collectionName]: LivechatInquiryModel, [LivechatDepartmentAgents.col.collectionName]: LivechatDepartmentAgentsModel, - [UsersSessions.col.collectionName]: UsersSessionsModel, [Rooms.col.collectionName]: RoomsModel, [LoginServiceConfiguration.col.collectionName]: LoginServiceConfigurationModel, - [InstanceStatus.col.collectionName]: InstanceStatusModel, - [IntegrationHistory.col.collectionName]: IntegrationHistoryModel, - [Integrations.col.collectionName]: IntegrationsModel, - [EmailInbox.col.collectionName]: EmailInboxModel, }; if (!process.env.DISABLE_DB_WATCH) { @@ -148,7 +158,7 @@ if (!process.env.DISABLE_DB_WATCH) { }; initWatchers(models, api.broadcastLocal.bind(api), (model, fn) => { - const meteorModel = map[model.col.collectionName]; + const meteorModel = map[model.col.collectionName] || new BaseDbWatch(model.col.collectionName); if (!meteorModel) { return; } diff --git a/app/oauth2-server-config/server/admin/methods/addOAuthApp.js b/app/oauth2-server-config/server/admin/methods/addOAuthApp.js index cb4d73e19f27c..688409e5ddf4c 100644 --- a/app/oauth2-server-config/server/admin/methods/addOAuthApp.js +++ b/app/oauth2-server-config/server/admin/methods/addOAuthApp.js @@ -3,11 +3,12 @@ import { Random } from 'meteor/random'; import _ from 'underscore'; import { hasPermission } from '../../../../authorization'; -import { Users, OAuthApps } from '../../../../models'; +import { Users } from '../../../../models/server'; +import { OAuthApps } from '../../../../models/server/raw'; import { parseUriList } from '../functions/parseUriList'; Meteor.methods({ - addOAuthApp(application) { + async addOAuthApp(application) { if (!hasPermission(this.userId, 'manage-oauth-apps')) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'addOAuthApp' }); } @@ -31,7 +32,7 @@ Meteor.methods({ application.clientSecret = Random.secret(); application._createdAt = new Date(); application._createdBy = Users.findOne(this.userId, { fields: { username: 1 } }); - application._id = OAuthApps.insert(application); + application._id = (await OAuthApps.insertOne(application)).insertedId; return application; }, }); diff --git a/app/oauth2-server-config/server/admin/methods/deleteOAuthApp.js b/app/oauth2-server-config/server/admin/methods/deleteOAuthApp.js index 6c0b1e665de64..d1df82d95704a 100644 --- a/app/oauth2-server-config/server/admin/methods/deleteOAuthApp.js +++ b/app/oauth2-server-config/server/admin/methods/deleteOAuthApp.js @@ -1,18 +1,18 @@ import { Meteor } from 'meteor/meteor'; import { hasPermission } from '../../../../authorization'; -import { OAuthApps } from '../../../../models'; +import { OAuthApps } from '../../../../models/server/raw'; Meteor.methods({ - deleteOAuthApp(applicationId) { + async deleteOAuthApp(applicationId) { if (!hasPermission(this.userId, 'manage-oauth-apps')) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'deleteOAuthApp' }); } - const application = OAuthApps.findOne(applicationId); + const application = await OAuthApps.findOneById(applicationId); if (application == null) { throw new Meteor.Error('error-application-not-found', 'Application not found', { method: 'deleteOAuthApp' }); } - OAuthApps.remove({ _id: applicationId }); + await OAuthApps.deleteOne({ _id: applicationId }); return true; }, }); diff --git a/app/oauth2-server-config/server/admin/methods/updateOAuthApp.js b/app/oauth2-server-config/server/admin/methods/updateOAuthApp.js index 007f5be2e95c4..3a7f88dda09e7 100644 --- a/app/oauth2-server-config/server/admin/methods/updateOAuthApp.js +++ b/app/oauth2-server-config/server/admin/methods/updateOAuthApp.js @@ -2,11 +2,12 @@ import { Meteor } from 'meteor/meteor'; import _ from 'underscore'; import { hasPermission } from '../../../../authorization'; -import { OAuthApps, Users } from '../../../../models'; +import { OAuthApps } from '../../../../models/server/raw'; +import { Users } from '../../../../models/server'; import { parseUriList } from '../functions/parseUriList'; Meteor.methods({ - updateOAuthApp(applicationId, application) { + async updateOAuthApp(applicationId, application) { if (!hasPermission(this.userId, 'manage-oauth-apps')) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'updateOAuthApp' }); } @@ -19,7 +20,7 @@ Meteor.methods({ if (!_.isBoolean(application.active)) { throw new Meteor.Error('error-invalid-arguments', 'Invalid arguments', { method: 'updateOAuthApp' }); } - const currentApplication = OAuthApps.findOne(applicationId); + const currentApplication = await OAuthApps.findOneById(applicationId); if (currentApplication == null) { throw new Meteor.Error('error-application-not-found', 'Application not found', { method: 'updateOAuthApp' }); } @@ -30,7 +31,7 @@ Meteor.methods({ throw new Meteor.Error('error-invalid-redirectUri', 'Invalid redirectUri', { method: 'updateOAuthApp' }); } - OAuthApps.update(applicationId, { + await OAuthApps.updateOne({ _id: applicationId }, { $set: { name: application.name, active: application.active, @@ -43,6 +44,6 @@ Meteor.methods({ }), }, }); - return OAuthApps.findOne(applicationId); + return OAuthApps.findOneById(applicationId); }, }); diff --git a/app/oauth2-server-config/server/oauth/default-services.js b/app/oauth2-server-config/server/oauth/default-services.js deleted file mode 100644 index d39489c9ec850..0000000000000 --- a/app/oauth2-server-config/server/oauth/default-services.js +++ /dev/null @@ -1,17 +0,0 @@ -import { OAuthApps } from '../../../models'; - -if (!OAuthApps.findOne('zapier')) { - OAuthApps.insert({ - _id: 'zapier', - name: 'Zapier', - active: true, - clientId: 'zapier', - clientSecret: 'RTK6TlndaCIolhQhZ7_KHIGOKj41RnlaOq_o-7JKwLr', - redirectUri: 'https://zapier.com/dashboard/auth/oauth/return/RocketChatDevAPI/', - _createdAt: new Date(), - _createdBy: { - _id: 'system', - username: 'system', - }, - }); -} diff --git a/app/oauth2-server-config/server/oauth/default-services.ts b/app/oauth2-server-config/server/oauth/default-services.ts new file mode 100644 index 0000000000000..05fd8f5c5d350 --- /dev/null +++ b/app/oauth2-server-config/server/oauth/default-services.ts @@ -0,0 +1,20 @@ +import { OAuthApps } from '../../../models/server/raw'; + +async function run(): Promise { + if (!await OAuthApps.findOneById('zapier')) { + await OAuthApps.insertOne({ + _id: 'zapier', + name: 'Zapier', + active: true, + clientId: 'zapier', + clientSecret: 'RTK6TlndaCIolhQhZ7_KHIGOKj41RnlaOq_o-7JKwLr', + redirectUri: 'https://zapier.com/dashboard/auth/oauth/return/RocketChatDevAPI/', + _createdAt: new Date(), + _createdBy: { + _id: 'system', + username: 'system', + }, + }); + } +} +run(); diff --git a/app/oauth2-server-config/server/oauth/oauth2-server.js b/app/oauth2-server-config/server/oauth/oauth2-server.js index 438aaaa2e0e64..c801074db4d50 100644 --- a/app/oauth2-server-config/server/oauth/oauth2-server.js +++ b/app/oauth2-server-config/server/oauth/oauth2-server.js @@ -1,15 +1,18 @@ import { Meteor } from 'meteor/meteor'; +import { Mongo } from 'meteor/mongo'; import { WebApp } from 'meteor/webapp'; import { OAuth2Server } from 'meteor/rocketchat:oauth2-server'; -import { OAuthApps, Users } from '../../../models'; +import { Users } from '../../../models/server'; +import { OAuthApps } from '../../../models/server/raw'; import { API } from '../../../api/server'; const oauth2server = new OAuth2Server({ accessTokensCollectionName: 'rocketchat_oauth_access_tokens', refreshTokensCollectionName: 'rocketchat_oauth_refresh_tokens', authCodesCollectionName: 'rocketchat_oauth_auth_codes', - clientsCollection: OAuthApps.model, + // TODO: Remove workaround. Used to pass meteor collection reference to a package + clientsCollection: new Mongo.Collection(OAuthApps.col.collectionName), debug: true, }); diff --git a/app/oembed/server/server.js b/app/oembed/server/server.js index 05f95cfbc0dc4..a3b32443fd1db 100644 --- a/app/oembed/server/server.js +++ b/app/oembed/server/server.js @@ -10,7 +10,8 @@ import ipRangeCheck from 'ip-range-check'; import he from 'he'; import jschardet from 'jschardet'; -import { OEmbedCache, Messages } from '../../models'; +import { Messages } from '../../models/server'; +import { OEmbedCache } from '../../models/server/raw'; import { callbacks } from '../../callbacks'; import { settings } from '../../settings'; import { isURL } from '../../utils/lib/isURL'; @@ -214,8 +215,8 @@ OEmbed.getUrlMeta = function(url, withFragment) { }); }; -OEmbed.getUrlMetaWithCache = function(url, withFragment) { - const cache = OEmbedCache.findOneById(url); +OEmbed.getUrlMetaWithCache = async function(url, withFragment) { + const cache = await OEmbedCache.findOneById(url); if (cache != null) { return cache.data; @@ -223,7 +224,7 @@ OEmbed.getUrlMetaWithCache = function(url, withFragment) { const data = OEmbed.getUrlMeta(url, withFragment); if (data != null) { try { - OEmbedCache.createWithIdAndData(url, data); + await OEmbedCache.createWithIdAndData(url, data); } catch (_error) { SystemLogger.error('OEmbed duplicated record', url); } @@ -262,21 +263,21 @@ const getRelevantMetaTags = function(metaObj) { const insertMaxWidthInOembedHtml = (oembedHtml) => oembedHtml?.replace('iframe', 'iframe style=\"max-width: 100%;width:400px;height:225px\"'); -OEmbed.rocketUrlParser = function(message) { +OEmbed.rocketUrlParser = async function(message) { if (Array.isArray(message.urls)) { - let attachments = []; + const attachments = []; let changed = false; - message.urls.forEach(function(item) { + for await (const item of message.urls) { if (item.ignoreParse === true) { return; } if (!isURL(item.url)) { return; } - const data = OEmbed.getUrlMetaWithCache(item.url); + const data = await OEmbed.getUrlMetaWithCache(item.url); if (data != null) { if (data.attachments) { - attachments = _.union(attachments, data.attachments); + attachments.push(...data.attachments); return; } if (data.meta != null) { @@ -291,7 +292,7 @@ OEmbed.rocketUrlParser = function(message) { item.parsedUrl = data.parsedUrl; changed = true; } - }); + } if (attachments.length) { Messages.setMessageAttachments(message._id, attachments); } @@ -304,7 +305,7 @@ OEmbed.rocketUrlParser = function(message) { settings.watch('API_Embed', function(value) { if (value) { - return callbacks.add('afterSaveMessage', OEmbed.rocketUrlParser, callbacks.priority.LOW, 'API_Embed'); + return callbacks.add('afterSaveMessage', (message) => Promise.await(OEmbed.rocketUrlParser(message)), callbacks.priority.LOW, 'API_Embed'); } return callbacks.remove('afterSaveMessage', 'API_Embed'); }); diff --git a/app/reactions/server/setReaction.js b/app/reactions/server/setReaction.js index 279e4e813d88e..e5f2a885b3083 100644 --- a/app/reactions/server/setReaction.js +++ b/app/reactions/server/setReaction.js @@ -2,7 +2,8 @@ import { Meteor } from 'meteor/meteor'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import _ from 'underscore'; -import { Messages, EmojiCustom, Rooms } from '../../models/server'; +import { Messages, Rooms } from '../../models/server'; +import { EmojiCustom } from '../../models/server/raw'; import { callbacks } from '../../callbacks/server'; import { emoji } from '../../emoji/server'; import { isTheLastMessage, msgStream } from '../../lib/server'; @@ -20,7 +21,7 @@ const removeUserReaction = (message, reaction, username) => { async function setReaction(room, user, message, reaction, shouldReact) { reaction = `:${ reaction.replace(/:/g, '') }:`; - if (!emoji.list[reaction] && EmojiCustom.findByNameOrAlias(reaction).count() === 0) { + if (!emoji.list[reaction] && await EmojiCustom.findByNameOrAlias(reaction).count() === 0) { throw new Meteor.Error('error-not-allowed', 'Invalid emoji provided.', { method: 'setReaction' }); } diff --git a/app/smarsh-connector/server/functions/generateEml.js b/app/smarsh-connector/server/functions/generateEml.js index 8633fd1ca0ca8..f828ce310387c 100644 --- a/app/smarsh-connector/server/functions/generateEml.js +++ b/app/smarsh-connector/server/functions/generateEml.js @@ -4,7 +4,8 @@ import _ from 'underscore'; import moment from 'moment'; import { settings } from '../../../settings'; -import { Rooms, Messages, Users, SmarshHistory } from '../../../models'; +import { Rooms, Messages, Users } from '../../../models/server'; +import { SmarshHistory } from '../../../models/server/raw'; import { MessageTypes } from '../../../ui-utils'; import { smarsh } from '../lib/rocketchat'; import 'moment-timezone'; @@ -31,8 +32,8 @@ smarsh.generateEml = () => { const smarshMissingEmail = settings.get('Smarsh_MissingEmail_Email'); const timeZone = settings.get('Smarsh_Timezone'); - Rooms.find().forEach((room) => { - const smarshHistory = SmarshHistory.findOne({ _id: room._id }); + Rooms.find().forEach(async (room) => { + const smarshHistory = await SmarshHistory.findOne({ _id: room._id }); const query = { rid: room._id }; if (smarshHistory) { diff --git a/app/smarsh-connector/server/functions/sendEmail.js b/app/smarsh-connector/server/functions/sendEmail.js index 9b69b05b3ac1e..67fcfde02e675 100644 --- a/app/smarsh-connector/server/functions/sendEmail.js +++ b/app/smarsh-connector/server/functions/sendEmail.js @@ -4,19 +4,18 @@ // subject: 'Rocket.Chat, 17 Users, 24 Messages, 1 File, 799504 Minutes, in #random', // files: ['i3nc9l3mn'] // } -import _ from 'underscore'; import { UploadFS } from 'meteor/jalik:ufs'; import * as Mailer from '../../../mailer'; -import { Uploads } from '../../../models'; +import { Uploads } from '../../../models/server/raw'; import { settings } from '../../../settings'; import { smarsh } from '../lib/rocketchat'; -smarsh.sendEmail = (data) => { +smarsh.sendEmail = async (data) => { const attachments = []; - _.each(data.files, (fileId) => { - const file = Uploads.findOneById(fileId); + for await (const fileId of data.files) { + const file = await Uploads.findOneById(fileId); if (file.store === 'rocketchat_uploads' || file.store === 'fileSystem') { const rs = UploadFS.getStore(file.store).getReadStream(fileId, file); attachments.push({ @@ -24,8 +23,7 @@ smarsh.sendEmail = (data) => { streamSource: rs, }); } - }); - + } Mailer.sendNoWrap({ to: settings.get('Smarsh_Email'), diff --git a/app/statistics/server/lib/SAUMonitor.js b/app/statistics/server/lib/SAUMonitor.js index 36b7036fb5a04..43d41cad7e33c 100644 --- a/app/statistics/server/lib/SAUMonitor.js +++ b/app/statistics/server/lib/SAUMonitor.js @@ -4,9 +4,9 @@ import { SyncedCron } from 'meteor/littledata:synced-cron'; import UAParser from 'ua-parser-js'; import { UAParserMobile, UAParserDesktop } from './UAParserCustom'; -import { Sessions } from '../../../models/server'; +import { Sessions } from '../../../models/server/raw'; +import { aggregates } from '../../../models/server/raw/Sessions'; import { Logger } from '../../../logger'; -import { aggregates } from '../../../models/server/models/Sessions'; import { getMostImportantRole } from './getMostImportantRole'; const getDateObj = (dateTime = new Date()) => ({ @@ -32,7 +32,7 @@ export class SAUMonitorClass { this._jobName = 'aggregate-sessions'; } - start(instanceId) { + async start(instanceId) { if (this.isRunning()) { return; } @@ -44,7 +44,7 @@ export class SAUMonitorClass { return; } - this._startMonitoring(() => { + await this._startMonitoring(() => { this._started = true; logger.debug(`[start] - InstanceId: ${ this._instanceId }`); }); @@ -70,12 +70,12 @@ export class SAUMonitorClass { return this._started === true; } - _startMonitoring(callback) { + async _startMonitoring(callback) { try { this._handleAccountEvents(); this._handleOnConnection(); this._startSessionControl(); - this._initActiveServerSessions(); + await this._initActiveServerSessions(); this._startAggregation(); if (callback) { callback(); @@ -94,8 +94,8 @@ export class SAUMonitorClass { return; } - this._timer = Meteor.setInterval(() => { - this._updateActiveSessions(); + this._timer = Meteor.setInterval(async () => { + await this._updateActiveSessions(); }, this._monitorTime); } @@ -110,8 +110,8 @@ export class SAUMonitorClass { } // this._handleSession(connection, getDateObj()); - connection.onClose(() => { - Sessions.closeByInstanceIdAndSessionId(this._instanceId, connection.id); + connection.onClose(async () => { + await Sessions.closeByInstanceIdAndSessionId(this._instanceId, connection.id); }); }); } @@ -121,7 +121,7 @@ export class SAUMonitorClass { return; } - Accounts.onLogin((info) => { + Accounts.onLogin(async (info) => { if (!this.isRunning()) { return; } @@ -132,11 +132,11 @@ export class SAUMonitorClass { const loginAt = new Date(); const params = { userId, roles, mostImportantRole, loginAt, ...getDateObj() }; - this._handleSession(info.connection, params); + await this._handleSession(info.connection, params); this._updateConnectionInfo(info.connection.id, { loginAt }); }); - Accounts.onLogout((info) => { + Accounts.onLogout(async (info) => { if (!this.isRunning()) { return; } @@ -144,17 +144,17 @@ export class SAUMonitorClass { const sessionId = info.connection.id; if (info.user) { const userId = info.user._id; - Sessions.logoutByInstanceIdAndSessionIdAndUserId(this._instanceId, sessionId, userId); + await Sessions.logoutByInstanceIdAndSessionIdAndUserId(this._instanceId, sessionId, userId); } }); } - _handleSession(connection, params) { + async _handleSession(connection, params) { const data = this._getConnectionInfo(connection, params); - Sessions.createOrUpdate(data); + await Sessions.createOrUpdate(data); } - _updateActiveSessions() { + async _updateActiveSessions() { if (!this.isRunning()) { return; } @@ -167,8 +167,8 @@ export class SAUMonitorClass { const beforeDateTime = new Date(this._today.year, this._today.month - 1, this._today.day, 23, 59, 59, 999); const nextDateTime = new Date(currentDay.year, currentDay.month - 1, currentDay.day); - const createSessions = (objects, ids) => { - Sessions.createBatch(objects); + const createSessions = async (objects, ids) => { + await Sessions.createBatch(objects); Meteor.defer(() => { Sessions.updateActiveSessionsByDateAndInstanceIdAndIds({ year, month, day }, this._instanceId, ids, { lastActivityAt: beforeDateTime }); @@ -180,8 +180,8 @@ export class SAUMonitorClass { } // Otherwise, just update the lastActivityAt field - this._applyAllServerSessionsIds((sessions) => { - Sessions.updateActiveSessionsByDateAndInstanceIdAndIds({ year, month, day }, this._instanceId, sessions, { lastActivityAt: currentDateTime }); + await this._applyAllServerSessionsIds(async (sessions) => { + await Sessions.updateActiveSessionsByDateAndInstanceIdAndIds({ year, month, day }, this._instanceId, sessions, { lastActivityAt: currentDateTime }); }); } @@ -266,32 +266,38 @@ export class SAUMonitorClass { }; } - _initActiveServerSessions() { - this._applyAllServerSessions((connectionHandle) => { - this._handleSession(connectionHandle, getDateObj()); + async _initActiveServerSessions() { + await this._applyAllServerSessions(async (connectionHandle) => { + await this._handleSession(connectionHandle, getDateObj()); }); } - _applyAllServerSessions(callback) { + async _applyAllServerSessions(callback) { if (!callback || typeof callback !== 'function') { return; } const sessions = Object.values(Meteor.server.sessions).filter((session) => session.userId); - sessions.forEach((session) => { - callback(session.connectionHandle); - }); + for await (const session of sessions) { + await callback(session.connectionHandle); + } } - _applyAllServerSessionsIds(callback) { + async recursive(callback, sessionIds) { + await callback(sessionIds.splice(0, 500)); + + if (sessionIds.length) { + await this.recursive(callback, sessionIds); + } + } + + async _applyAllServerSessionsIds(callback) { if (!callback || typeof callback !== 'function') { return; } const sessionIds = Object.values(Meteor.server.sessions).filter((session) => session.userId).map((s) => s.id); - while (sessionIds.length) { - callback(sessionIds.splice(0, 500)); - } + await this.recursive(callback, sessionIds); } _updateConnectionInfo(sessionId, data = {}) { @@ -315,8 +321,8 @@ export class SAUMonitorClass { return Promise.all(arr.splice(0, limit).map((item) => { ids.push(item.id); return this._getConnectionInfo(item.connectionHandle, params); - })).then((data) => { - callback(data, ids); + })).then(async (data) => { + await callback(data, ids); return batch(arr, limit); }).catch((e) => { logger.debug(`Error: ${ e.message }`); @@ -333,13 +339,13 @@ export class SAUMonitorClass { SyncedCron.add({ name: this._jobName, schedule: (parser) => parser.text('at 2:00 am'), - job: () => { - this.aggregate(); + job: async () => { + await this.aggregate(); }, }); } - aggregate() { + async aggregate() { if (!this.isRunning()) { return; } @@ -357,16 +363,16 @@ export class SAUMonitorClass { day: { $lte: yesterday.day }, }; - aggregates.dailySessionsOfYesterday(Sessions.model.rawCollection(), yesterday).forEach(Meteor.bindEnvironment((record) => { + await aggregates.dailySessionsOfYesterday(Sessions.col, yesterday).forEach(async (record) => { record._id = `${ record.userId }-${ record.year }-${ record.month }-${ record.day }`; - Sessions.upsert({ _id: record._id }, record); - })); + await Sessions.updateOne({ _id: record._id }, record, { upsert: true }); + }); - Sessions.update(match, { + await Sessions.updateMany(match, { $set: { type: 'computed-session', _computedAt: new Date(), }, - }, { multi: true }); + }); } } diff --git a/app/statistics/server/lib/statistics.js b/app/statistics/server/lib/statistics.js index b4ffb9f7dd25f..c9d99c049dc53 100644 --- a/app/statistics/server/lib/statistics.js +++ b/app/statistics/server/lib/statistics.js @@ -3,24 +3,21 @@ import os from 'os'; import _ from 'underscore'; import { Meteor } from 'meteor/meteor'; import { InstanceStatus } from 'meteor/konecty:multiple-instances-status'; +import { MongoInternals } from 'meteor/mongo'; import { - Sessions, Settings, Users, Rooms, Subscriptions, - Uploads, Messages, LivechatVisitors, - Integrations, - Statistics, } from '../../../models/server'; import { settings } from '../../../settings/server'; import { Info, getMongoInfo } from '../../../utils/server'; import { getControl } from '../../../../server/lib/migrations'; import { getStatistics as federationGetStatistics } from '../../../federation/server/functions/dashboard'; -import { NotificationQueue, Users as UsersRaw } from '../../../models/server/raw'; +import { NotificationQueue, Users as UsersRaw, Statistics, Sessions, Integrations, Uploads } from '../../../models/server/raw'; import { readSecondaryPreferred } from '../../../../server/database/readSecondaryPreferred'; import { getAppsStatistics } from './getAppsStatistics'; import { getServicesStatistics } from './getServicesStatistics'; @@ -55,9 +52,11 @@ const getUserLanguages = (totalUsers) => { return languages; }; +const { db } = MongoInternals.defaultRemoteCollectionDriver().mongo; + export const statistics = { get: function _getStatistics() { - const readPreference = readSecondaryPreferred(Uploads.model.rawDatabase()); + const readPreference = readSecondaryPreferred(db); const statistics = {}; @@ -162,8 +161,8 @@ export const statistics = { statistics.enterpriseReady = true; - statistics.uploadsTotal = Uploads.find().count(); - const [result] = Promise.await(Uploads.model.rawCollection().aggregate([{ + statistics.uploadsTotal = Promise.await(Uploads.find().count()); + const [result] = Promise.await(Uploads.col.aggregate([{ $group: { _id: 'total', total: { $sum: '$size' } }, }], { readPreference }).toArray()); statistics.uploadsTotalSize = result ? result.total : 0; @@ -176,20 +175,20 @@ export const statistics = { statistics.mongoVersion = mongoVersion; statistics.mongoStorageEngine = mongoStorageEngine; - statistics.uniqueUsersOfYesterday = Sessions.getUniqueUsersOfYesterday(); - statistics.uniqueUsersOfLastWeek = Sessions.getUniqueUsersOfLastWeek(); - statistics.uniqueUsersOfLastMonth = Sessions.getUniqueUsersOfLastMonth(); - statistics.uniqueDevicesOfYesterday = Sessions.getUniqueDevicesOfYesterday(); - statistics.uniqueDevicesOfLastWeek = Sessions.getUniqueDevicesOfLastWeek(); - statistics.uniqueDevicesOfLastMonth = Sessions.getUniqueDevicesOfLastMonth(); - statistics.uniqueOSOfYesterday = Sessions.getUniqueOSOfYesterday(); - statistics.uniqueOSOfLastWeek = Sessions.getUniqueOSOfLastWeek(); - statistics.uniqueOSOfLastMonth = Sessions.getUniqueOSOfLastMonth(); + statistics.uniqueUsersOfYesterday = Promise.await(Sessions.getUniqueUsersOfYesterday()); + statistics.uniqueUsersOfLastWeek = Promise.await(Sessions.getUniqueUsersOfLastWeek()); + statistics.uniqueUsersOfLastMonth = Promise.await(Sessions.getUniqueUsersOfLastMonth()); + statistics.uniqueDevicesOfYesterday = Promise.await(Sessions.getUniqueDevicesOfYesterday()); + statistics.uniqueDevicesOfLastWeek = Promise.await(Sessions.getUniqueDevicesOfLastWeek()); + statistics.uniqueDevicesOfLastMonth = Promise.await(Sessions.getUniqueDevicesOfLastMonth()); + statistics.uniqueOSOfYesterday = Promise.await(Sessions.getUniqueOSOfYesterday()); + statistics.uniqueOSOfLastWeek = Promise.await(Sessions.getUniqueOSOfLastWeek()); + statistics.uniqueOSOfLastMonth = Promise.await(Sessions.getUniqueOSOfLastMonth()); statistics.apps = getAppsStatistics(); statistics.services = getServicesStatistics(); - const integrations = Promise.await(Integrations.model.rawCollection().find({}, { + const integrations = Promise.await(Integrations.find({}, { projection: { _id: 0, type: 1, @@ -215,10 +214,10 @@ export const statistics = { return statistics; }, - save() { + async save() { const rcStatistics = statistics.get(); rcStatistics.createdAt = new Date(); - Statistics.insert(rcStatistics); + await Statistics.insertOne(rcStatistics); return rcStatistics; }, }; diff --git a/app/user-data-download/server/cronProcessDownloads.js b/app/user-data-download/server/cronProcessDownloads.js index 8fb19b86b08c6..35c8de44af20b 100644 --- a/app/user-data-download/server/cronProcessDownloads.js +++ b/app/user-data-download/server/cronProcessDownloads.js @@ -10,7 +10,8 @@ import moment from 'moment'; import { v4 as uuidv4 } from 'uuid'; import { settings } from '../../settings/server'; -import { Subscriptions, Rooms, Users, Uploads, Messages, UserDataFiles, ExportOperations, Avatars } from '../../models/server'; +import { Subscriptions, Rooms, Users, Messages } from '../../models/server'; +import { Avatars, ExportOperations, UserDataFiles, Uploads } from '../../models/server/raw'; import { FileUpload } from '../../file-upload/server'; import { DataExport } from './DataExport'; import * as Mailer from '../../mailer'; @@ -186,8 +187,8 @@ const getMessageData = function(msg, hideUsers, userData, usersMap) { return messageObject; }; -export const copyFile = function(attachmentData, assetsPath) { - const file = Uploads.findOneById(attachmentData._id); +export const copyFile = async function(attachmentData, assetsPath) { + const file = await Uploads.findOneById(attachmentData._id); if (!file) { return; } @@ -439,12 +440,12 @@ const generateUserFile = function(exportOperation, userData) { } }; -const generateUserAvatarFile = function(exportOperation, userData) { +const generateUserAvatarFile = async function(exportOperation, userData) { if (!userData) { return; } - const file = Avatars.findOneByName(userData.username); + const file = await Avatars.findOneByName(userData.username); if (!file) { return; } @@ -478,7 +479,7 @@ const continueExportOperation = async function(exportOperation) { } if (!exportOperation.generatedAvatar) { - generateUserAvatarFile(exportOperation, exportOperation.userData); + await generateUserAvatarFile(exportOperation, exportOperation.userData); } if (exportOperation.status === 'exporting-rooms') { @@ -511,9 +512,9 @@ const continueExportOperation = async function(exportOperation) { const generatedFileName = uuidv4(); if (exportOperation.status === 'downloading') { - exportOperation.fileList.forEach((attachmentData) => { - copyFile(attachmentData, exportOperation.assetsPath); - }); + for await (const attachmentData of exportOperation.fileList) { + await copyFile(attachmentData, exportOperation.assetsPath); + } const targetFile = joinPath(zipFolder, `${ generatedFileName }.zip`); if (await fsExists(targetFile)) { @@ -539,17 +540,17 @@ const continueExportOperation = async function(exportOperation) { exportOperation.fileId = fileId; exportOperation.status = 'completed'; - ExportOperations.updateOperation(exportOperation); + await ExportOperations.updateOperation(exportOperation); } - ExportOperations.updateOperation(exportOperation); + await ExportOperations.updateOperation(exportOperation); } catch (e) { console.error(e); } }; async function processDataDownloads() { - const operation = ExportOperations.findOnePending(); + const operation = await ExportOperations.findOnePending(); if (!operation) { return; } @@ -571,7 +572,7 @@ async function processDataDownloads() { await ExportOperations.updateOperation(operation); if (operation.status === 'completed') { - const file = operation.fileId ? UserDataFiles.findOneById(operation.fileId) : UserDataFiles.findLastFileByUser(operation.userId); + const file = operation.fileId ? await UserDataFiles.findOneById(operation.fileId) : await UserDataFiles.findLastFileByUser(operation.userId); if (!file) { return; } diff --git a/app/user-data-download/server/exportDownload.js b/app/user-data-download/server/exportDownload.js index d99eda5c88e60..6ea406de307c2 100644 --- a/app/user-data-download/server/exportDownload.js +++ b/app/user-data-download/server/exportDownload.js @@ -1,12 +1,12 @@ import { WebApp } from 'meteor/webapp'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; -import { UserDataFiles } from '../../models'; +import { UserDataFiles } from '../../models/server/raw'; import { DataExport } from './DataExport'; import { settings } from '../../settings/server'; -WebApp.connectHandlers.use(DataExport.getPath(), function(req, res, next) { +WebApp.connectHandlers.use(DataExport.getPath(), async function(req, res, next) { const match = /^\/([^\/]+)/.exec(req.url); if (!settings.get('UserData_EnableDownload')) { @@ -16,7 +16,7 @@ WebApp.connectHandlers.use(DataExport.getPath(), function(req, res, next) { } if (match && match[1]) { - const file = UserDataFiles.findOneById(match[1]); + const file = await UserDataFiles.findOneById(match[1]); if (file) { if (!DataExport.requestCanAccessFiles(req, file.userId)) { res.setHeader('Content-Type', 'text/html; charset=UTF-8'); diff --git a/app/user-status/server/methods/deleteCustomUserStatus.js b/app/user-status/server/methods/deleteCustomUserStatus.js index e81a8140d7180..6935266a74757 100644 --- a/app/user-status/server/methods/deleteCustomUserStatus.js +++ b/app/user-status/server/methods/deleteCustomUserStatus.js @@ -1,21 +1,21 @@ import { Meteor } from 'meteor/meteor'; import { hasPermission } from '../../../authorization/server'; -import { CustomUserStatus } from '../../../models/server'; +import { CustomUserStatus } from '../../../models/server/raw'; import { api } from '../../../../server/sdk/api'; Meteor.methods({ - deleteCustomUserStatus(userStatusID) { + async deleteCustomUserStatus(userStatusID) { if (!hasPermission(this.userId, 'manage-user-status')) { throw new Meteor.Error('not_authorized'); } - const userStatus = CustomUserStatus.findOneById(userStatusID); + const userStatus = await CustomUserStatus.findOneById(userStatusID); if (userStatus == null) { throw new Meteor.Error('Custom_User_Status_Error_Invalid_User_Status', 'Invalid user status', { method: 'deleteCustomUserStatus' }); } - CustomUserStatus.removeById(userStatusID); + await CustomUserStatus.removeById(userStatusID); api.broadcast('user.deleteCustomStatus', userStatus); return true; diff --git a/app/user-status/server/methods/insertOrUpdateUserStatus.js b/app/user-status/server/methods/insertOrUpdateUserStatus.js index a01cc751c525d..ebc0e918acbae 100644 --- a/app/user-status/server/methods/insertOrUpdateUserStatus.js +++ b/app/user-status/server/methods/insertOrUpdateUserStatus.js @@ -2,11 +2,11 @@ import { Meteor } from 'meteor/meteor'; import s from 'underscore.string'; import { hasPermission } from '../../../authorization'; -import { CustomUserStatus } from '../../../models'; +import { CustomUserStatus } from '../../../models/server/raw'; import { api } from '../../../../server/sdk/api'; Meteor.methods({ - insertOrUpdateUserStatus(userStatusData) { + async insertOrUpdateUserStatus(userStatusData) { if (!hasPermission(this.userId, 'manage-user-status')) { throw new Meteor.Error('not_authorized'); } @@ -26,9 +26,9 @@ Meteor.methods({ let matchingResults = []; if (userStatusData._id) { - matchingResults = CustomUserStatus.findByNameExceptId(userStatusData.name, userStatusData._id).fetch(); + matchingResults = await CustomUserStatus.findByNameExceptId(userStatusData.name, userStatusData._id).toArray(); } else { - matchingResults = CustomUserStatus.findByName(userStatusData.name).fetch(); + matchingResults = await CustomUserStatus.findByName(userStatusData.name).toArray(); } if (matchingResults.length > 0) { @@ -47,7 +47,7 @@ Meteor.methods({ statusType: userStatusData.statusType || null, }; - const _id = CustomUserStatus.create(createUserStatus); + const _id = await (await CustomUserStatus.create(createUserStatus)).insertedId; api.broadcast('user.updateCustomStatus', createUserStatus); @@ -56,11 +56,11 @@ Meteor.methods({ // update User status if (userStatusData.name !== userStatusData.previousName) { - CustomUserStatus.setName(userStatusData._id, userStatusData.name); + await CustomUserStatus.setName(userStatusData._id, userStatusData.name); } if (userStatusData.statusType !== userStatusData.previousStatusType) { - CustomUserStatus.setStatusType(userStatusData._id, userStatusData.statusType); + await CustomUserStatus.setStatusType(userStatusData._id, userStatusData.statusType); } api.broadcast('user.updateCustomStatus', userStatusData); diff --git a/app/user-status/server/methods/listCustomUserStatus.js b/app/user-status/server/methods/listCustomUserStatus.js index a47f1ff01bc27..b8ec637d99b6f 100644 --- a/app/user-status/server/methods/listCustomUserStatus.js +++ b/app/user-status/server/methods/listCustomUserStatus.js @@ -1,14 +1,14 @@ import { Meteor } from 'meteor/meteor'; -import { CustomUserStatus } from '../../../models'; +import { CustomUserStatus } from '../../../models/server/raw'; Meteor.methods({ - listCustomUserStatus() { + async listCustomUserStatus() { const currentUserId = Meteor.userId(); if (!currentUserId) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'listCustomUserStatus' }); } - return CustomUserStatus.find({}).fetch(); + return CustomUserStatus.find({}).toArray(); }, }); diff --git a/app/utils/server/functions/normalizeMessageFileUpload.js b/app/utils/server/functions/normalizeMessageFileUpload.js index 450396b0b7651..948dc047c252c 100644 --- a/app/utils/server/functions/normalizeMessageFileUpload.js +++ b/app/utils/server/functions/normalizeMessageFileUpload.js @@ -1,11 +1,11 @@ import { getURL } from '../../lib/getURL'; import { FileUpload } from '../../../file-upload/server'; -import { Uploads } from '../../../models/server'; +import { Uploads } from '../../../models/server/raw'; -export const normalizeMessageFileUpload = (message) => { +export const normalizeMessageFileUpload = async (message) => { if (message.file && !message.fileUpload) { const jwt = FileUpload.generateJWTToFileUrls({ rid: message.rid, userId: message.u._id, fileId: message.file._id }); - const file = Uploads.findOne({ _id: message.file._id }); + const file = await Uploads.findOne({ _id: message.file._id }); if (!file) { return message; } diff --git a/app/version-check/server/functions/checkVersionUpdate.js b/app/version-check/server/functions/checkVersionUpdate.js index 65cd246d663cd..9933081a038cd 100644 --- a/app/version-check/server/functions/checkVersionUpdate.js +++ b/app/version-check/server/functions/checkVersionUpdate.js @@ -42,7 +42,7 @@ export default () => { if (update.exists) { Settings.updateValueById('Update_LatestAvailableVersion', update.lastestVersion.version); - sendMessagesToAdmins({ + Promise.await(sendMessagesToAdmins({ msgs: ({ adminUser }) => [{ msg: `*${ TAPi18n.__('Update_your_RocketChat', adminUser.language) }*\n${ TAPi18n.__('New_version_available_(s)', update.lastestVersion.version, adminUser.language) }\n${ update.lastestVersion.infoUrl }` }], banners: [{ id: `versionUpdate-${ update.lastestVersion.version }`.replace(/\./g, '_'), @@ -52,11 +52,11 @@ export default () => { textArguments: [update.lastestVersion.version], link: update.lastestVersion.infoUrl, }], - }); + })); } if (alerts && alerts.length) { - sendMessagesToAdmins({ + Promise.await(sendMessagesToAdmins({ msgs: ({ adminUser }) => alerts .filter((alert) => !Users.bannerExistsById(adminUser._id, `alert-${ alert.id }`)) .map((alert) => ({ @@ -71,6 +71,6 @@ export default () => { modifiers: alert.modifiers, link: alert.infoUrl, })), - }); + })); } }; diff --git a/app/webdav/client/actionButton.js b/app/webdav/client/actionButton.js index 6b72d87857114..cbfd32da1c3f8 100644 --- a/app/webdav/client/actionButton.js +++ b/app/webdav/client/actionButton.js @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { t, getURL } from '../../utils'; -import { WebdavAccounts } from '../../models'; +import { WebdavAccounts } from '../../models/client'; import { settings } from '../../settings'; import { MessageAction, modal } from '../../ui-utils'; import { messageArgs } from '../../ui-utils/client/lib/messageArgs'; diff --git a/app/webdav/client/selectWebdavAccount.js b/app/webdav/client/selectWebdavAccount.js index 5acb7f43f9c43..14f771822cc40 100644 --- a/app/webdav/client/selectWebdavAccount.js +++ b/app/webdav/client/selectWebdavAccount.js @@ -3,7 +3,7 @@ import { Template } from 'meteor/templating'; import { modal } from '../../ui-utils'; import { t } from '../../utils'; -import { WebdavAccounts } from '../../models'; +import { WebdavAccounts } from '../../models/client'; import { dispatchToastMessage } from '../../../client/lib/toast'; Template.selectWebdavAccount.helpers({ diff --git a/app/webdav/client/startup/messageBoxActions.js b/app/webdav/client/startup/messageBoxActions.js index a209259d4aaa1..be63edecd5992 100644 --- a/app/webdav/client/startup/messageBoxActions.js +++ b/app/webdav/client/startup/messageBoxActions.js @@ -4,7 +4,7 @@ import { Tracker } from 'meteor/tracker'; import { t } from '../../../utils'; import { settings } from '../../../settings'; import { messageBox, modal } from '../../../ui-utils'; -import { WebdavAccounts } from '../../../models'; +import { WebdavAccounts } from '../../../models/client'; messageBox.actions.add('WebDAV', 'Add Server', { id: 'add-webdav', diff --git a/app/webdav/server/methods/addWebdavAccount.js b/app/webdav/server/methods/addWebdavAccount.js index 3bfc6aef34955..fdaf715903fac 100644 --- a/app/webdav/server/methods/addWebdavAccount.js +++ b/app/webdav/server/methods/addWebdavAccount.js @@ -1,8 +1,8 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; -import { settings } from '../../../settings'; -import { WebdavAccounts } from '../../../models'; +import { settings } from '../../../settings/server'; +import { WebdavAccounts } from '../../../models/server/raw'; import { WebdavClientAdapter } from '../lib/webdavClientAdapter'; import { Notifications } from '../../../notifications/server'; @@ -24,7 +24,7 @@ Meteor.methods({ pass: String, })); - const duplicateAccount = WebdavAccounts.findOne({ user_id: userId, server_url: formData.serverURL, username: formData.username }); + const duplicateAccount = await WebdavAccounts.findOneByUserIdServerUrlAndUsername({ user_id: userId, server_url: formData.serverURL, username: formData.username }); if (duplicateAccount !== undefined) { throw new Meteor.Error('duplicated-account', { method: 'addWebdavAccount', @@ -49,7 +49,7 @@ Meteor.methods({ }; await client.stat('/'); - WebdavAccounts.insert(accountData); + await WebdavAccounts.insertOne(accountData); Notifications.notifyUser(userId, 'webdav', { type: 'changed', account: accountData, @@ -89,12 +89,14 @@ Meteor.methods({ }; await client.stat('/'); - WebdavAccounts.upsert({ + await WebdavAccounts.updateOne({ user_id: userId, server_url: data.serverURL, name: data.name, }, { $set: accountData, + }, { + upsert: true, }); Notifications.notifyUser(userId, 'webdav', { type: 'changed', diff --git a/app/webdav/server/methods/getFileFromWebdav.js b/app/webdav/server/methods/getFileFromWebdav.js index aeeda2a2636ec..a63d72c047237 100644 --- a/app/webdav/server/methods/getFileFromWebdav.js +++ b/app/webdav/server/methods/getFileFromWebdav.js @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { settings } from '../../../settings'; import { getWebdavCredentials } from './getWebdavCredentials'; -import { WebdavAccounts } from '../../../models'; +import { WebdavAccounts } from '../../../models/server/raw'; import { WebdavClientAdapter } from '../lib/webdavClientAdapter'; Meteor.methods({ @@ -14,7 +14,7 @@ Meteor.methods({ throw new Meteor.Error('error-not-allowed', 'WebDAV Integration Not Allowed', { method: 'getFileFromWebdav' }); } - const account = WebdavAccounts.findOne({ _id: accountId, user_id: Meteor.userId() }); + const account = await WebdavAccounts.findOneByIdAndUserId(accountId, Meteor.userId()); if (!account) { throw new Meteor.Error('error-invalid-account', 'Invalid WebDAV Account', { method: 'getFileFromWebdav' }); } diff --git a/app/webdav/server/methods/getWebdavFileList.js b/app/webdav/server/methods/getWebdavFileList.js index 52e3c6e3904a2..e9b5e3526d7ce 100644 --- a/app/webdav/server/methods/getWebdavFileList.js +++ b/app/webdav/server/methods/getWebdavFileList.js @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { settings } from '../../../settings'; import { getWebdavCredentials } from './getWebdavCredentials'; -import { WebdavAccounts } from '../../../models'; +import { WebdavAccounts } from '../../../models/server/raw'; import { WebdavClientAdapter } from '../lib/webdavClientAdapter'; Meteor.methods({ @@ -15,7 +15,7 @@ Meteor.methods({ throw new Meteor.Error('error-not-allowed', 'WebDAV Integration Not Allowed', { method: 'getWebdavFileList' }); } - const account = WebdavAccounts.findOne({ _id: accountId, user_id: Meteor.userId() }); + const account = await WebdavAccounts.findOneByIdAndUserId(accountId, Meteor.userId()); if (!account) { throw new Meteor.Error('error-invalid-account', 'Invalid WebDAV Account', { method: 'getWebdavFileList' }); } diff --git a/app/webdav/server/methods/getWebdavFilePreview.js b/app/webdav/server/methods/getWebdavFilePreview.js index 2de21cae6fef3..5940d44601b41 100644 --- a/app/webdav/server/methods/getWebdavFilePreview.js +++ b/app/webdav/server/methods/getWebdavFilePreview.js @@ -3,7 +3,7 @@ import { createClient } from 'webdav'; import { settings } from '../../../settings'; import { getWebdavCredentials } from './getWebdavCredentials'; -import { WebdavAccounts } from '../../../models'; +import { WebdavAccounts } from '../../../models/server/raw'; Meteor.methods({ async getWebdavFilePreview(accountId, path) { @@ -15,7 +15,7 @@ Meteor.methods({ throw new Meteor.Error('error-not-allowed', 'WebDAV Integration Not Allowed', { method: 'getWebdavFilePreview' }); } - const account = WebdavAccounts.findOne({ _id: accountId, user_id: Meteor.userId() }); + const account = await WebdavAccounts.findOneByIdAndUserId(accountId, Meteor.userId()); if (!account) { throw new Meteor.Error('error-invalid-account', 'Invalid WebDAV Account', { method: 'getWebdavFilePreview' }); } diff --git a/app/webdav/server/methods/removeWebdavAccount.js b/app/webdav/server/methods/removeWebdavAccount.js index 46f8c7b515f70..dc6ba032cfc7e 100644 --- a/app/webdav/server/methods/removeWebdavAccount.js +++ b/app/webdav/server/methods/removeWebdavAccount.js @@ -1,18 +1,18 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; -import { WebdavAccounts } from '../../../models'; +import { WebdavAccounts } from '../../../models/server/raw'; import { Notifications } from '../../../notifications/server'; Meteor.methods({ - removeWebdavAccount(accountId) { + async removeWebdavAccount(accountId) { if (!Meteor.userId()) { throw new Meteor.Error('error-invalid-user', 'Invalid User', { method: 'removeWebdavAccount' }); } check(accountId, String); - const removed = WebdavAccounts.removeByUserAndId(accountId, Meteor.userId()); + const removed = await WebdavAccounts.removeByUserAndId(accountId, Meteor.userId()); if (removed) { Notifications.notifyUser(Meteor.userId(), 'webdav', { type: 'removed', diff --git a/app/webdav/server/methods/uploadFileToWebdav.ts b/app/webdav/server/methods/uploadFileToWebdav.ts index 345550285e7fd..1f794ea0ad481 100644 --- a/app/webdav/server/methods/uploadFileToWebdav.ts +++ b/app/webdav/server/methods/uploadFileToWebdav.ts @@ -3,7 +3,7 @@ import { Meteor } from 'meteor/meteor'; import { settings } from '../../../settings/server'; import { Logger } from '../../../logger/server'; import { getWebdavCredentials } from './getWebdavCredentials'; -import { WebdavAccounts } from '../../../models/server'; +import { WebdavAccounts } from '../../../models/server/raw'; import { WebdavClientAdapter } from '../lib/webdavClientAdapter'; const logger = new Logger('WebDAV_Upload'); @@ -18,7 +18,7 @@ Meteor.methods({ throw new Meteor.Error('error-not-allowed', 'WebDAV Integration Not Allowed', { method: 'uploadFileToWebdav' }); } - const account = WebdavAccounts.findOne({ _id: accountId }); + const account = await WebdavAccounts.findOneById(accountId); if (!account) { throw new Meteor.Error('error-invalid-account', 'Invalid WebDAV Account', { method: 'uploadFileToWebdav' }); } diff --git a/definition/Federation.ts b/definition/Federation.ts new file mode 100644 index 0000000000000..d961d82264290 --- /dev/null +++ b/definition/Federation.ts @@ -0,0 +1,5 @@ +import { IRocketChatRecord } from './IRocketChatRecord'; + +export interface IFederationServer extends IRocketChatRecord { + domain: string; +} diff --git a/definition/IAnalytic.ts b/definition/IAnalytic.ts new file mode 100644 index 0000000000000..2a9b1e83c2e99 --- /dev/null +++ b/definition/IAnalytic.ts @@ -0,0 +1,33 @@ +import { IRocketChatRecord } from './IRocketChatRecord'; + +interface IAnalyticsBase extends IRocketChatRecord { + type: 'messages' | 'users' | 'seat-request'; + date: number; +} + +export interface IAnalyticsMessages extends IAnalyticsBase { + type: 'messages'; + room: { + _id: string; + name?: string; + t: string; + usernames: string[]; + }; +} + +export interface IAnalyticsUsers extends IAnalyticsBase { + type: 'users'; + room: { + _id: string; + name?: string; + t: string; + usernames: string[]; + }; +} + +export interface IAnalyticsSeatRequest extends IAnalyticsBase { + type: 'seat-request'; + count: number; +} + +export type IAnalytic = IAnalyticsBase | IAnalyticsMessages | IAnalyticsUsers | IAnalyticsSeatRequest; diff --git a/definition/IAvatar.ts b/definition/IAvatar.ts new file mode 100644 index 0000000000000..b8bd8b82bfbdf --- /dev/null +++ b/definition/IAvatar.ts @@ -0,0 +1,13 @@ +import { IRocketChatRecord } from './IRocketChatRecord'; + +export interface IAvatar extends IRocketChatRecord { + name: string; + rid: string; + userId: string; + store: string; + complete: boolean; + uploading: boolean; + progress: number; + extension: string; + uploadedAt: Date; +} diff --git a/definition/ICredentialToken.ts b/definition/ICredentialToken.ts new file mode 100644 index 0000000000000..ceaf44203cbad --- /dev/null +++ b/definition/ICredentialToken.ts @@ -0,0 +1,10 @@ +export interface ICredentialToken { + _id: string; + + userInfo: { + username?: string; + attributes?: any; + profile?: Record; + }; + expireAt: Date; +} diff --git a/definition/ICustomSound.ts b/definition/ICustomSound.ts new file mode 100644 index 0000000000000..0fd8c09e0851a --- /dev/null +++ b/definition/ICustomSound.ts @@ -0,0 +1,6 @@ +import { IRocketChatRecord } from './IRocketChatRecord'; + +export interface ICustomSound extends IRocketChatRecord { + name: string; + statusType: string; +} diff --git a/definition/ICustomUserStatus.ts b/definition/ICustomUserStatus.ts new file mode 100644 index 0000000000000..56bb8874be3fb --- /dev/null +++ b/definition/ICustomUserStatus.ts @@ -0,0 +1,6 @@ +import { IRocketChatRecord } from './IRocketChatRecord'; + +export interface ICustomUserStatus extends IRocketChatRecord { + name: string; + statusType: string; +} diff --git a/definition/IEmojiCustom.ts b/definition/IEmojiCustom.ts new file mode 100644 index 0000000000000..a7d874f834eaf --- /dev/null +++ b/definition/IEmojiCustom.ts @@ -0,0 +1,7 @@ +import { IRocketChatRecord } from './IRocketChatRecord'; + +export interface IEmojiCustom extends IRocketChatRecord { + name: string; + aliases: string; + extension: string; +} diff --git a/definition/IExportOperation.ts b/definition/IExportOperation.ts new file mode 100644 index 0000000000000..9ccd591c4823a --- /dev/null +++ b/definition/IExportOperation.ts @@ -0,0 +1,16 @@ +import { IRocketChatRecord } from './IRocketChatRecord'; + +export interface IExportOperation extends IRocketChatRecord { + roomList?: string[]; + status: string; + fileList: string[]; + generatedFile?: string; + fileId: string; + userNameTable: string; + userData: string; + generatedUserFile: string; + generatedAvatar: string; + exportPath: string; + assetsPath: string; + createdAt: Date; +} diff --git a/definition/IIntegration.ts b/definition/IIntegration.ts index 5ad55faab937a..fa851e5a12088 100644 --- a/definition/IIntegration.ts +++ b/definition/IIntegration.ts @@ -1,5 +1,9 @@ -export interface IIntegration { - _id: string; +import { IRocketChatRecord } from './IRocketChatRecord'; +import { IUser } from './IUser'; + +export interface IIntegration extends IRocketChatRecord { type: string; enabled: boolean; + channel: string; + _createdBy: IUser; } diff --git a/definition/IIntegrationHistory.ts b/definition/IIntegrationHistory.ts index 2149aff521bea..04e1df6cbb03d 100644 --- a/definition/IIntegrationHistory.ts +++ b/definition/IIntegrationHistory.ts @@ -1,5 +1,6 @@ -export interface IIntegrationHistory { - _id: string; +import { IRocketChatRecord } from './IRocketChatRecord'; + +export interface IIntegrationHistory extends IRocketChatRecord { type: string; step: string; integration: { @@ -8,7 +9,20 @@ export interface IIntegrationHistory { event: string; _createdAt: Date; _updatedAt: Date; - // "data" : + data?: { + user?: any; + room?: any; + }; ranPrepareScript: boolean; finished: boolean; + + triggerWord?: string; + prepareSentMessage?: string; + processSentMessage?: string; + url?: string; + httpCallData?: string; + httpError?: any; + httpResult?: string; + error?: any; + errorStack?: any; } diff --git a/definition/IInvite.ts b/definition/IInvite.ts new file mode 100644 index 0000000000000..b02a02576257d --- /dev/null +++ b/definition/IInvite.ts @@ -0,0 +1,11 @@ +import { IRocketChatRecord } from './IRocketChatRecord'; + +export interface IInvite extends IRocketChatRecord { + days: number; + maxUses: number; + rid: string; + userId: string; + createdAt: Date; + expires: Date; + uses: number; +} diff --git a/definition/ILivechatBusinessHour.ts b/definition/ILivechatBusinessHour.ts index cdb18c9fd4beb..ab2ec4f70315a 100644 --- a/definition/ILivechatBusinessHour.ts +++ b/definition/ILivechatBusinessHour.ts @@ -21,6 +21,7 @@ export interface IBusinessHourWorkHour { start: IBusinessHourTime; finish: IBusinessHourTime; open: boolean; + code: unknown; } export interface IBusinessHourTimezone { diff --git a/definition/IOAuthApps.ts b/definition/IOAuthApps.ts new file mode 100644 index 0000000000000..71e004a067bd5 --- /dev/null +++ b/definition/IOAuthApps.ts @@ -0,0 +1,14 @@ +export interface IOAuthApps { + _id: string; + + name: string; + active: boolean; + clientId: string; + clientSecret: string; + redirectUri: string; + _createdAt: Date; + _createdBy: { + _id: string; + username: string; + }; +} diff --git a/definition/IOEmbedCache.ts b/definition/IOEmbedCache.ts new file mode 100644 index 0000000000000..b99341bddf63c --- /dev/null +++ b/definition/IOEmbedCache.ts @@ -0,0 +1,6 @@ +export interface IOEmbedCache { + _id: string; + + data: any; + updatedAt: Date; +} diff --git a/definition/IReport.ts b/definition/IReport.ts new file mode 100644 index 0000000000000..61ee97bb9914e --- /dev/null +++ b/definition/IReport.ts @@ -0,0 +1,9 @@ +import { IRocketChatRecord } from './IRocketChatRecord'; +import type { IMessage } from './IMessage/IMessage'; + +export interface IReport extends IRocketChatRecord { + message: IMessage; + description: string; + ts: Date; + userId: string; +} diff --git a/definition/IRoom.ts b/definition/IRoom.ts index 62d68e97de2cc..8597a9035e82d 100644 --- a/definition/IRoom.ts +++ b/definition/IRoom.ts @@ -61,6 +61,8 @@ export interface IRoom extends IRocketChatRecord { sysMes?: string[]; muted?: string[]; + + usernames?: string[]; } export interface ICreatedRoom extends IRoom { @@ -73,6 +75,8 @@ export interface IDirectMessageRoom extends Omit; } +export const isDirectMessageRoom = (room: Partial): room is IDirectMessageRoom => room.t === 'd'; + export enum OmnichannelSourceType { WIDGET = 'widget', EMAIL = 'email', diff --git a/definition/IServerEvent.ts b/definition/IServerEvent.ts index edc831fb7c566..a0aab45e7a817 100644 --- a/definition/IServerEvent.ts +++ b/definition/IServerEvent.ts @@ -10,5 +10,5 @@ export interface IServerEvent { t: IServerEventType; ts: Date; ip: string; - u?: Partial; + u?: Partial>; } diff --git a/definition/ISession.ts b/definition/ISession.ts new file mode 100644 index 0000000000000..93981f655cf70 --- /dev/null +++ b/definition/ISession.ts @@ -0,0 +1,29 @@ +export interface ISession { + _id: string; + + type: string; + mostImportantRole: string; + userId: string; + lastActivityAt: Date; + device: { + type: string; + name: string; + longVersion: string; + os: { + name: string; + version: string; + }; + version: string; + }; + year: number; + month: number; + day: number; + instanceId: string; + sessionId: string; + _updatedAt: Date; + createdAt: Date; + host: string; + ip: string; + loginAt: Date; + closedAt: Date; +} diff --git a/definition/ISetting.ts b/definition/ISetting.ts index 096d25bcae856..635506db1c2e6 100644 --- a/definition/ISetting.ts +++ b/definition/ISetting.ts @@ -57,7 +57,7 @@ export interface ISettingBase { wizard?: { step: number; order: number; - }; + } | null; persistent?: boolean; // todo: remove readonly?: boolean; // todo: remove alert?: string; // todo: check if this is still used @@ -97,6 +97,7 @@ export interface ISettingCode extends ISettingBase { export interface ISettingAction extends ISettingBase { type: 'action'; + value: string; actionText?: string; } export interface ISettingAsset extends ISettingBase { @@ -104,6 +105,14 @@ export interface ISettingAsset extends ISettingBase { value: AssetValue; } +export interface ISettingDate extends ISettingBase { + type: 'date'; + value: Date; +} + +export const isDateSetting = (setting: ISetting): setting is ISettingDate => setting.type === 'date'; + + export const isSettingEnterprise = (setting: ISettingBase): setting is ISettingEnterprise => setting.enterprise === true; export const isSettingColor = (setting: ISettingBase): setting is ISettingColor => setting.type === 'color'; diff --git a/definition/ISmarshHistory.ts b/definition/ISmarshHistory.ts new file mode 100644 index 0000000000000..43ba0d8f91a8c --- /dev/null +++ b/definition/ISmarshHistory.ts @@ -0,0 +1,6 @@ +export interface ISmarshHistory { + _id: string; + + lastRan: Date; + lastResult: string; +} diff --git a/definition/IStatistic.ts b/definition/IStatistic.ts new file mode 100644 index 0000000000000..c94e92e5da5fc --- /dev/null +++ b/definition/IStatistic.ts @@ -0,0 +1,24 @@ +export interface IStatistic { + _id: string; + + version: string; + instanceCount: number; + oplogEnabled: boolean; + totalUsers: number; + activeUsers: number; + nonActiveUsers: number; + onlineUsers: number; + awayUsers: number; + offlineUsers: number; + totalRooms: number; + totalChannels: number; + totalPrivateGroups: number; + totalDirect: number; + totalLivechat: number; + totalMessages: number; + totalChannelMessages: number; + totalPrivateGroupMessages: number; + totalDirectMessages: number; + totalLivechatMessages: number; + pushQueue: number; +} diff --git a/definition/IUpload.ts b/definition/IUpload.ts new file mode 100644 index 0000000000000..1eba78e441da1 --- /dev/null +++ b/definition/IUpload.ts @@ -0,0 +1,12 @@ +import { IRocketChatRecord } from './IRocketChatRecord'; + +export interface IUpload extends IRocketChatRecord { + typeGroup?: string; + type?: string; + name: string; + aliases?: string; + extension?: string; + complete?: boolean; + uploading?: boolean; + progress?: number; +} diff --git a/definition/IUser.ts b/definition/IUser.ts index 732c684961ba0..e40908cec83af 100644 --- a/definition/IUser.ts +++ b/definition/IUser.ts @@ -94,7 +94,8 @@ export interface IRole { mandatory2fa?: boolean; name: string; protected: boolean; - scope?: string; + // scope?: string; + scope?: 'Users' | 'Subscriptions'; _id: string; } @@ -143,6 +144,12 @@ export interface IUser extends IRocketChatRecord { ldap?: boolean; } +export interface IRegisterUser extends IUser { + username: string; + name: string; +} +export const isRegisterUser = (user: IUser): user is IRegisterUser => user.username !== undefined && user.name !== undefined; + export type IUserDataEvent = { id: unknown; } diff --git a/definition/IUserDataFile.ts b/definition/IUserDataFile.ts new file mode 100644 index 0000000000000..8be01d1c88d2a --- /dev/null +++ b/definition/IUserDataFile.ts @@ -0,0 +1,13 @@ +import { IRocketChatRecord } from './IRocketChatRecord'; + +export interface IUserDataFile extends IRocketChatRecord { + name: string; + rid: string; + userId: string; + store: string; + complete: boolean; + uploading: boolean; + progress: number; + extension: string; + uploadedAt: Date; +} diff --git a/definition/IWebdavAccount.ts b/definition/IWebdavAccount.ts new file mode 100644 index 0000000000000..264df6f57546f --- /dev/null +++ b/definition/IWebdavAccount.ts @@ -0,0 +1,9 @@ +import { IRocketChatRecord } from './IRocketChatRecord'; + +export interface IWebdavAccount extends IRocketChatRecord { + user_id: string; + server_url: string; + username: string; + password: string; + name: string; +} diff --git a/definition/externals/meteor/meteor.d.ts b/definition/externals/meteor/meteor.d.ts index f4e6c3a65c447..d2f9bc1c83005 100644 --- a/definition/externals/meteor/meteor.d.ts +++ b/definition/externals/meteor/meteor.d.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/interface-name-prefix */ +import 'meteor/meteor'; declare module 'meteor/meteor' { namespace Meteor { diff --git a/ee/app/auditing/server/index.js b/ee/app/auditing/server/index.ts similarity index 79% rename from ee/app/auditing/server/index.js rename to ee/app/auditing/server/index.ts index a816e3a1bc2ca..57ac621de7dd3 100644 --- a/ee/app/auditing/server/index.js +++ b/ee/app/auditing/server/index.ts @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { onLicense } from '../../license/server'; -import { Permissions, Roles } from '../../../../app/models/server'; +import { Permissions, Roles } from '../../../../app/models/server/raw'; onLicense('auditing', () => { require('./methods'); @@ -14,8 +14,8 @@ onLicense('auditing', () => { ]; const defaultRoles = [ - { name: 'auditor', scope: 'Users' }, - { name: 'auditor-log', scope: 'Users' }, + { name: 'auditor', scope: 'Users' as const }, + { name: 'auditor-log', scope: 'Users' as const }, ]; permissions.forEach((permission) => { @@ -23,7 +23,7 @@ onLicense('auditing', () => { }); defaultRoles.forEach((role) => - Roles.createOrUpdate(role.name, role.scope, role.description), + Roles.createOrUpdate(role.name, role.scope), ); }); }); diff --git a/ee/app/authorization/server/resetEnterprisePermissions.js b/ee/app/authorization/server/resetEnterprisePermissions.js deleted file mode 100644 index cebc88ba83be6..0000000000000 --- a/ee/app/authorization/server/resetEnterprisePermissions.js +++ /dev/null @@ -1,6 +0,0 @@ -import { Permissions } from '../../../../app/models/server'; -import { guestPermissions } from '../lib/guestPermissions'; - -export const resetEnterprisePermissions = function() { - Permissions.update({ _id: { $nin: guestPermissions } }, { $pull: { roles: 'guest' } }, { multi: true }); -}; diff --git a/ee/app/authorization/server/resetEnterprisePermissions.ts b/ee/app/authorization/server/resetEnterprisePermissions.ts new file mode 100644 index 0000000000000..2c7cd9def87e1 --- /dev/null +++ b/ee/app/authorization/server/resetEnterprisePermissions.ts @@ -0,0 +1,7 @@ + +import { Permissions } from '../../../../app/models/server/raw'; +import { guestPermissions } from '../lib/guestPermissions'; + +export const resetEnterprisePermissions = async function(): Promise { + await Permissions.update({ _id: { $nin: guestPermissions } }, { $pull: { roles: 'guest' } }, { multi: true }); +}; diff --git a/ee/app/canned-responses/server/permissions.js b/ee/app/canned-responses/server/permissions.ts similarity index 90% rename from ee/app/canned-responses/server/permissions.js rename to ee/app/canned-responses/server/permissions.ts index 26e838540df1b..f32650dd09b9e 100644 --- a/ee/app/canned-responses/server/permissions.js +++ b/ee/app/canned-responses/server/permissions.ts @@ -1,6 +1,6 @@ import { Meteor } from 'meteor/meteor'; -import Permissions from '../../../../app/models/server/models/Permissions'; +import { Permissions } from '../../../../app/models/server/raw'; Meteor.startup(() => { Permissions.create('view-canned-responses', ['livechat-agent', 'livechat-monitor', 'livechat-manager', 'admin']); diff --git a/ee/app/engagement-dashboard/server/lib/messages.js b/ee/app/engagement-dashboard/server/lib/messages.js index e49443dc58291..f4208928fcbb2 100644 --- a/ee/app/engagement-dashboard/server/lib/messages.js +++ b/ee/app/engagement-dashboard/server/lib/messages.js @@ -1,9 +1,7 @@ import moment from 'moment'; -import AnalyticsRaw from '../../../../../app/models/server/raw/Analytics'; import { roomTypes } from '../../../../../app/utils'; -import { Messages } from '../../../../../app/models/server/raw'; -import { Analytics } from '../../../../../app/models/server'; +import { Messages, Analytics } from '../../../../../app/models/server/raw'; import { convertDateToInt, diffBetweenDaysInclusive, convertIntToDate, getTotalOfWeekItems } from './date'; export const handleMessagesSent = (message, room) => { @@ -11,7 +9,7 @@ export const handleMessagesSent = (message, room) => { if (!roomTypesToShow.includes(room.t)) { return message; } - Promise.await(AnalyticsRaw.saveMessageSent({ + Promise.await(Analytics.saveMessageSent({ date: convertDateToInt(message.ts), room, })); @@ -23,7 +21,7 @@ export const handleMessagesDeleted = (message, room) => { if (!roomTypesToShow.includes(room.t)) { return; } - Promise.await(AnalyticsRaw.saveMessageDeleted({ + Promise.await(Analytics.saveMessageDeleted({ date: convertDateToInt(message.ts), room, })); @@ -31,7 +29,7 @@ export const handleMessagesDeleted = (message, room) => { }; export const fillFirstDaysOfMessagesIfNeeded = async (date) => { - const messagesFromAnalytics = await AnalyticsRaw.findByTypeBeforeDate({ + const messagesFromAnalytics = await Analytics.findByTypeBeforeDate({ type: 'messages', date: convertDateToInt(date), }).toArray(); @@ -41,10 +39,10 @@ export const fillFirstDaysOfMessagesIfNeeded = async (date) => { start: startOfPeriod, end: date, }); - messages.forEach((message) => Analytics.insert({ + await Promise.all(messages.map((message) => Analytics.insertOne({ ...message, date: parseInt(message.date), - })); + }))); } }; @@ -54,16 +52,16 @@ export const findWeeklyMessagesSentData = async ({ start, end }) => { const startOfLastWeek = moment(endOfLastWeek).clone().subtract(daysBetweenDates, 'days').toDate(); const today = convertDateToInt(end); const yesterday = convertDateToInt(moment(end).clone().subtract(1, 'days').toDate()); - const currentPeriodMessages = await AnalyticsRaw.getMessagesSentTotalByDate({ + const currentPeriodMessages = await Analytics.getMessagesSentTotalByDate({ start: convertDateToInt(start), end: convertDateToInt(end), options: { count: daysBetweenDates, sort: { _id: -1 } }, - }); - const lastPeriodMessages = await AnalyticsRaw.getMessagesSentTotalByDate({ + }).toArray(); + const lastPeriodMessages = await Analytics.getMessagesSentTotalByDate({ start: convertDateToInt(startOfLastWeek), end: convertDateToInt(endOfLastWeek), options: { count: daysBetweenDates, sort: { _id: -1 } }, - }); + }).toArray(); const yesterdayMessages = (currentPeriodMessages.find((item) => item._id === yesterday) || {}).messages || 0; const todayMessages = (currentPeriodMessages.find((item) => item._id === today) || {}).messages || 0; const currentPeriodTotalOfMessages = getTotalOfWeekItems(currentPeriodMessages, 'messages'); @@ -82,10 +80,10 @@ export const findWeeklyMessagesSentData = async ({ start, end }) => { }; export const findMessagesSentOrigin = async ({ start, end }) => { - const origins = await AnalyticsRaw.getMessagesOrigin({ + const origins = await Analytics.getMessagesOrigin({ start: convertDateToInt(start), end: convertDateToInt(end), - }); + }).toArray(); const roomTypesToShow = roomTypes.getTypesToShowOnDashboard(); const responseTypes = origins.map((origin) => origin.t); const missingTypes = roomTypesToShow.filter((type) => !responseTypes.includes(type)); @@ -96,10 +94,10 @@ export const findMessagesSentOrigin = async ({ start, end }) => { }; export const findTopFivePopularChannelsByMessageSentQuantity = async ({ start, end }) => { - const channels = await AnalyticsRaw.getMostPopularChannelsByMessagesSentQuantity({ + const channels = await Analytics.getMostPopularChannelsByMessagesSentQuantity({ start: convertDateToInt(start), end: convertDateToInt(end), options: { count: 5, sort: { messages: -1 } }, - }); + }).toArray(); return { channels }; }; diff --git a/ee/app/engagement-dashboard/server/lib/users.js b/ee/app/engagement-dashboard/server/lib/users.js index 69c72c007e06b..b9d8738827c17 100644 --- a/ee/app/engagement-dashboard/server/lib/users.js +++ b/ee/app/engagement-dashboard/server/lib/users.js @@ -1,9 +1,6 @@ import moment from 'moment'; -import AnalyticsRaw from '../../../../../app/models/server/raw/Analytics'; -import Sessions from '../../../../../app/models/server/raw/Sessions'; -import { Users } from '../../../../../app/models/server/raw'; -import { Analytics } from '../../../../../app/models/server'; +import { Users, Analytics, Sessions } from '../../../../../app/models/server/raw'; import { convertDateToInt, diffBetweenDaysInclusive, getTotalOfWeekItems, convertIntToDate } from './date'; export const handleUserCreated = (user) => { @@ -11,7 +8,7 @@ export const handleUserCreated = (user) => { return; } - Promise.await(AnalyticsRaw.saveUserData({ + Promise.await(Analytics.saveUserData({ date: convertDateToInt(user.ts), user, })); @@ -19,7 +16,7 @@ export const handleUserCreated = (user) => { }; export const fillFirstDaysOfUsersIfNeeded = async (date) => { - const usersFromAnalytics = await AnalyticsRaw.findByTypeBeforeDate({ + const usersFromAnalytics = await Analytics.findByTypeBeforeDate({ type: 'users', date: convertDateToInt(date), }).toArray(); @@ -29,7 +26,7 @@ export const fillFirstDaysOfUsersIfNeeded = async (date) => { start: startOfPeriod, end: date, }); - users.forEach((user) => Analytics.insert({ + users.forEach((user) => Analytics.insertOne({ ...user, date: parseInt(user.date), })); @@ -42,16 +39,16 @@ export const findWeeklyUsersRegisteredData = async ({ start, end }) => { const startOfLastWeek = moment(endOfLastWeek).clone().subtract(daysBetweenDates, 'days').toDate(); const today = convertDateToInt(end); const yesterday = convertDateToInt(moment(end).clone().subtract(1, 'days').toDate()); - const currentPeriodUsers = await AnalyticsRaw.getTotalOfRegisteredUsersByDate({ + const currentPeriodUsers = await Analytics.getTotalOfRegisteredUsersByDate({ start: convertDateToInt(start), end: convertDateToInt(end), options: { count: daysBetweenDates, sort: { _id: -1 } }, - }); - const lastPeriodUsers = await AnalyticsRaw.getTotalOfRegisteredUsersByDate({ + }).toArray(); + const lastPeriodUsers = await Analytics.getTotalOfRegisteredUsersByDate({ start: convertDateToInt(startOfLastWeek), end: convertDateToInt(endOfLastWeek), options: { count: daysBetweenDates, sort: { _id: -1 } }, - }); + }).toArray(); const yesterdayUsers = (currentPeriodUsers.find((item) => item._id === yesterday) || {}).users || 0; const todayUsers = (currentPeriodUsers.find((item) => item._id === today) || {}).users || 0; const currentPeriodTotalUsers = getTotalOfWeekItems(currentPeriodUsers, 'users'); diff --git a/ee/app/livechat-enterprise/server/permissions.js b/ee/app/livechat-enterprise/server/permissions.js deleted file mode 100644 index ef2b44ffd0b77..0000000000000 --- a/ee/app/livechat-enterprise/server/permissions.js +++ /dev/null @@ -1,22 +0,0 @@ -import { Permissions, Roles } from '../../../../app/models/server'; - -export const createPermissions = () => { - if (!Permissions) { - return; - } - - const livechatMonitorRole = 'livechat-monitor'; - const livechatManagerRole = 'livechat-manager'; - const adminRole = 'admin'; - - const monitorRole = Roles.findOneById(livechatMonitorRole, { fields: { _id: 1 } }); - if (!monitorRole) { - Roles.createOrUpdate(livechatMonitorRole); - } - - Permissions.create('manage-livechat-units', [adminRole, livechatManagerRole]); - Permissions.create('manage-livechat-monitors', [adminRole, livechatManagerRole]); - Permissions.create('manage-livechat-tags', [adminRole, livechatManagerRole]); - Permissions.create('manage-livechat-priorities', [adminRole, livechatManagerRole]); - Permissions.create('manage-livechat-canned-responses', [adminRole, livechatManagerRole, livechatMonitorRole]); -}; diff --git a/ee/app/livechat-enterprise/server/permissions.ts b/ee/app/livechat-enterprise/server/permissions.ts new file mode 100644 index 0000000000000..1b0ef93e0a688 --- /dev/null +++ b/ee/app/livechat-enterprise/server/permissions.ts @@ -0,0 +1,21 @@ + +import { Permissions, Roles } from '../../../../app/models/server/raw'; + +export const createPermissions = async (): Promise => { + const livechatMonitorRole = 'livechat-monitor'; + const livechatManagerRole = 'livechat-manager'; + const adminRole = 'admin'; + + const monitorRole = await Roles.findOneById(livechatMonitorRole, { fields: { _id: 1 } }); + if (!monitorRole) { + await Roles.createOrUpdate(livechatMonitorRole); + } + + await Promise.all([ + Permissions.create('manage-livechat-units', [adminRole, livechatManagerRole]), + Permissions.create('manage-livechat-monitors', [adminRole, livechatManagerRole]), + Permissions.create('manage-livechat-tags', [adminRole, livechatManagerRole]), + Permissions.create('manage-livechat-priorities', [adminRole, livechatManagerRole]), + Permissions.create('manage-livechat-canned-responses', [adminRole, livechatManagerRole, livechatMonitorRole]), + ]); +}; diff --git a/ee/server/lib/ldap/Manager.ts b/ee/server/lib/ldap/Manager.ts index 7230dbdb6c6d9..6b1c46b34eedb 100644 --- a/ee/server/lib/ldap/Manager.ts +++ b/ee/server/lib/ldap/Manager.ts @@ -8,10 +8,10 @@ import type { IRole } from '../../../../definition/IRole'; import { IImportUser } from '../../../../definition/IImportUser'; import { ImporterAfterImportCallback } from '../../../../app/importer/server/definitions/IConversionCallbacks'; import { settings } from '../../../../app/settings/server'; -import { Roles, Rooms } from '../../../../app/models/server'; +import { Rooms } from '../../../../app/models/server'; import { Users as UsersRaw, - Roles as RolesRaw, + Roles, Subscriptions as SubscriptionsRaw, } from '../../../../app/models/server/raw'; import { LDAPDataConverter } from '../../../../server/lib/ldap/DataConverter'; @@ -191,7 +191,7 @@ export class LDAPEEManager extends LDAPManager { return; } - const roles = await RolesRaw.find({}, { + const roles = await Roles.find({}, { fields: { _updatedAt: 0, }, @@ -224,7 +224,7 @@ export class LDAPEEManager extends LDAPManager { logger.debug(`User role exists for mapping ${ ldapField } -> ${ roleName }`); if (await this.isUserInGroup(ldap, syncUserRolesBaseDN, syncUserRolesFilter, { dn, username }, ldapField)) { - if (Roles.addUserRoles(user._id, roleName)) { + if (await Roles.addUserRoles(user._id, roleName)) { this.broadcastRoleChange('added', roleName, user._id, username); } logger.debug(`Synced user group ${ roleName } from LDAP for ${ user.username }`); @@ -235,7 +235,7 @@ export class LDAPEEManager extends LDAPManager { continue; } - if (Roles.removeUserRoles(user._id, roleName)) { + if (await Roles.removeUserRoles(user._id, roleName)) { this.broadcastRoleChange('removed', roleName, user._id, username); } } diff --git a/ee/server/lib/oauth/Manager.ts b/ee/server/lib/oauth/Manager.ts index 9d8222c1ca684..bce7a3e34b7e9 100644 --- a/ee/server/lib/oauth/Manager.ts +++ b/ee/server/lib/oauth/Manager.ts @@ -1,7 +1,8 @@ import { addUserRoles, removeUserFromRoles } from '../../../../app/authorization/server'; -import { Roles, Rooms } from '../../../../app/models/server'; +import { Rooms } from '../../../../app/models/server'; import { addUserToRoom, createRoom } from '../../../../app/lib/server/functions'; import { Logger } from '../../../../app/logger/server'; +import { Roles } from '../../../../app/models/server/raw'; export const logger = new Logger('OAuth'); @@ -64,7 +65,7 @@ export class OAuthEEManager { if (identity && roleClaimName) { // Adding roles if (identity[roleClaimName] && Array.isArray(identity[roleClaimName])) { - roles = identity[roleClaimName].filter((val: string) => val !== 'offline_access' && val !== 'uma_authorization' && Roles.findOneByIdOrName(val)); + roles = identity[roleClaimName].filter((val: string) => val !== 'offline_access' && val !== 'uma_authorization' && Promise.await(Roles.findOneByIdOrName(val))); } } diff --git a/ee/server/services/ddp-streamer/streams/index.ts b/ee/server/services/ddp-streamer/streams/index.ts index a34fd2757eec7..8d058b1a87c2f 100644 --- a/ee/server/services/ddp-streamer/streams/index.ts +++ b/ee/server/services/ddp-streamer/streams/index.ts @@ -14,10 +14,11 @@ const notifications = new NotificationsModule(Stream); getConnection() .then((db) => { + const Users = new UsersRaw(db.collection(Collections.User)); notifications.configure({ Rooms: new RoomsRaw(db.collection(Collections.Rooms)), - Subscriptions: new SubscriptionsRaw(db.collection(Collections.Subscriptions)), - Users: new UsersRaw(db.collection(Collections.User)), + Subscriptions: new SubscriptionsRaw(db.collection(Collections.Subscriptions), { Users }), + Users, Settings: new SettingsRaw(db.collection(Collections.Settings)), }); }); diff --git a/ee/server/services/stream-hub/StreamHub.ts b/ee/server/services/stream-hub/StreamHub.ts index b559e43a7f750..ebb4830ead123 100755 --- a/ee/server/services/stream-hub/StreamHub.ts +++ b/ee/server/services/stream-hub/StreamHub.ts @@ -31,13 +31,13 @@ export class StreamHub extends ServiceClass implements IServiceClass { const Rooms = new RoomsRaw(db.collection('rocketchat_room'), Trash); const Settings = new SettingsRaw(db.collection('rocketchat_settings'), Trash); const Users = new UsersRaw(UsersCol, Trash); - const UsersSessions = new UsersSessionsRaw(db.collection('usersSessions'), Trash); - const Subscriptions = new SubscriptionsRaw(db.collection('rocketchat_subscription'), Trash); + const UsersSessions = new UsersSessionsRaw(db.collection('usersSessions'), Trash, { preventSetUpdatedAt: true }); + const Subscriptions = new SubscriptionsRaw(db.collection('rocketchat_subscription'), { Users }, Trash); const LivechatInquiry = new LivechatInquiryRaw(db.collection('rocketchat_livechat_inquiry'), Trash); const LivechatDepartmentAgents = new LivechatDepartmentAgentsRaw(db.collection('rocketchat_livechat_department_agents'), Trash); const Messages = new MessagesRaw(db.collection('rocketchat_message'), Trash); const Permissions = new PermissionsRaw(db.collection('rocketchat_permissions'), Trash); - const Roles = new RolesRaw(db.collection('rocketchat_roles'), Trash, { Users, Subscriptions }); + const Roles = new RolesRaw(db.collection('rocketchat_roles'), { Users, Subscriptions }, Trash); const LoginServiceConfiguration = new LoginServiceConfigurationRaw(db.collection('meteor_accounts_loginServiceConfiguration'), Trash); const InstanceStatus = new InstanceStatusRaw(db.collection('instances'), Trash); const IntegrationHistory = new IntegrationHistoryRaw(db.collection('rocketchat_integration_history'), Trash); diff --git a/imports/message-read-receipt/server/api/methods/getReadReceipts.js b/imports/message-read-receipt/server/api/methods/getReadReceipts.js index e13647249217d..810c7db70d659 100644 --- a/imports/message-read-receipt/server/api/methods/getReadReceipts.js +++ b/imports/message-read-receipt/server/api/methods/getReadReceipts.js @@ -5,7 +5,7 @@ import { canAccessRoom } from '../../../../../app/authorization/server'; import { ReadReceipt } from '../../lib/ReadReceipt'; Meteor.methods({ - getReadReceipts({ messageId }) { + async getReadReceipts({ messageId }) { if (!Meteor.userId()) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getReadReceipts' }); } diff --git a/imports/message-read-receipt/server/lib/ReadReceipt.js b/imports/message-read-receipt/server/lib/ReadReceipt.js index ab30a835763b6..82310014d5e5b 100644 --- a/imports/message-read-receipt/server/lib/ReadReceipt.js +++ b/imports/message-read-receipt/server/lib/ReadReceipt.js @@ -1,13 +1,12 @@ import { Meteor } from 'meteor/meteor'; import { Random } from 'meteor/random'; -import { ReadReceipts, Subscriptions, Messages, Rooms, Users, LivechatVisitors } from '../../../../app/models'; -import { settings } from '../../../../app/settings'; -import { roomTypes } from '../../../../app/utils'; +import { Subscriptions, Messages, Rooms, Users, LivechatVisitors } from '../../../../app/models/server'; +import { ReadReceipts } from '../../../../app/models/server/raw'; +import { settings } from '../../../../app/settings/server'; +import { roomTypes } from '../../../../app/utils/server'; import { SystemLogger } from '../../../../server/lib/logger/system'; -const rawReadReceipts = ReadReceipts.model.rawCollection(); - // debounced function by roomId, so multiple calls within 2 seconds to same roomId runs only once const list = {}; const debounceByRoomId = function(fn) { @@ -85,17 +84,21 @@ export const ReadReceipt = { } try { - await rawReadReceipts.insertMany(receipts); + await ReadReceipts.insertMany(receipts); } catch (e) { SystemLogger.error('Error inserting read receipts per user'); } } }, - getReceipts(message) { - return ReadReceipts.findByMessageId(message._id).map((receipt) => ({ + async getReceipts(message) { + const receipts = await ReadReceipts.findByMessageId(message._id).toArray(); + + return receipts.map((receipt) => ({ ...receipt, - user: receipt.token ? LivechatVisitors.getVisitorByToken(receipt.token, { fields: { username: 1, name: 1 } }) : Users.findOneById(receipt.userId, { fields: { username: 1, name: 1 } }), + user: receipt.token + ? LivechatVisitors.getVisitorByToken(receipt.token, { fields: { username: 1, name: 1 } }) + : Users.findOneById(receipt.userId, { fields: { username: 1, name: 1 } }), })); }, }; diff --git a/package-lock.json b/package-lock.json index c679ed74f1dbf..fb1db4f80cf91 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9916,6 +9916,15 @@ } } }, + "@types/node-rsa": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/node-rsa/-/node-rsa-1.1.1.tgz", + "integrity": "sha512-itzxtaBgk4OMbrCawVCvas934waMZWjW17v7EYgFVlfYS/cl0/P7KZdojWCq9SDJMI5cnLQLUP8ayhVCTY8TEg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/nodemailer": { "version": "6.4.2", "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.2.tgz", diff --git a/package.json b/package.json index 77fd4943a19bc..c5865032264dd 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "@types/moment-timezone": "^0.5.30", "@types/mongodb": "^3.6.19", "@types/node": "^12.20.10", + "@types/node-rsa": "^1.1.1", "@types/nodemailer": "^6.4.2", "@types/parseurl": "^1.3.1", "@types/psl": "^1.1.0", diff --git a/server/cron/statistics.js b/server/cron/statistics.js index 7c51e51963cf5..8f268bf0c1a58 100644 --- a/server/cron/statistics.js +++ b/server/cron/statistics.js @@ -5,8 +5,8 @@ import { getWorkspaceAccessToken } from '../../app/cloud/server'; import { statistics } from '../../app/statistics'; import { settings } from '../../app/settings/server'; -function generateStatistics(logger) { - const cronStatistics = statistics.save(); +async function generateStatistics(logger) { + const cronStatistics = await statistics.save(); cronStatistics.host = Meteor.absoluteUrl(); diff --git a/server/database/readSecondaryPreferred.ts b/server/database/readSecondaryPreferred.ts index b23e538a319c3..e29e90b4dc923 100644 --- a/server/database/readSecondaryPreferred.ts +++ b/server/database/readSecondaryPreferred.ts @@ -1,6 +1,6 @@ -import { ReadPreference, Db } from 'mongodb'; +import { ReadPreference, Db, ReadPreferenceOrMode } from 'mongodb'; -export function readSecondaryPreferred(db: Db, tags: any[] = []): ReadPreference | string { +export function readSecondaryPreferred(db: Db, tags: any[] = []): ReadPreferenceOrMode { const { readPreferenceTags, readPreference } = db.options; if (tags.length) { diff --git a/server/features/EmailInbox/EmailInbox.ts b/server/features/EmailInbox/EmailInbox.ts index f483e546a4aba..4e8f4aac86070 100644 --- a/server/features/EmailInbox/EmailInbox.ts +++ b/server/features/EmailInbox/EmailInbox.ts @@ -55,7 +55,7 @@ export async function configureEmailInboxes(): Promise { } try { - await EmailMessageHistory.insertOne({ _id: email.messageId, email: emailInboxRecord.email }); + await EmailMessageHistory.create({ _id: email.messageId, email: emailInboxRecord.email }); onEmailReceived(email, emailInboxRecord.email, emailInboxRecord.department); } catch (e: any) { // In case the email message history has been received by other instance.. diff --git a/server/features/EmailInbox/EmailInbox_Outgoing.ts b/server/features/EmailInbox/EmailInbox_Outgoing.ts index f9cf4e1d21402..271729d20a62b 100644 --- a/server/features/EmailInbox/EmailInbox_Outgoing.ts +++ b/server/features/EmailInbox/EmailInbox_Outgoing.ts @@ -8,7 +8,8 @@ import { IEmailInbox } from '../../../definition/IEmailInbox'; import { IUser } from '../../../definition/IUser'; import { FileUpload } from '../../../app/file-upload/server'; import { slashCommands } from '../../../app/utils/server'; -import { Messages, Rooms, Uploads, Users } from '../../../app/models/server'; +import { Messages, Rooms, Users } from '../../../app/models/server'; +import { Uploads } from '../../../app/models/server/raw'; import { Inbox, inboxes } from './EmailInbox'; import { sendMessage } from '../../../app/lib/server/functions/sendMessage'; import { settings } from '../../../app/settings/server'; @@ -81,7 +82,11 @@ slashCommands.add('sendEmailAttachment', (command: any, params: string) => { }); } - const file = Uploads.findOneById(message.file._id); + const file = Promise.await(Uploads.findOneById(message.file._id)); + + if (!file) { + return; + } FileUpload.getBuffer(file, (_err?: Error, buffer?: Buffer) => { !_err && buffer && sendEmail(inbox, { diff --git a/server/lib/channelExport.ts b/server/lib/channelExport.ts index 720304cc9247a..d8a046522482e 100644 --- a/server/lib/channelExport.ts +++ b/server/lib/channelExport.ts @@ -156,9 +156,9 @@ export const sendFile = async (data: ExportFile, user: IUser): Promise => await exportMessages(); - fullFileList.forEach((attachmentData: any) => { - copyFile(attachmentData, assetsPath); - }); + for await (const attachmentData of fullFileList) { + await copyFile(attachmentData, assetsPath); + } const exportFile = `${ baseDir }-export.zip`; await makeZipFile(exportPath, exportFile); diff --git a/server/lib/migrations.ts b/server/lib/migrations.ts index f83db8d418ddd..a7be121af9d8c 100644 --- a/server/lib/migrations.ts +++ b/server/lib/migrations.ts @@ -135,7 +135,7 @@ function migrate(direction: 'up' | 'down', migration: IMigration): void { log.startup(`Running ${ direction }() on version ${ migration.version }${ migration.name ? `(${ migration.name })` : '' }`); - migration[direction]?.(migration); + Promise.await(migration[direction]?.(migration)); } diff --git a/server/lib/pushConfig.js b/server/lib/pushConfig.ts similarity index 83% rename from server/lib/pushConfig.js rename to server/lib/pushConfig.ts index bb70e738c4920..150617458b5fe 100644 --- a/server/lib/pushConfig.js +++ b/server/lib/pushConfig.ts @@ -2,13 +2,13 @@ import { Meteor } from 'meteor/meteor'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { getWorkspaceAccessToken } from '../../app/cloud/server'; -import { hasRole } from '../../app/authorization'; -import { settings } from '../../app/settings'; +import { hasRole } from '../../app/authorization/server'; +import { settings } from '../../app/settings/server'; import { appTokensCollection, Push } from '../../app/push/server'; Meteor.methods({ - push_test() { + 'push_test'() { const user = Meteor.user(); if (!user) { @@ -71,17 +71,25 @@ Meteor.methods({ }, }); -function configurePush() { - if (!settings.get('Push_enable')) { +settings.watch('Push_enable', async function(enabled) { + if (!enabled) { return; } - const gateways = settings.get('Push_enable_gateway') && settings.get('Register_Server') && settings.get('Cloud_Service_Agree_PrivacyTerms') - ? settings.get('Push_gateway').split('\n') + ? settings.get('Push_gateway').split('\n') : undefined; - let apn; - let gcm; + let apn: { + apiKey?: string; + passphrase: string; + key: string; + cert: string; + gateway?: string; + } | undefined; + let gcm: { + apiKey: string; + projectNumber: string; + } | undefined; if (!gateways) { gcm = { @@ -120,9 +128,7 @@ function configurePush() { gateways, uniqueId: settings.get('uniqueID'), getAuthorization() { - return `Bearer ${ getWorkspaceAccessToken() }`; + return `Bearer ${ Promise.await(getWorkspaceAccessToken()) }`; }, }); -} - -Meteor.startup(configurePush); +}); diff --git a/server/lib/sendMessagesToAdmins.js b/server/lib/sendMessagesToAdmins.js index 5eedbf14aeaec..08af72647e963 100644 --- a/server/lib/sendMessagesToAdmins.js +++ b/server/lib/sendMessagesToAdmins.js @@ -1,47 +1,45 @@ + import { Meteor } from 'meteor/meteor'; import { SystemLogger } from './logger/system'; -import { Roles, Users } from '../../app/models/server'; +import { Users } from '../../app/models/server'; +import { Roles } from '../../app/models/server/raw'; -export function sendMessagesToAdmins({ - fromId = 'rocket.cat', - checkFrom = true, - msgs = [], - banners = [], -}) { - const fromUser = checkFrom ? Users.findOneById(fromId, { fields: { _id: 1 } }) : true; +export async function sendMessagesToAdmins(message) { + const fromUser = message.checkFrom ? Users.findOneById(message.fromId, { fields: { _id: 1 } }) : true; + const users = await Roles.findUsersInRole('admin'); - Roles.findUsersInRole('admin').forEach((adminUser) => { + users.forEach((adminUser) => { if (fromUser) { try { - Meteor.runAsUser(fromId, () => Meteor.call('createDirectMessage', adminUser.username)); + Meteor.runAsUser(message.fromId, () => Meteor.call('createDirectMessage', adminUser.username)); - const rid = [adminUser._id, fromId].sort().join(''); + const rid = [adminUser._id, message.fromId].sort().join(''); - if (typeof msgs === 'function') { - msgs = msgs({ adminUser }); + if (typeof message.msgs === 'function') { + message.msgs = message.msgs({ adminUser }); } - if (!Array.isArray(msgs)) { - msgs = [msgs]; + if (!Array.isArray(message.msgs)) { + message.msgs = [message.msgs]; } - if (typeof banners === 'function') { - banners = banners({ adminUser }); + if (typeof message.banners === 'function') { + message.banners = message.banners({ adminUser }); } - if (!Array.isArray(banners)) { - banners = Array.from(banners); + if (!Array.isArray(message.banners)) { + message.banners = [message.banners]; } - Meteor.runAsUser(fromId, () => { - msgs.forEach((msg) => Meteor.call('sendMessage', Object.assign({ rid }, msg))); + Meteor.runAsUser(message.fromId, () => { + message.msgs.forEach((msg) => Meteor.call('sendMessage', Object.assign({ rid }, msg))); }); } catch (e) { SystemLogger.error(e); } } - banners.forEach((banner) => Users.addBannerById(adminUser._id, banner)); + message.banners.forEach((banner) => Users.addBannerById(adminUser._id, banner)); }); } diff --git a/server/methods/OEmbedCacheCleanup.js b/server/methods/OEmbedCacheCleanup.js index 168cc4aba10bc..d2a8d651467bc 100644 --- a/server/methods/OEmbedCacheCleanup.js +++ b/server/methods/OEmbedCacheCleanup.js @@ -1,11 +1,11 @@ import { Meteor } from 'meteor/meteor'; -import { OEmbedCache } from '../../app/models'; +import { OEmbedCache } from '../../app/models/server/raw'; import { settings } from '../../app/settings'; import { hasRole } from '../../app/authorization'; Meteor.methods({ - OEmbedCacheCleanup() { + async OEmbedCacheCleanup() { if (Meteor.userId() && !hasRole(Meteor.userId(), 'admin')) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'OEmbedCacheCleanup', @@ -15,7 +15,7 @@ Meteor.methods({ const date = new Date(); const expirationDays = settings.get('API_EmbedCacheExpirationDays'); date.setDate(date.getDate() - expirationDays); - OEmbedCache.removeAfterDate(date); + await OEmbedCache.removeAfterDate(date); return { message: 'cache_cleared', }; diff --git a/server/methods/afterVerifyEmail.js b/server/methods/afterVerifyEmail.js deleted file mode 100644 index 5e1d65004d5b7..0000000000000 --- a/server/methods/afterVerifyEmail.js +++ /dev/null @@ -1,29 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { Users, Roles } from '../../app/models'; - -Meteor.methods({ - afterVerifyEmail() { - const userId = Meteor.userId(); - - if (!userId) { - throw new Meteor.Error('error-invalid-user', 'Invalid user', { - method: 'afterVerifyEmail', - }); - } - - const user = Users.findOneById(userId); - if (user && user.emails && Array.isArray(user.emails)) { - const verifiedEmail = user.emails.find((email) => email.verified); - const rolesToChangeTo = { anonymous: ['user'] }; - const rolesThatNeedChanges = user.roles.filter((role) => rolesToChangeTo[role]); - - if (rolesThatNeedChanges.length && verifiedEmail) { - rolesThatNeedChanges.forEach((role) => { - Roles.addUserRoles(user._id, rolesToChangeTo[role]); - Roles.removeUserRoles(user._id, role); - }); - } - } - }, -}); diff --git a/server/methods/afterVerifyEmail.ts b/server/methods/afterVerifyEmail.ts new file mode 100644 index 0000000000000..6f6a621fc3159 --- /dev/null +++ b/server/methods/afterVerifyEmail.ts @@ -0,0 +1,39 @@ +import { Meteor } from 'meteor/meteor'; + +import { Users } from '../../app/models/server'; +import { Roles } from '../../app/models/server/raw'; +import { IUser } from '../../definition/IUser'; + +const rolesToChangeTo: Map = new Map([ + ['anonymous', ['user']], +]); + +Meteor.methods({ + async afterVerifyEmail() { + const userId = Meteor.userId(); + + if (!userId) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { + method: 'afterVerifyEmail', + }); + } + + const user = Users.findOneById(userId) as IUser; + if (user && user.emails && Array.isArray(user.emails)) { + const verifiedEmail = user.emails.find((email) => email.verified); + + const rolesThatNeedChanges = user.roles.filter((role) => rolesToChangeTo.has(role)); + + + if (verifiedEmail) { + await Promise.all(rolesThatNeedChanges.map(async (role) => { + const rolesToAdd = rolesToChangeTo.get(role); + if (rolesToAdd) { + await Roles.addUserRoles(userId, rolesToAdd); + } + await Roles.removeUserRoles(user._id, [role]); + })); + } + } + }, +}); diff --git a/server/methods/browseChannels.js b/server/methods/browseChannels.js index 03bb78922c7c0..9fc942a1657bc 100644 --- a/server/methods/browseChannels.js +++ b/server/methods/browseChannels.js @@ -145,7 +145,7 @@ const getTeams = (user, searchTerm, sort, pagination) => { }; }; -const getUsers = (user, text, workspace, sort, pagination) => { +const getUsers = async (user, text, workspace, sort, pagination) => { if (!user || !hasPermission(user._id, 'view-outside-room') || !hasPermission(user._id, 'view-d-room')) { return; } @@ -183,7 +183,7 @@ const getUsers = (user, text, workspace, sort, pagination) => { // Try to find federated users, when applicable if (isFederationEnabled() && workspace === 'external' && text.indexOf('@') !== -1) { - const users = federationSearchUsers(text); + const users = await federationSearchUsers(text); for (const user of users) { if (results.find((e) => e._id === user._id)) { continue; } @@ -208,7 +208,7 @@ const getUsers = (user, text, workspace, sort, pagination) => { }; Meteor.methods({ - browseChannels({ text = '', workspace = '', type = 'channels', sortBy = 'name', sortDirection = 'asc', page, offset, limit = 10 }) { + async browseChannels({ text = '', workspace = '', type = 'channels', sortBy = 'name', sortDirection = 'asc', page, offset, limit = 10 }) { const searchTerm = s.trim(escapeRegExp(text)); if (!['channels', 'users', 'teams'].includes(type) || !['asc', 'desc'].includes(sortDirection) || ((!page && page !== 0) && (!offset && offset !== 0))) { diff --git a/server/methods/createDirectMessage.js b/server/methods/createDirectMessage.js index 151298a89c727..0a729ce723802 100644 --- a/server/methods/createDirectMessage.js +++ b/server/methods/createDirectMessage.js @@ -36,7 +36,7 @@ export function createDirectMessage(usernames, userId, excludeSelf = false) { // If the username does have an `@`, but does not exist locally, we create it first if (!to && username.indexOf('@') !== -1) { - to = addUser(username); + to = Promise.await(addUser(username)); } if (!to) { diff --git a/server/methods/deleteFileMessage.js b/server/methods/deleteFileMessage.js index 18353d1a45c82..a35a407f92118 100644 --- a/server/methods/deleteFileMessage.js +++ b/server/methods/deleteFileMessage.js @@ -5,7 +5,7 @@ import { FileUpload } from '../../app/file-upload'; import { Messages } from '../../app/models'; Meteor.methods({ - deleteFileMessage(fileID) { + async deleteFileMessage(fileID) { check(fileID, String); const msg = Messages.getMessageByFileId(fileID); diff --git a/server/methods/deleteUser.js b/server/methods/deleteUser.js index f47da58045e4e..20ea77dd30b15 100644 --- a/server/methods/deleteUser.js +++ b/server/methods/deleteUser.js @@ -7,7 +7,7 @@ import { callbacks } from '../../app/callbacks/server'; import { deleteUser } from '../../app/lib/server'; Meteor.methods({ - deleteUser(userId, confirmRelinquish = false) { + async deleteUser(userId, confirmRelinquish = false) { check(userId, String); if (!Meteor.userId()) { @@ -46,7 +46,7 @@ Meteor.methods({ }); } - deleteUser(userId, confirmRelinquish); + await deleteUser(userId, confirmRelinquish); callbacks.run('afterDeleteUser', user); diff --git a/server/methods/registerUser.js b/server/methods/registerUser.js index cef1d1b8398be..802f271efc408 100644 --- a/server/methods/registerUser.js +++ b/server/methods/registerUser.js @@ -9,7 +9,7 @@ import { validateEmailDomain, passwordPolicy } from '../../app/lib'; import { validateInviteToken } from '../../app/invites/server/functions/validateInviteToken'; Meteor.methods({ - registerUser(formData) { + async registerUser(formData) { const AllowAnonymousRead = settings.get('Accounts_AllowAnonymousRead'); const AllowAnonymousWrite = settings.get('Accounts_AllowAnonymousWrite'); const manuallyApproveNewUsers = settings.get('Accounts_ManuallyApproveNewUsers'); @@ -45,7 +45,7 @@ Meteor.methods({ } try { - validateInviteToken(formData.secretURL); + await validateInviteToken(formData.secretURL); } catch (e) { throw new Meteor.Error('error-user-registration-secret', 'User registration is only allowed via Secret URL', { method: 'registerUser' }); } diff --git a/server/methods/removeRoomOwner.js b/server/methods/removeRoomOwner.ts similarity index 86% rename from server/methods/removeRoomOwner.js rename to server/methods/removeRoomOwner.ts index 9ae598883c6be..adb33a7b29b52 100644 --- a/server/methods/removeRoomOwner.js +++ b/server/methods/removeRoomOwner.ts @@ -1,14 +1,14 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; -import { hasPermission, getUsersInRole } from '../../app/authorization'; -import { Users, Subscriptions, Messages } from '../../app/models'; -import { settings } from '../../app/settings'; +import { hasPermission, getUsersInRole } from '../../app/authorization/server'; +import { Users, Subscriptions, Messages } from '../../app/models/server'; +import { settings } from '../../app/settings/server'; import { api } from '../sdk/api'; import { Team } from '../sdk'; Meteor.methods({ - removeRoomOwner(rid, userId) { + async removeRoomOwner(rid, userId) { check(rid, String); check(userId, String); @@ -45,7 +45,7 @@ Meteor.methods({ }); } - const numOwners = getUsersInRole('owner', rid).count(); + const numOwners = await (await getUsersInRole('owner', rid)).count(); if (numOwners === 1) { throw new Meteor.Error('error-remove-last-owner', 'This is the last owner. Please set a new owner before removing this one.', { @@ -65,9 +65,9 @@ Meteor.methods({ role: 'owner', }); - const team = Promise.await(Team.getOneByMainRoomId(rid)); + const team = await Team.getOneByMainRoomId(rid); if (team) { - Promise.await(Team.removeRolesFromMember(team._id, userId, ['owner'])); + await Team.removeRolesFromMember(team._id, userId, ['owner']); } if (settings.get('UI_DisplayRoles')) { diff --git a/server/methods/removeUserFromRoom.js b/server/methods/removeUserFromRoom.ts similarity index 89% rename from server/methods/removeUserFromRoom.js rename to server/methods/removeUserFromRoom.ts index 70576c73ced40..d4b8ac0f9a011 100644 --- a/server/methods/removeUserFromRoom.js +++ b/server/methods/removeUserFromRoom.ts @@ -1,14 +1,14 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; -import { hasPermission, hasRole, getUsersInRole, removeUserFromRoles } from '../../app/authorization'; -import { Users, Subscriptions, Rooms, Messages } from '../../app/models'; -import { callbacks } from '../../app/callbacks'; +import { hasPermission, hasRole, getUsersInRole, removeUserFromRoles } from '../../app/authorization/server'; +import { Users, Subscriptions, Rooms, Messages } from '../../app/models/server'; +import { callbacks } from '../../app/callbacks/server'; import { roomTypes, RoomMemberActions } from '../../app/utils/server'; import { Team } from '../sdk'; Meteor.methods({ - removeUserFromRoom(data) { + async removeUserFromRoom(data) { check(data, Match.ObjectIncluding({ rid: String, username: String, @@ -48,7 +48,7 @@ Meteor.methods({ } if (hasRole(removedUser._id, 'owner', room._id)) { - const numOwners = getUsersInRole('owner', room._id).fetch().length; + const numOwners = await (await getUsersInRole('owner', room._id)).count(); if (numOwners === 1) { throw new Meteor.Error('error-you-are-last-owner', 'You are the last owner. Please set new owner before leaving the room.', { @@ -74,7 +74,7 @@ Meteor.methods({ if (room.teamId && room.teamMain) { // if a user is kicked from the main team room, delete the team membership - Promise.await(Team.removeMember(room.teamId, removedUser._id)); + await Team.removeMember(room.teamId, removedUser._id); } Meteor.defer(function() { diff --git a/server/methods/reportMessage.js b/server/methods/reportMessage.js index 9ff33ef8426d7..ea2f42daa1e50 100644 --- a/server/methods/reportMessage.js +++ b/server/methods/reportMessage.js @@ -1,10 +1,11 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; -import { Messages, Reports } from '../../app/models'; +import { Messages } from '../../app/models/server'; +import { Reports } from '../../app/models/server/raw'; Meteor.methods({ - reportMessage(messageId, description) { + async reportMessage(messageId, description) { check(messageId, String); check(description, String); @@ -27,6 +28,8 @@ Meteor.methods({ }); } - return Reports.createWithMessageDescriptionAndUserId(message, description, Meteor.userId()); + await Reports.createWithMessageDescriptionAndUserId(message, description, Meteor.userId()); + + return true; }, }); diff --git a/server/methods/requestDataDownload.js b/server/methods/requestDataDownload.js index f388b4831bd00..a49654f4807a8 100644 --- a/server/methods/requestDataDownload.js +++ b/server/methods/requestDataDownload.js @@ -4,8 +4,8 @@ import path from 'path'; import mkdirp from 'mkdirp'; import { Meteor } from 'meteor/meteor'; -import { ExportOperations, UserDataFiles } from '../../app/models'; -import { settings } from '../../app/settings'; +import { ExportOperations, UserDataFiles } from '../../app/models/server/raw'; +import { settings } from '../../app/settings/server'; import { DataExport } from '../../app/user-data-download/server/DataExport'; let tempFolder = '/tmp/userData'; @@ -16,13 +16,13 @@ if (settings.get('UserData_FileSystemPath') != null) { } Meteor.methods({ - requestDataDownload({ fullExport = false }) { + async requestDataDownload({ fullExport = false }) { const currentUserData = Meteor.user(); const userId = currentUserData._id; - const lastOperation = ExportOperations.findLastOperationByUser(userId, fullExport); + const lastOperation = await ExportOperations.findLastOperationByUser(userId, fullExport); const requestDay = lastOperation ? lastOperation.createdAt : new Date(); - const pendingOperationsBeforeMyRequestCount = ExportOperations.findAllPendingBeforeMyRequest(requestDay).count(); + const pendingOperationsBeforeMyRequestCount = await ExportOperations.findAllPendingBeforeMyRequest(requestDay).count(); if (lastOperation) { const yesterday = new Date(); @@ -30,7 +30,7 @@ Meteor.methods({ if (lastOperation.createdAt > yesterday) { if (lastOperation.status === 'completed') { - const file = lastOperation.fileId ? UserDataFiles.findOneById(lastOperation.fileId) : UserDataFiles.findLastFileByUser(userId); + const file = lastOperation.fileId ? await UserDataFiles.findOneById(lastOperation.fileId) : await UserDataFiles.findLastFileByUser(userId); if (file) { return { requested: false, @@ -64,7 +64,7 @@ Meteor.methods({ userData: currentUserData, }; - const id = ExportOperations.create(exportOperation); + const id = await ExportOperations.create(exportOperation); exportOperation._id = id; const folderName = path.join(tempFolder, id); @@ -81,7 +81,7 @@ Meteor.methods({ exportOperation.assetsPath = assetsFolder; exportOperation.status = 'pending'; - ExportOperations.updateOperation(exportOperation); + await ExportOperations.updateOperation(exportOperation); return { requested: true, diff --git a/server/modules/watchers/watchers.module.ts b/server/modules/watchers/watchers.module.ts index b95a6b535bad2..9ba8b2ada9918 100644 --- a/server/modules/watchers/watchers.module.ts +++ b/server/modules/watchers/watchers.module.ts @@ -364,13 +364,13 @@ export function initWatchers(models: IModelsParam, broadcast: BroadcastCallback, } }); - watch(Integrations, async ({ clientAction, id, data }) => { + watch(Integrations, async ({ clientAction, id, data: eventData }) => { if (clientAction === 'removed') { broadcast('watch.integrations', { clientAction, id, data: { _id: id } }); return; } - data = data ?? await Integrations.findOneById(id); + const data = eventData ?? await Integrations.findOneById(id); if (!data) { return; } diff --git a/server/publications/settings/index.js b/server/publications/settings/index.ts similarity index 55% rename from server/publications/settings/index.js rename to server/publications/settings/index.ts index 3e9db1beab857..2c54b6fc0eff3 100644 --- a/server/publications/settings/index.js +++ b/server/publications/settings/index.ts @@ -1,19 +1,20 @@ import { Meteor } from 'meteor/meteor'; -import { Settings } from '../../../app/models/server'; import { hasPermission, hasAtLeastOnePermission } from '../../../app/authorization/server'; import { getSettingPermissionId } from '../../../app/authorization/lib'; import { SettingsEvents } from '../../../app/settings/server'; +import { Settings } from '../../../app/models/server/raw'; +import { ISetting } from '../../../definition/ISetting'; Meteor.methods({ - 'public-settings/get'(updatedAt) { + async 'public-settings/get'(updatedAt) { if (updatedAt instanceof Date) { - const records = Settings.findNotHiddenPublicUpdatedAfter(updatedAt).fetch(); + const records = await Settings.findNotHiddenPublicUpdatedAfter(updatedAt).toArray(); SettingsEvents.emit('fetch-settings', records); return { update: records, - remove: Settings.trashFindDeletedAfter(updatedAt, { + remove: await Settings.trashFindDeletedAfter(updatedAt, { hidden: { $ne: true, }, @@ -23,16 +24,16 @@ Meteor.methods({ _id: 1, _deletedAt: 1, }, - }).fetch(), + }).toArray(), }; } - const publicSettings = Settings.findNotHiddenPublic().fetch(); + const publicSettings = await Settings.findNotHiddenPublic().toArray() as ISetting[]; SettingsEvents.emit('fetch-settings', publicSettings); return publicSettings; }, - 'private-settings/get'(updatedAfter) { + async 'private-settings/get'(updatedAfter) { const uid = Meteor.userId(); if (!uid) { @@ -46,13 +47,13 @@ Meteor.methods({ return []; } - const bypass = (settings) => settings; + const bypass = (settings: T): T => settings; - const applyFilter = (fn, args) => fn(args); + const applyFilter = (fn: Function, args: any[]): any => fn(args); - const getAuthorizedSettingsFiltered = (settings) => settings.filter((record) => hasPermission(uid, getSettingPermissionId(record._id))); + const getAuthorizedSettingsFiltered = (settings: ISetting[]): ISetting[] => settings.filter((record) => hasPermission(uid, getSettingPermissionId(record._id))); - const getAuthorizedSettings = (updatedAfter, privilegedSetting) => applyFilter(privilegedSetting ? bypass : getAuthorizedSettingsFiltered, Settings.findNotHidden(updatedAfter && { updatedAfter }).fetch()); + const getAuthorizedSettings = async (updatedAfter: Date, privilegedSetting: boolean): Promise => applyFilter(privilegedSetting ? bypass : getAuthorizedSettingsFiltered, await Settings.findNotHidden(updatedAfter && { updatedAfter }).toArray()); if (!(updatedAfter instanceof Date)) { // this does not only imply an unfiltered setting range, it also identifies the caller's context: @@ -62,8 +63,8 @@ Meteor.methods({ } return { - update: getAuthorizedSettings(updatedAfter, privilegedSetting), - remove: Settings.trashFindDeletedAfter(updatedAfter, { + update: await getAuthorizedSettings(updatedAfter, privilegedSetting), + remove: await Settings.trashFindDeletedAfter(updatedAfter, { hidden: { $ne: true, }, @@ -72,7 +73,7 @@ Meteor.methods({ _id: 1, _deletedAt: 1, }, - }).fetch(), + }).toArray(), }; }, }); diff --git a/server/routes/avatar/room.js b/server/routes/avatar/room.js index 7f359f31f79f1..000395d74aa96 100644 --- a/server/routes/avatar/room.js +++ b/server/routes/avatar/room.js @@ -7,17 +7,18 @@ import { setCacheAndDispositionHeaders, } from './utils'; import { FileUpload } from '../../../app/file-upload'; -import { Rooms, Avatars } from '../../../app/models/server'; +import { Rooms } from '../../../app/models/server'; +import { Avatars } from '../../../app/models/server/raw'; import { roomTypes } from '../../../app/utils'; -const getRoomAvatar = (roomId) => { +const getRoomAvatar = async (roomId) => { const room = Rooms.findOneById(roomId, { fields: { t: 1, prid: 1, name: 1, fname: 1 } }); if (!room) { return {}; } - const file = Avatars.findOneByRoomId(room._id); + const file = await Avatars.findOneByRoomId(room._id); // if it is a discussion that doesn't have it's own avatar, returns the parent's room avatar if (room.prid && !file) { @@ -27,10 +28,10 @@ const getRoomAvatar = (roomId) => { return { room, file }; }; -export const roomAvatar = Meteor.bindEnvironment(function(req, res/* , next*/) { +export const roomAvatar = Meteor.bindEnvironment(async function(req, res/* , next*/) { const roomId = decodeURIComponent(req.url.substr(1).replace(/\?.*$/, '')); - const { room, file } = getRoomAvatar(roomId); + const { room, file } = await getRoomAvatar(roomId); if (!room) { res.writeHead(404); res.end(); diff --git a/server/routes/avatar/user.js b/server/routes/avatar/user.js index ac93faca25631..2b93d20b4e7fa 100644 --- a/server/routes/avatar/user.js +++ b/server/routes/avatar/user.js @@ -8,11 +8,12 @@ import { } from './utils'; import { FileUpload } from '../../../app/file-upload'; import { settings } from '../../../app/settings/server'; -import { Users, Avatars } from '../../../app/models/server'; +import { Users } from '../../../app/models/server'; +import { Avatars } from '../../../app/models/server/raw'; // request /avatar/@name forces returning the svg -export const userAvatar = Meteor.bindEnvironment(function(req, res) { +export const userAvatar = Meteor.bindEnvironment(async function(req, res) { const requestUsername = decodeURIComponent(req.url.substr(1).replace(/\?.*$/, '')); if (!requestUsername) { @@ -34,7 +35,7 @@ export const userAvatar = Meteor.bindEnvironment(function(req, res) { const reqModifiedHeader = req.headers['if-modified-since']; - const file = Avatars.findOneByName(requestUsername); + const file = await Avatars.findOneByName(requestUsername); if (file) { res.setHeader('Content-Security-Policy', 'default-src \'none\''); diff --git a/server/sdk/types/ITeamService.ts b/server/sdk/types/ITeamService.ts index ad4a790ab6fef..50396cd20552b 100644 --- a/server/sdk/types/ITeamService.ts +++ b/server/sdk/types/ITeamService.ts @@ -70,6 +70,7 @@ export interface ITeamService { members(uid: string, teamId: string, canSeeAll: boolean, options?: IPaginationOptions, queryOptions?: FilterQuery): Promise>; addMembers(uid: string, teamId: string, members: Array): Promise; updateMember(teamId: string, members: ITeamMemberParams): Promise; + removeMember(teamId: string, userId: string): Promise; removeMembers(uid: string, teamId: string, members: Array): Promise; getInfoByName(teamName: string): Promise | null>; getInfoById(teamId: string): Promise | null>; @@ -90,4 +91,5 @@ export interface ITeamService { insertMemberOnTeams(userId: string, teamIds: Array): Promise; removeMemberFromTeams(userId: string, teamIds: Array): Promise; removeAllMembersFromTeam(teamId: string): Promise; + removeRolesFromMember(teamId: string, userId: string, roles: Array): Promise; } diff --git a/server/services/analytics/service.ts b/server/services/analytics/service.ts index b2d4b8dee9e08..ab69003a733e7 100644 --- a/server/services/analytics/service.ts +++ b/server/services/analytics/service.ts @@ -1,8 +1,9 @@ -import { Db } from 'mongodb'; +import type { Db } from 'mongodb'; import { ServiceClass } from '../../sdk/types/ServiceClass'; import { IAnalyticsService } from '../../sdk/types/IAnalyticsService'; import { AnalyticsRaw } from '../../../app/models/server/raw/Analytics'; +import { IAnalyticsSeatRequest } from '../../../definition/IAnalytic'; export class AnalyticsService extends ServiceClass implements IAnalyticsService { protected name = 'analytics'; @@ -19,8 +20,8 @@ export class AnalyticsService extends ServiceClass implements IAnalyticsService } async getSeatRequestCount(): Promise { - const result = await this.Analytics.findOne({ type: 'seat-request' }); - return result ? result.count : 0; + const result = await this.Analytics.findOne({ type: 'seat-request' }, {}); + return result?.count ? result.count : 0; } async resetSeatRequestCount(): Promise { diff --git a/server/services/authorization/service.ts b/server/services/authorization/service.ts index f5c6b0a1fa430..728d5a49c591a 100644 --- a/server/services/authorization/service.ts +++ b/server/services/authorization/service.ts @@ -45,9 +45,16 @@ export class Authorization extends ServiceClass implements IAuthorization { this.Permissions = db.collection('rocketchat_permissions'); this.Users = new UsersRaw(db.collection('users')); - this.Roles = new RolesRaw(db.collection('rocketchat_roles')); - Subscriptions = new SubscriptionsRaw(db.collection('rocketchat_subscription')); + Subscriptions = new SubscriptionsRaw(db.collection('rocketchat_subscription'), { + Users: this.Users, + }); + + this.Roles = new RolesRaw(db.collection('rocketchat_roles'), { + Users: this.Users, + Subscriptions, + }); + Settings = new SettingsRaw(db.collection('rocketchat_settings')); Rooms = new RoomsRaw(db.collection('rocketchat_room')); TeamMembers = new TeamMemberRaw(db.collection('rocketchat_team_member')); @@ -98,7 +105,7 @@ export class Authorization extends ServiceClass implements IAuthorization { } private getPublicRoles = mem(async (): Promise => { - const roles = await this.Roles.find>({ scope: 'Users', description: { $exists: 1, $ne: '' } }, { projection: { _id: 1 } }).toArray(); + const roles = await this.Roles.find>({ scope: 'Users', description: { $exists: true, $ne: '' } }, { projection: { _id: 1 } }).toArray(); return roles.map(({ _id }) => _id); }, { maxAge: 10000 }); diff --git a/server/services/nps/notification.ts b/server/services/nps/notification.ts index 72dd87c80397b..af48327810875 100644 --- a/server/services/nps/notification.ts +++ b/server/services/nps/notification.ts @@ -39,9 +39,9 @@ export const getBannerForAdmins = Meteor.bindEnvironment((expireAt: Date): Omit< }); export const notifyAdmins = Meteor.bindEnvironment((expireAt: Date) => { - sendMessagesToAdmins({ + Promise.await(sendMessagesToAdmins({ msgs: ({ adminUser }: { adminUser: any }): any => ({ msg: TAPi18n.__('NPS_survey_is_scheduled_to-run-at__date__for_all_users', { date: moment(expireAt).format('YYYY-MM-DD'), lng: adminUser.language }), }), - }); + })); }); diff --git a/server/services/team/service.ts b/server/services/team/service.ts index 0bc5106439a7f..d76e8bab32a91 100644 --- a/server/services/team/service.ts +++ b/server/services/team/service.ts @@ -59,10 +59,12 @@ export class TeamService extends ServiceClass implements ITeamService { super(); this.RoomsModel = new RoomsRaw(db.collection('rocketchat_room')); - this.SubscriptionsModel = new SubscriptionsRaw(db.collection('rocketchat_subscription')); + this.Users = new UsersRaw(db.collection('users')); + this.SubscriptionsModel = new SubscriptionsRaw(db.collection('rocketchat_subscription'), { + Users: this.Users, + }); this.TeamModel = new TeamRaw(db.collection('rocketchat_team')); this.TeamMembersModel = new TeamMemberRaw(db.collection('rocketchat_team_member')); - this.Users = new UsersRaw(db.collection('users')); this.MessagesModel = new MessagesRaw(db.collection('rocketchat_message')); } diff --git a/server/startup/initialData.js b/server/startup/initialData.js index 7db252c92b031..668426a7d3ca4 100644 --- a/server/startup/initialData.js +++ b/server/startup/initialData.js @@ -1,15 +1,15 @@ import { Meteor } from 'meteor/meteor'; import { Accounts } from 'meteor/accounts-base'; -import _ from 'underscore'; import { RocketChatFile } from '../../app/file'; -import { FileUpload } from '../../app/file-upload'; -import { addUserRoles, getUsersInRole } from '../../app/authorization'; -import { Users, Settings, Rooms } from '../../app/models'; -import { settings } from '../../app/settings'; -import { checkUsernameAvailability, addUserToDefaultChannels } from '../../app/lib'; - -Meteor.startup(function() { +import { FileUpload } from '../../app/file-upload/server'; +import { addUserRoles, getUsersInRole } from '../../app/authorization/server'; +import { Users, Rooms } from '../../app/models/server'; +import { settings } from '../../app/settings/server'; +import { checkUsernameAvailability, addUserToDefaultChannels } from '../../app/lib/server'; +import { Settings } from '../../app/models/server/raw'; + +Meteor.startup(async function() { if (settings.get('Show_Setup_Wizard') === 'pending' && !Rooms.findOneById('GENERAL')) { Rooms.createWithIdTypeAndName('GENERAL', 'c', 'general', { default: true, @@ -48,7 +48,7 @@ Meteor.startup(function() { } if (process.env.ADMIN_PASS) { - if (_.isEmpty(getUsersInRole('admin').fetch())) { + if (await (await getUsersInRole('admin')).count() === 0) { console.log('Inserting admin user:'.green); const adminUser = { name: 'Administrator', @@ -134,16 +134,16 @@ Meteor.startup(function() { } } - if (_.isEmpty(getUsersInRole('admin').fetch())) { + if (await (await getUsersInRole('admin')).count() === 0) { const oldestUser = Users.getOldest({ _id: 1, username: 1, name: 1 }); if (oldestUser) { - addUserRoles(oldestUser._id, 'admin'); + addUserRoles(oldestUser._id, ['admin']); console.log(`No admins are found. Set ${ oldestUser.username || oldestUser.name } as admin for being the oldest user`); } } - if (!_.isEmpty(getUsersInRole('admin').fetch())) { + if (await (await getUsersInRole('admin')).count() !== 0) { if (settings.get('Show_Setup_Wizard') === 'pending') { console.log('Setting Setup Wizard to "in_progress" because, at least, one admin was found'); Settings.updateValueById('Show_Setup_Wizard', 'in_progress'); @@ -189,7 +189,7 @@ Meteor.startup(function() { Accounts.setPassword(adminUser._id, adminUser._id); - addUserRoles(adminUser._id, 'admin'); + addUserRoles(adminUser._id, ['admin']); if (settings.get('Show_Setup_Wizard') === 'pending') { Settings.updateValueById('Show_Setup_Wizard', 'in_progress'); diff --git a/server/startup/instance.js b/server/startup/instance.js index 5dcebcb2be5b7..c4aa1ebada0b7 100644 --- a/server/startup/instance.js +++ b/server/startup/instance.js @@ -5,10 +5,6 @@ import { InstanceStatus } from 'meteor/konecty:multiple-instances-status'; import { startStreamBroadcast } from '../stream/streamBroadcast'; -export function getInstances() { - InstanceStatus.find().fetch(); -} - Meteor.startup(function() { const instance = { host: process.env.INSTANCE_IP ? String(process.env.INSTANCE_IP).trim() : 'localhost', diff --git a/server/startup/migrations/v174.js b/server/startup/migrations/v174.ts similarity index 53% rename from server/startup/migrations/v174.js rename to server/startup/migrations/v174.ts index a03aeb430a15a..f9b0639035993 100644 --- a/server/startup/migrations/v174.js +++ b/server/startup/migrations/v174.ts @@ -1,5 +1,5 @@ +import { Permissions } from '../../../app/models/server/raw'; import { addMigration } from '../../lib/migrations'; -import { Permissions } from '../../../app/models/server'; const appRolePermissions = [ 'api-bypass-rate-limit', @@ -18,9 +18,9 @@ const appRolePermissions = [ addMigration({ version: 174, up() { - Permissions.update({ _id: { $in: appRolePermissions } }, { $addToSet: { roles: 'app' } }, { multi: true }); + return Permissions.update({ _id: { $in: appRolePermissions } }, { $addToSet: { roles: 'app' } }, { multi: true }); }, down() { - Permissions.update({ _id: { $in: appRolePermissions } }, { $pull: { roles: 'app' } }, { multi: true }); + return Permissions.update({ _id: { $in: appRolePermissions } }, { $pull: { roles: 'app' } }, { multi: true }); }, }); diff --git a/server/startup/migrations/v175.js b/server/startup/migrations/v175.js index 0b12049c66d2a..0d931a02e4c15 100644 --- a/server/startup/migrations/v175.js +++ b/server/startup/migrations/v175.js @@ -1,18 +1,17 @@ import { addMigration } from '../../lib/migrations'; import { theme } from '../../../app/theme/server/server'; -import { Settings } from '../../../app/models'; +import { Settings } from '../../../app/models/server/raw'; addMigration({ version: 175, up() { - Object.entries(theme.variables) + Promise.await(Promise.all(Object.entries(theme.variables) .filter(([, value]) => value.type === 'color') - .forEach(([key, { editor }]) => { - Settings.update({ _id: `theme-color-${ key }` }, { - $set: { - packageEditor: editor, - }, - }); - }); + .map(([key, { editor }]) => Settings.update({ _id: `theme-color-${ key }` }, { + $set: { + packageEditor: editor, + }, + })), + )); }, }); diff --git a/server/startup/migrations/v176.js b/server/startup/migrations/v176.js deleted file mode 100644 index 4484febcb5a6f..0000000000000 --- a/server/startup/migrations/v176.js +++ /dev/null @@ -1,12 +0,0 @@ -import { addMigration } from '../../lib/migrations'; -import { - Settings, -} from '../../../app/models'; - - -addMigration({ - version: 176, - up() { - Settings.remove({ _id: 'Livechat', type: 'group' }); - }, -}); diff --git a/server/startup/migrations/v176.ts b/server/startup/migrations/v176.ts new file mode 100644 index 0000000000000..5dcbf7bc89805 --- /dev/null +++ b/server/startup/migrations/v176.ts @@ -0,0 +1,9 @@ +import { Settings } from '../../../app/models/server/raw'; +import { addMigration } from '../../lib/migrations'; + +addMigration({ + version: 176, + up() { + return Settings.deleteOne({ _id: 'Livechat', type: 'group' }); + }, +}); diff --git a/server/startup/migrations/v177.js b/server/startup/migrations/v177.ts similarity index 72% rename from server/startup/migrations/v177.js rename to server/startup/migrations/v177.ts index 3acca9065d7b5..51b62d3c6bf2d 100644 --- a/server/startup/migrations/v177.js +++ b/server/startup/migrations/v177.ts @@ -1,16 +1,18 @@ +import { Settings } from '../../../app/models/server/raw'; import { addMigration } from '../../lib/migrations'; -import { Settings } from '../../../app/models/server'; addMigration({ version: 177, up() { // Disable auto opt in for existent installations - Settings.upsert({ + Settings.update({ _id: 'Accounts_TwoFactorAuthentication_By_Email_Auto_Opt_In', }, { $set: { value: false, }, + }, { + upsert: true, }); }, }); diff --git a/server/startup/migrations/v178.js b/server/startup/migrations/v178.js deleted file mode 100644 index 2ee62cfd81666..0000000000000 --- a/server/startup/migrations/v178.js +++ /dev/null @@ -1,12 +0,0 @@ -import { addMigration } from '../../lib/migrations'; -import { - Settings, -} from '../../../app/models'; - - -addMigration({ - version: 178, - up() { - Settings.remove({ _id: 'Livechat_enable_inquiry_fetch_by_stream' }); - }, -}); diff --git a/server/startup/migrations/v178.ts b/server/startup/migrations/v178.ts new file mode 100644 index 0000000000000..503c3d66d1cd1 --- /dev/null +++ b/server/startup/migrations/v178.ts @@ -0,0 +1,9 @@ +import { Settings } from '../../../app/models/server/raw'; +import { addMigration } from '../../lib/migrations'; + +addMigration({ + version: 178, + up() { + return Settings.removeById('Livechat_enable_inquiry_fetch_by_stream'); + }, +}); diff --git a/server/startup/migrations/v182.js b/server/startup/migrations/v182.js index fde3cc07062c8..74a840c88a9ae 100644 --- a/server/startup/migrations/v182.js +++ b/server/startup/migrations/v182.js @@ -1,9 +1,9 @@ import { addMigration } from '../../lib/migrations'; -import { Analytics } from '../../../app/models/server'; +import { Analytics } from '../../../app/models/server/raw'; addMigration({ version: 182, up() { - Analytics.remove({}); + Analytics.deleteMany({}); }, }); diff --git a/server/startup/migrations/v183.js b/server/startup/migrations/v183.js index e031f63f028ce..525148b0f8a87 100644 --- a/server/startup/migrations/v183.js +++ b/server/startup/migrations/v183.js @@ -2,7 +2,8 @@ import { Meteor } from 'meteor/meteor'; import { Random } from 'meteor/random'; import { addMigration } from '../../lib/migrations'; -import { Rooms, Messages, Subscriptions, Uploads, Settings, Users } from '../../../app/models/server'; +import { Rooms, Messages, Subscriptions, Users } from '../../../app/models/server'; +import { Settings, Uploads } from '../../../app/models/server/raw'; const unifyRooms = (room) => { // verify if other DM already exists @@ -59,8 +60,8 @@ const fixSelfDMs = () => { }, { multi: true }); // Fix error of upload permission check using Meteor.userId() - Meteor.runAsUser(room.uids[0], () => { - Uploads.update({ rid: room._id }, { + Meteor.runAsUser(room.uids[0], async () => { + await Uploads.update({ rid: room._id }, { $set: { rid: correctId, }, @@ -90,11 +91,11 @@ const fixDiscussions = () => { }); }; -const fixUserSearch = () => { - const setting = Settings.findOneById('Accounts_SearchFields', { fields: { value: 1 } }); +const fixUserSearch = async () => { + const setting = await Settings.findOneById('Accounts_SearchFields', { projection: { value: 1 } }); const value = setting?.value?.trim(); if (value === '' || value === 'username, name') { - Settings.updateValueById('Accounts_SearchFields', 'username, name, bio'); + await Settings.updateValueById('Accounts_SearchFields', 'username, name, bio'); } Users.tryDropIndex('name_text_username_text_bio_text'); @@ -105,6 +106,6 @@ addMigration({ up() { fixDiscussions(); fixSelfDMs(); - fixUserSearch(); + return fixUserSearch(); }, }); diff --git a/server/startup/migrations/v184.js b/server/startup/migrations/v184.ts similarity index 71% rename from server/startup/migrations/v184.js rename to server/startup/migrations/v184.ts index 84112ae971a95..4542313f029f0 100644 --- a/server/startup/migrations/v184.js +++ b/server/startup/migrations/v184.ts @@ -1,16 +1,18 @@ +import { Settings } from '../../../app/models/server/raw'; import { addMigration } from '../../lib/migrations'; -import { Settings } from '../../../app/models/server'; addMigration({ version: 184, up() { // Set SAML signature validation type to 'Either' - Settings.upsert({ + Settings.update({ _id: 'SAML_Custom_Default_signature_validation_type', }, { $set: { value: 'Either', }, + }, { + upsert: true, }); }, }); diff --git a/server/startup/migrations/v185.js b/server/startup/migrations/v185.js deleted file mode 100644 index de080b1ebab4f..0000000000000 --- a/server/startup/migrations/v185.js +++ /dev/null @@ -1,19 +0,0 @@ -import { addMigration } from '../../lib/migrations'; -import { - Settings, -} from '../../../app/models/server'; - -addMigration({ - version: 185, - up() { - const setting = Settings.findOne({ _id: 'Message_SetNameToAliasEnabled' }); - if (setting && setting.value) { - Settings.update({ _id: 'UI_Use_Real_Name' }, { - $set: { - value: true, - }, - }); - } - Settings.remove({ _id: 'Message_SetNameToAliasEnabled' }); - }, -}); diff --git a/server/startup/migrations/v185.ts b/server/startup/migrations/v185.ts new file mode 100644 index 0000000000000..c9d79130aea85 --- /dev/null +++ b/server/startup/migrations/v185.ts @@ -0,0 +1,18 @@ +import { Settings } from '../../../app/models/server/raw'; +import { addMigration } from '../../lib/migrations'; + + +addMigration({ + version: 185, + async up() { + const setting = await Settings.findOne({ _id: 'Message_SetNameToAliasEnabled' }); + if (setting && setting.value) { + await Settings.update({ _id: 'UI_Use_Real_Name' }, { + $set: { + value: true, + }, + }); + } + return Settings.removeById('Message_SetNameToAliasEnabled'); + }, +}); diff --git a/server/startup/migrations/v187.js b/server/startup/migrations/v187.js index 349e4142dd541..f3f958a248dc8 100644 --- a/server/startup/migrations/v187.js +++ b/server/startup/migrations/v187.js @@ -1,8 +1,7 @@ import { Mongo } from 'meteor/mongo'; import { addMigration } from '../../lib/migrations'; -import { Settings } from '../../../app/models/server'; -import { NotificationQueue } from '../../../app/models/server/raw'; +import { NotificationQueue, Settings } from '../../../app/models/server/raw'; function convertNotification(notification) { try { @@ -55,14 +54,14 @@ async function migrateNotifications() { addMigration({ version: 187, - up() { - Settings.remove({ _id: 'Push_send_interval' }); - Settings.remove({ _id: 'Push_send_batch_size' }); - Settings.remove({ _id: 'Push_debug' }); - Settings.remove({ _id: 'Notifications_Always_Notify_Mobile' }); + async up() { + await Settings.removeById('Push_send_interval'); + await Settings.removeById('Push_send_batch_size'); + await Settings.removeById('Push_debug'); + await Settings.removeById('Notifications_Always_Notify_Mobile'); try { - Promise.await(migrateNotifications()); + await migrateNotifications(); } catch (err) { // Ignore if the collection does not exist if (!err.code || err.code !== 26) { diff --git a/server/startup/migrations/v188.js b/server/startup/migrations/v188.ts similarity index 51% rename from server/startup/migrations/v188.js rename to server/startup/migrations/v188.ts index b63ca803a96e6..50bc75990d033 100644 --- a/server/startup/migrations/v188.js +++ b/server/startup/migrations/v188.ts @@ -1,5 +1,6 @@ +import { Permissions } from '../../../app/models/server/raw'; import { addMigration } from '../../lib/migrations'; -import { Permissions } from '../../../app/models/server'; + const newRolePermissions = [ 'view-d-room', @@ -11,6 +12,6 @@ const roleName = 'guest'; addMigration({ version: 188, up() { - Permissions.update({ _id: { $in: newRolePermissions } }, { $addToSet: { roles: roleName } }, { multi: true }); + return Permissions.update({ _id: { $in: newRolePermissions } }, { $addToSet: { roles: roleName } }, { multi: true }); }, }); diff --git a/server/startup/migrations/v189.js b/server/startup/migrations/v189.js deleted file mode 100644 index 9e90f77531d2c..0000000000000 --- a/server/startup/migrations/v189.js +++ /dev/null @@ -1,11 +0,0 @@ -import { addMigration } from '../../lib/migrations'; -import { Settings } from '../../../app/models/server'; - -addMigration({ - version: 189, - up() { - Settings.remove({ _id: 'Livechat_Knowledge_Enabled' }); - Settings.remove({ _id: 'Livechat_Knowledge_Apiai_Key' }); - Settings.remove({ _id: 'Livechat_Knowledge_Apiai_Language' }); - }, -}); diff --git a/server/startup/migrations/v189.ts b/server/startup/migrations/v189.ts new file mode 100644 index 0000000000000..310bad4bc2243 --- /dev/null +++ b/server/startup/migrations/v189.ts @@ -0,0 +1,11 @@ +import { Settings } from '../../../app/models/server/raw'; +import { addMigration } from '../../lib/migrations'; + +addMigration({ + version: 189, + async up() { + await Settings.removeById('Livechat_Knowledge_Enabled'); + await Settings.removeById('Livechat_Knowledge_Apiai_Key'); + await Settings.removeById('Livechat_Knowledge_Apiai_Language'); + }, +}); diff --git a/server/startup/migrations/v190.js b/server/startup/migrations/v190.js index a49a8e3482831..52e07bc9ab62b 100644 --- a/server/startup/migrations/v190.js +++ b/server/startup/migrations/v190.js @@ -5,7 +5,7 @@ addMigration({ version: 190, up() { // Remove unused settings - Promise.await(Settings.col.deleteOne({ _id: 'Accounts_Default_User_Preferences_desktopNotificationDuration' })); + Promise.await(Settings.removeById('Accounts_Default_User_Preferences_desktopNotificationDuration')); Promise.await(Subscriptions.col.updateMany({ desktopNotificationDuration: { $exists: true, diff --git a/server/startup/migrations/v191.js b/server/startup/migrations/v191.js deleted file mode 100644 index b2d7ed2b81df0..0000000000000 --- a/server/startup/migrations/v191.js +++ /dev/null @@ -1,9 +0,0 @@ -import { addMigration } from '../../lib/migrations'; -import { Settings } from '../../../app/models/server'; - -addMigration({ - version: 191, - up() { - Settings.remove({ _id: /theme-color-status/ }, { multi: true }); - }, -}); diff --git a/server/startup/migrations/v191.ts b/server/startup/migrations/v191.ts new file mode 100644 index 0000000000000..f30c1699899f8 --- /dev/null +++ b/server/startup/migrations/v191.ts @@ -0,0 +1,9 @@ +import { Settings } from '../../../app/models/server/raw'; +import { addMigration } from '../../lib/migrations'; + +addMigration({ + version: 191, + async up() { + return Settings.deleteMany({ _id: /theme-color-status/ }); + }, +}); diff --git a/server/startup/migrations/v194.js b/server/startup/migrations/v194.js index 3129b37e50683..d7e20745beb8d 100644 --- a/server/startup/migrations/v194.js +++ b/server/startup/migrations/v194.js @@ -1,12 +1,12 @@ import { - Settings, Users, } from '../../../app/models/server'; +import { Settings } from '../../../app/models/server/raw'; import { addMigration } from '../../lib/migrations'; -function updateFieldMap() { +async function updateFieldMap() { const _id = 'SAML_Custom_Default_user_data_fieldmap'; - const setting = Settings.findOne({ _id }); + const setting = await Settings.findOne({ _id }); if (!setting || !setting.value) { return; } @@ -19,7 +19,7 @@ function updateFieldMap() { if (setting.value === '{"username":"username", "email":"email", "cn": "name"}') { // include de eppn identifier if it was used const value = `{"username":"username", "email":"email", "name": "cn"${ usedEppn ? ', "__identifier__": "eppn"' : '' }}`; - Settings.update({ _id }, { + await Settings.update({ _id }, { $set: { value, }, @@ -90,7 +90,7 @@ function updateIdentifierLocation() { function setOldLogoutResponseTemplate() { // For existing users, use a template compatible with the old SAML implementation instead of the default - Settings.upsert({ + return Settings.update({ _id: 'SAML_Custom_Default_LogoutResponse_template', }, { $set: { @@ -99,14 +99,16 @@ function setOldLogoutResponseTemplate() { `, }, + }, { + upsert: true, }); } addMigration({ version: 194, - up() { - updateFieldMap(); - updateIdentifierLocation(); - setOldLogoutResponseTemplate(); + async up() { + await updateFieldMap(); + await updateIdentifierLocation(); + await setOldLogoutResponseTemplate(); }, }); diff --git a/server/startup/migrations/v195.js b/server/startup/migrations/v195.ts similarity index 61% rename from server/startup/migrations/v195.js rename to server/startup/migrations/v195.ts index 5334f31bb2aab..89221f519bb48 100644 --- a/server/startup/migrations/v195.js +++ b/server/startup/migrations/v195.ts @@ -1,16 +1,14 @@ import moment from 'moment-timezone'; -import { ObjectId } from 'mongodb'; import { Mongo } from 'meteor/mongo'; import { addMigration } from '../../lib/migrations'; -import { Permissions, Settings } from '../../../app/models/server'; -import { LivechatBusinessHours } from '../../../app/models/server/raw'; -import { LivechatBusinessHourTypes } from '../../../definition/ILivechatBusinessHour'; +import { LivechatBusinessHours, Permissions, Settings } from '../../../app/models/server/raw'; +import { ILivechatBusinessHour, IBusinessHourWorkHour, LivechatBusinessHourTypes } from '../../../definition/ILivechatBusinessHour'; -const migrateCollection = () => { - const LivechatOfficeHour = new Mongo.Collection('rocketchat_livechat_office_hour'); +const migrateCollection = async (): Promise => { + const LivechatOfficeHour = new Mongo.Collection('rocketchat_livechat_office_hour'); const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']; - const officeHours = []; + const officeHours: IBusinessHourWorkHour[] = []; days.forEach((day) => { const officeHour = LivechatOfficeHour.findOne({ day }); if (officeHour) { @@ -22,15 +20,15 @@ const migrateCollection = () => { return; } - const businessHour = { + const businessHour: Omit = { name: '', active: true, type: LivechatBusinessHourTypes.DEFAULT, ts: new Date(), - workHours: officeHours.map((officeHour) => ({ + workHours: officeHours.map((officeHour): IBusinessHourWorkHour => ({ day: officeHour.day, start: { - time: officeHour.start, + time: officeHour.start as any, utc: { dayOfWeek: moment(`${ officeHour.day }:${ officeHour.start }`, 'dddd:HH:mm').utc().format('dddd'), time: moment(`${ officeHour.day }:${ officeHour.start }`, 'dddd:HH:mm').utc().format('HH:mm'), @@ -41,7 +39,7 @@ const migrateCollection = () => { }, }, finish: { - time: officeHour.finish, + time: officeHour.finish as any, utc: { dayOfWeek: moment(`${ officeHour.day }:${ officeHour.finish }`, 'dddd:HH:mm').utc().format('dddd'), time: moment(`${ officeHour.day }:${ officeHour.finish }`, 'dddd:HH:mm').utc().format('HH:mm'), @@ -56,11 +54,10 @@ const migrateCollection = () => { })), timezone: { name: moment.tz.guess(), - utc: moment().utcOffset() / 60, + utc: String(moment().utcOffset() / 60), }, }; - if (LivechatBusinessHours.find({ type: LivechatBusinessHourTypes.DEFAULT }).count() === 0) { - businessHour._id = new ObjectId().toHexString(); + if (await LivechatBusinessHours.find({ type: LivechatBusinessHourTypes.DEFAULT }).count() === 0) { LivechatBusinessHours.insertOne(businessHour); } else { LivechatBusinessHours.update({ type: LivechatBusinessHourTypes.DEFAULT }, { $set: { ...businessHour } }); @@ -77,14 +74,14 @@ const migrateCollection = () => { addMigration({ version: 195, - up() { - Settings.remove({ _id: 'Livechat_enable_office_hours' }); - Settings.remove({ _id: 'Livechat_allow_online_agents_outside_office_hours' }); - const permission = Permissions.findOneById('view-livechat-officeHours'); + async up() { + await Settings.removeById('Livechat_enable_office_hours'); + await Settings.removeById('Livechat_allow_online_agents_outside_office_hours'); + const permission = await Permissions.findOneById('view-livechat-officeHours'); if (permission) { - Permissions.upsert({ _id: 'view-livechat-business-hours' }, { $set: { roles: permission.roles } }); - Permissions.remove({ _id: 'view-livechat-officeHours' }); + await Permissions.update({ _id: 'view-livechat-business-hours' }, { $set: { roles: permission.roles } }, { upsert: true }); + await Permissions.deleteOne({ _id: 'view-livechat-officeHours' }); } - Promise.await(migrateCollection()); + await migrateCollection(); }, }); diff --git a/server/startup/migrations/v198.js b/server/startup/migrations/v198.ts similarity index 54% rename from server/startup/migrations/v198.js rename to server/startup/migrations/v198.ts index e2e2bf16fdb5e..1a979fe3e9485 100644 --- a/server/startup/migrations/v198.js +++ b/server/startup/migrations/v198.ts @@ -1,44 +1,50 @@ -import { Settings } from '../../../app/models/server'; +import { Settings } from '../../../app/models/server/raw'; import { addMigration } from '../../lib/migrations'; addMigration({ version: 198, - up: () => { - const discussion = Settings.findOneById('RetentionPolicy_DoNotExcludeDiscussion'); - const thread = Settings.findOneById('RetentionPolicy_DoNotExcludeThreads'); - const pinned = Settings.findOneById('RetentionPolicy_ExcludePinned'); + up: async () => { + const discussion = await Settings.findOneById('RetentionPolicy_DoNotExcludeDiscussion'); + const thread = await Settings.findOneById('RetentionPolicy_DoNotExcludeThreads'); + const pinned = await Settings.findOneById('RetentionPolicy_ExcludePinned'); if (discussion) { - Settings.upsert({ + await Settings.update({ _id: 'RetentionPolicy_DoNotPruneDiscussion', }, { $set: { value: discussion.value, }, + }, { + upsert: true, }); } if (thread) { - Settings.upsert({ + await Settings.update({ _id: 'RetentionPolicy_DoNotPruneThreads', }, { $set: { value: thread.value, }, + }, { + upsert: true, }); } if (pinned) { - Settings.upsert({ + await Settings.update({ _id: 'RetentionPolicy_DoNotPrunePinned', }, { $set: { value: pinned.value, }, + }, { + upsert: true, }); } - Settings.remove({ + return Settings.deleteMany({ _id: { $in: ['RetentionPolicy_DoNotExcludeDiscussion', 'RetentionPolicy_DoNotExcludeThreads', 'RetentionPolicy_ExcludePinned'] }, }); }, diff --git a/server/startup/migrations/v201.js b/server/startup/migrations/v201.js index 0fb5ae254412e..0a74087cf773b 100644 --- a/server/startup/migrations/v201.js +++ b/server/startup/migrations/v201.js @@ -1,17 +1,17 @@ import { Meteor } from 'meteor/meteor'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; -import { Settings } from '../../../app/models/server'; +import { Settings } from '../../../app/models/server/raw'; import { addMigration } from '../../lib/migrations'; import { sendMessagesToAdmins } from '../../lib/sendMessagesToAdmins'; addMigration({ version: 201, - up: () => { - const pushEnabled = Settings.findOneById('Push_enable'); - const pushGatewayEnabled = Settings.findOneById('Push_enable_gateway'); - const registerServer = Settings.findOneById('Register_Server'); - const cloudAgreement = Settings.findOneById('Cloud_Service_Agree_PrivacyTerms'); + up: async () => { + const pushEnabled = await Settings.findOneById('Push_enable'); + const pushGatewayEnabled = await Settings.findOneById('Push_enable_gateway'); + const registerServer = await Settings.findOneById('Register_Server'); + const cloudAgreement = await Settings.findOneById('Cloud_Service_Agree_PrivacyTerms'); if (!pushEnabled?.value) { return; @@ -24,12 +24,14 @@ addMigration({ } // if push gateway is enabled but server is not registered or cloud terms not agreed, disable gateway and alert admin - Settings.upsert({ + Settings.update({ _id: 'Push_enable_gateway', }, { $set: { value: false, }, + }, { + update: true, }); const id = 'push-gateway-disabled'; diff --git a/server/startup/migrations/v202.js b/server/startup/migrations/v202.js index 02362a2d165e9..7cb0e457f2cfa 100644 --- a/server/startup/migrations/v202.js +++ b/server/startup/migrations/v202.js @@ -1,15 +1,15 @@ import { addMigration } from '../../lib/migrations'; -import Uploads from '../../../app/models/server/models/Uploads'; +import { Uploads } from '../../../app/models/server/raw'; addMigration({ version: 202, - up() { - Promise.await(Uploads.model.rawCollection().updateMany({ + async up() { + await Uploads.updateMany({ type: 'audio/mp3', }, { $set: { type: 'audio/mpeg', }, - })); + }); }, }); diff --git a/server/startup/migrations/v203.js b/server/startup/migrations/v203.js deleted file mode 100644 index ed70284f278d0..0000000000000 --- a/server/startup/migrations/v203.js +++ /dev/null @@ -1,10 +0,0 @@ -import { addMigration } from '../../lib/migrations'; -import { Avatars } from '../../../app/models/server'; - -addMigration({ - version: 203, - up() { - Avatars.tryDropIndex({ name: 1 }); - Avatars.tryEnsureIndex({ name: 1 }, { sparse: true }); - }, -}); diff --git a/server/startup/migrations/v203.ts b/server/startup/migrations/v203.ts new file mode 100644 index 0000000000000..d5594ece72bb6 --- /dev/null +++ b/server/startup/migrations/v203.ts @@ -0,0 +1,10 @@ +import { addMigration } from '../../lib/migrations'; +import { Avatars } from '../../../app/models/server/raw'; + +addMigration({ + version: 203, + async up() { + await Avatars.col.dropIndex('name_1'); + await Avatars.col.createIndex({ name: 1 }, { sparse: true }); + }, +}); diff --git a/server/startup/migrations/v205.js b/server/startup/migrations/v205.js index 061075e17ece1..2feed878dcbc4 100644 --- a/server/startup/migrations/v205.js +++ b/server/startup/migrations/v205.js @@ -1,16 +1,18 @@ +import { Settings } from '../../../app/models/server/raw'; import { addMigration } from '../../lib/migrations'; -import { Settings } from '../../../app/models/server'; addMigration({ version: 205, up() { // Disable this new enforcement setting for existent installations. - Settings.upsert({ + Settings.update({ _id: 'Accounts_TwoFactorAuthentication_Enforce_Password_Fallback', }, { $set: { value: false, }, + }, { + upsert: true, }); }, }); diff --git a/server/startup/migrations/v207.js b/server/startup/migrations/v207.js deleted file mode 100644 index 556f663946320..0000000000000 --- a/server/startup/migrations/v207.js +++ /dev/null @@ -1,9 +0,0 @@ -import { addMigration } from '../../lib/migrations'; -import { Settings } from '../../../app/models'; - -addMigration({ - version: 207, - up() { - Settings.removeById('theme-color-tertiary-background-color'); - }, -}); diff --git a/server/startup/migrations/v207.ts b/server/startup/migrations/v207.ts new file mode 100644 index 0000000000000..cac3bd875c1c4 --- /dev/null +++ b/server/startup/migrations/v207.ts @@ -0,0 +1,10 @@ +import { Settings } from '../../../app/models/server/raw'; +import { addMigration } from '../../lib/migrations'; + + +addMigration({ + version: 207, + up() { + return Settings.removeById('theme-color-tertiary-background-color'); + }, +}); diff --git a/server/startup/migrations/v208.js b/server/startup/migrations/v208.js index 9a604c6e4a88f..ced4c5f2d5b37 100644 --- a/server/startup/migrations/v208.js +++ b/server/startup/migrations/v208.js @@ -1,41 +1,31 @@ -import Future from 'fibers/future'; - import { addMigration } from '../../lib/migrations'; import { Users, Sessions } from '../../../app/models/server/raw'; -async function migrateSessions(fut) { +async function migrateSessions() { const cursor = Users.find({ roles: 'anonymous' }, { projection: { _id: 1 } }); if (!cursor) { return; } - const users = await cursor.toArray(); if (users.length === 0) { - fut.return(); return; } const userIds = users.map(({ _id }) => _id); - Sessions.update({ + await Sessions.updateMany({ userId: { $in: userIds }, }, { $set: { roles: ['anonymous'], }, - }, { - multi: true, }); - - fut.return(); } addMigration({ version: 208, up() { - const fut = new Future(); - migrateSessions(fut); - fut.wait(); + Promise.await(migrateSessions()); }, }); diff --git a/server/startup/migrations/v214.js b/server/startup/migrations/v214.js deleted file mode 100644 index 787b8acbeb74c..0000000000000 --- a/server/startup/migrations/v214.js +++ /dev/null @@ -1,11 +0,0 @@ -import { addMigration } from '../../lib/migrations'; -import { Permissions } from '../../../app/models/server'; - -const roleName = 'admin'; - -addMigration({ - version: 214, - up() { - Permissions.update({ _id: 'toggle-room-e2e-encryption' }, { $addToSet: { roles: roleName } }); - }, -}); diff --git a/server/startup/migrations/v214.ts b/server/startup/migrations/v214.ts new file mode 100644 index 0000000000000..ec0b5491e433a --- /dev/null +++ b/server/startup/migrations/v214.ts @@ -0,0 +1,12 @@ +import { Permissions } from '../../../app/models/server/raw'; +import { addMigration } from '../../lib/migrations'; + + +const roleName = 'admin'; + +addMigration({ + version: 214, + up() { + return Permissions.update({ _id: 'toggle-room-e2e-encryption' }, { $addToSet: { roles: roleName } }); + }, +}); diff --git a/server/startup/migrations/v215.js b/server/startup/migrations/v215.ts similarity index 52% rename from server/startup/migrations/v215.js rename to server/startup/migrations/v215.ts index e3d71a070a727..bd6f083582545 100644 --- a/server/startup/migrations/v215.js +++ b/server/startup/migrations/v215.ts @@ -1,14 +1,14 @@ +import { Settings } from '../../../app/models/server/raw'; import { addMigration } from '../../lib/migrations'; -import { Settings } from '../../../app/models/server'; const removed = ['advocacy', 'industry', 'publicRelations', 'healthcarePharmaceutical', 'helpCenter']; addMigration({ version: 215, - up() { - const current = Settings.findOneById('Industry'); - if (removed.includes(current.value)) { - Settings.update({ + async up() { + const current = await Settings.findOneById('Industry'); + if (current && typeof current.value === 'string' && removed.includes(current.value)) { + await Settings.update({ _id: 'Industry', }, { $set: { diff --git a/server/startup/migrations/v216.js b/server/startup/migrations/v216.js index d004e5a32c7e7..4a3225d6b251f 100644 --- a/server/startup/migrations/v216.js +++ b/server/startup/migrations/v216.js @@ -1,42 +1,43 @@ +import { Settings } from '../../../app/models/server/raw'; import { addMigration } from '../../lib/migrations'; -import { Settings } from '../../../app/models'; addMigration({ version: 216, - up() { - Settings.find({ _id: /Accounts_OAuth_Custom/, i18nLabel: 'Accounts_OAuth_Custom_Enable' }).forEach(function(customOauth) { + async up() { + return Promise.all((await Settings.find({ _id: /Accounts_OAuth_Custom/, i18nLabel: 'Accounts_OAuth_Custom_Enable' }).toArray()).map(async function(customOauth) { const parts = customOauth._id.split('-'); const name = parts[1]; const id = `Accounts_OAuth_Custom-${ name }-key_field`; - if (!Settings.findOne({ _id: id })) { - Settings.insert({ - _id: id, - type: 'select', - group: 'OAuth', - section: `Custom OAuth: ${ name }`, - i18nLabel: 'Accounts_OAuth_Custom_Key_Field', - persistent: true, - values: [ - { - key: 'username', - i18nLabel: 'Username', - }, - { - key: 'email', - i18nLabel: 'Email', - }, - ], - packageValue: 'username', - valueSource: 'packageValue', - ts: new Date(), - hidden: false, - blocked: false, - sorter: 103, - i18nDescription: `Accounts_OAuth_Custom-${ name }-key_field_Description`, - createdAt: new Date(), - value: 'username', - }); + if (await Settings.findOne({ _id: id })) { + return; } - }); + return Settings.insert({ + _id: id, + type: 'select', + group: 'OAuth', + section: `Custom OAuth: ${ name }`, + i18nLabel: 'Accounts_OAuth_Custom_Key_Field', + persistent: true, + values: [ + { + key: 'username', + i18nLabel: 'Username', + }, + { + key: 'email', + i18nLabel: 'Email', + }, + ], + packageValue: 'username', + valueSource: 'packageValue', + ts: new Date(), + hidden: false, + blocked: false, + sorter: 103, + i18nDescription: `Accounts_OAuth_Custom-${ name }-key_field_Description`, + createdAt: new Date(), + value: 'username', + }); + })); }, }); diff --git a/server/startup/migrations/v217.js b/server/startup/migrations/v217.js deleted file mode 100644 index 3c82aeb5931c1..0000000000000 --- a/server/startup/migrations/v217.js +++ /dev/null @@ -1,12 +0,0 @@ -import { addMigration } from '../../lib/migrations'; -import { Permissions } from '../../../app/models'; - -addMigration({ - version: 217, - up() { - const oldPermission = Permissions.findOne('view-livechat-queue'); - if (oldPermission) { - Permissions.update({ _id: 'view-livechat-queue' }, { $addToSet: { roles: 'livechat-agent' } }); - } - }, -}); diff --git a/server/startup/migrations/v217.ts b/server/startup/migrations/v217.ts new file mode 100644 index 0000000000000..3ca79fccc2d5b --- /dev/null +++ b/server/startup/migrations/v217.ts @@ -0,0 +1,13 @@ +import { Permissions } from '../../../app/models/server/raw'; +import { addMigration } from '../../lib/migrations'; + + +addMigration({ + version: 217, + async up() { + const oldPermission = await Permissions.findOne('view-livechat-queue'); + if (oldPermission) { + return Permissions.update({ _id: 'view-livechat-queue' }, { $addToSet: { roles: 'livechat-agent' } }); + } + }, +}); diff --git a/server/startup/migrations/v218.js b/server/startup/migrations/v218.js deleted file mode 100644 index 01b2668a7cda1..0000000000000 --- a/server/startup/migrations/v218.js +++ /dev/null @@ -1,9 +0,0 @@ -import { addMigration } from '../../lib/migrations'; -import { Statistics } from '../../../app/models/server'; - -addMigration({ - version: 218, - up() { - Statistics.tryDropIndex({ createdAt: 1 }); - }, -}); diff --git a/server/startup/migrations/v218.ts b/server/startup/migrations/v218.ts new file mode 100644 index 0000000000000..c17991620cb25 --- /dev/null +++ b/server/startup/migrations/v218.ts @@ -0,0 +1,10 @@ +import { addMigration } from '../../lib/migrations'; +import { Statistics } from '../../../app/models/server/raw'; + +addMigration({ + version: 218, + up() { + // TODO test if dropIndex do not raise exceptions. + Statistics.col.dropIndex('createdAt_1'); + }, +}); diff --git a/server/startup/migrations/v219.js b/server/startup/migrations/v219.ts similarity index 70% rename from server/startup/migrations/v219.js rename to server/startup/migrations/v219.ts index bd16d7c18b895..cd2d5eacc06f3 100644 --- a/server/startup/migrations/v219.js +++ b/server/startup/migrations/v219.ts @@ -1,16 +1,17 @@ + +import { Settings } from '../../../app/models/server/raw'; import { addMigration } from '../../lib/migrations'; -import { Settings } from '../../../app/models/server'; addMigration({ version: 219, - up() { + async up() { const SettingIds = { old: 'Livechat_auto_close_abandoned_rooms', new: 'Livechat_abandoned_rooms_action', }; - const oldSetting = Settings.findOne({ _id: SettingIds.old }); + const oldSetting = await Settings.findOne({ _id: SettingIds.old }); if (!oldSetting) { return; } @@ -27,8 +28,6 @@ addMigration({ }, }); - Settings.remove({ - _id: SettingIds.old, - }); + return Settings.removeById(SettingIds.old); }, }); diff --git a/server/startup/migrations/v220.js b/server/startup/migrations/v220.ts similarity index 99% rename from server/startup/migrations/v220.js rename to server/startup/migrations/v220.ts index 59795ce4eff95..cdaa4e6c07e62 100644 --- a/server/startup/migrations/v220.js +++ b/server/startup/migrations/v220.ts @@ -1,10 +1,10 @@ +import { Settings } from '../../../app/models/server/raw'; import { addMigration } from '../../lib/migrations'; -import { Settings } from '../../../app/models/server'; addMigration({ version: 220, - up() { - Settings.update({ + async up() { + await Settings.update({ _id: 'Organization_Type', }, { $set: { @@ -29,7 +29,7 @@ addMigration({ }, }); - Settings.update({ + await Settings.update({ _id: 'Industry', }, { $set: { @@ -142,7 +142,7 @@ addMigration({ }, }); - Settings.update({ + await Settings.update({ _id: 'Country', }, { values: [ diff --git a/server/startup/migrations/v223.js b/server/startup/migrations/v223.js deleted file mode 100644 index 60c60180ea70a..0000000000000 --- a/server/startup/migrations/v223.js +++ /dev/null @@ -1,11 +0,0 @@ -import { addMigration } from '../../lib/migrations'; -import { Permissions } from '../../../app/models/server'; - -const roleName = 'user'; - -addMigration({ - version: 223, - up() { - Permissions.update({ _id: 'message-impersonate' }, { $addToSet: { roles: roleName } }); - }, -}); diff --git a/server/startup/migrations/v223.ts b/server/startup/migrations/v223.ts new file mode 100644 index 0000000000000..ef357c554277d --- /dev/null +++ b/server/startup/migrations/v223.ts @@ -0,0 +1,11 @@ +import { Permissions } from '../../../app/models/server/raw'; +import { addMigration } from '../../lib/migrations'; + +const roleName = 'user'; + +addMigration({ + version: 223, + up() { + return Permissions.update({ _id: 'message-impersonate' }, { $addToSet: { roles: roleName } }); + }, +}); diff --git a/server/startup/migrations/v224.js b/server/startup/migrations/v224.js deleted file mode 100644 index 5e69367f56093..0000000000000 --- a/server/startup/migrations/v224.js +++ /dev/null @@ -1,11 +0,0 @@ -import { addMigration } from '../../lib/migrations'; -import { Permissions } from '../../../app/models/server'; - -const roleName = 'app'; - -addMigration({ - version: 224, - up() { - Permissions.update({ _id: 'message-impersonate' }, { $addToSet: { roles: roleName } }); - }, -}); diff --git a/server/startup/migrations/v224.ts b/server/startup/migrations/v224.ts new file mode 100644 index 0000000000000..be266c225bd08 --- /dev/null +++ b/server/startup/migrations/v224.ts @@ -0,0 +1,9 @@ +import { Permissions } from '../../../app/models/server/raw'; +import { addMigration } from '../../lib/migrations'; + +addMigration({ + version: 224, + up() { + return Permissions.update({ _id: 'message-impersonate' }, { $addToSet: { roles: 'app' } }); + }, +}); diff --git a/server/startup/migrations/v225.js b/server/startup/migrations/v225.ts similarity index 70% rename from server/startup/migrations/v225.js rename to server/startup/migrations/v225.ts index bee7349b8b64f..62fabfcde6203 100644 --- a/server/startup/migrations/v225.js +++ b/server/startup/migrations/v225.ts @@ -1,30 +1,35 @@ import { addMigration } from '../../lib/migrations'; -import { Settings, Users } from '../../../app/models/server'; +import { Users } from '../../../app/models/server'; +import { Settings } from '../../../app/models/server/raw'; addMigration({ version: 225, - up() { - const hideAvatarsSetting = Settings.findOneById('Accounts_Default_User_Preferences_hideAvatars'); - const hideAvatarsSidebarSetting = Settings.findOneById('Accounts_Default_User_Preferences_sidebarHideAvatar'); + async up() { + const hideAvatarsSetting = await Settings.findOneById('Accounts_Default_User_Preferences_hideAvatars'); + const hideAvatarsSidebarSetting = await Settings.findOneById('Accounts_Default_User_Preferences_sidebarHideAvatar'); Settings.removeById('Accounts_Default_User_Preferences_sidebarShowDiscussion'); Settings.removeById('Accounts_Default_User_Preferences_sidebarHideAvatar'); - Settings.upsert({ + Settings.update({ _id: 'Accounts_Default_User_Preferences_sidebarDisplayAvatar', }, { $set: { - value: !hideAvatarsSidebarSetting.value, + value: !hideAvatarsSidebarSetting?.value, }, + }, { + upsert: true, }); - Settings.removeById('Accounts_Default_User_Preferences_hideAvatars'); - Settings.upsert({ + await Settings.removeById('Accounts_Default_User_Preferences_hideAvatars'); + Settings.update({ _id: 'Accounts_Default_User_Preferences_displayAvatars', }, { $set: { - value: !hideAvatarsSetting.value, + value: !hideAvatarsSetting?.value, }, + }, { + upsert: true, }); Users.update({ 'settings.preferences.hideAvatars': true }, { diff --git a/server/startup/migrations/v226.js b/server/startup/migrations/v226.js deleted file mode 100644 index da53274e5ec44..0000000000000 --- a/server/startup/migrations/v226.js +++ /dev/null @@ -1,9 +0,0 @@ -import { addMigration } from '../../lib/migrations'; -import { Settings } from '../../../app/models/server'; - -addMigration({ - version: 226, - up() { - Settings.removeById('Apps_Game_Center_enabled'); - }, -}); diff --git a/server/startup/migrations/v226.ts b/server/startup/migrations/v226.ts new file mode 100644 index 0000000000000..c94cc0b0d1e8f --- /dev/null +++ b/server/startup/migrations/v226.ts @@ -0,0 +1,9 @@ +import { Settings } from '../../../app/models/server/raw'; +import { addMigration } from '../../lib/migrations'; + +addMigration({ + version: 226, + up() { + return Settings.removeById('Apps_Game_Center_enabled'); + }, +}); diff --git a/server/startup/migrations/v228.js b/server/startup/migrations/v228.js deleted file mode 100644 index 9ac69ef146cc4..0000000000000 --- a/server/startup/migrations/v228.js +++ /dev/null @@ -1,11 +0,0 @@ -import { addMigration } from '../../lib/migrations'; -import { Permissions } from '../../../app/models'; - -addMigration({ - version: 228, - up() { - if (Permissions) { - Permissions.update({ _id: 'manage-livechat-canned-responses' }, { $addToSet: { roles: 'livechat-monitor' } }); - } - }, -}); diff --git a/server/startup/migrations/v228.ts b/server/startup/migrations/v228.ts new file mode 100644 index 0000000000000..4d49253980f39 --- /dev/null +++ b/server/startup/migrations/v228.ts @@ -0,0 +1,9 @@ +import { Permissions } from '../../../app/models/server/raw'; +import { addMigration } from '../../lib/migrations'; + +addMigration({ + version: 228, + up() { + return Permissions.update({ _id: 'manage-livechat-canned-responses' }, { $addToSet: { roles: 'livechat-monitor' } }); + }, +}); diff --git a/server/startup/migrations/v229.js b/server/startup/migrations/v229.ts similarity index 62% rename from server/startup/migrations/v229.js rename to server/startup/migrations/v229.ts index 87c38d2a68f6e..71c00c907909b 100644 --- a/server/startup/migrations/v229.js +++ b/server/startup/migrations/v229.ts @@ -1,15 +1,15 @@ -import { Settings } from '../../../app/models/server'; +import { Settings } from '../../../app/models/server/raw'; import { addMigration } from '../../lib/migrations'; addMigration({ version: 229, - up() { - const oldNamesValidationSetting = Settings.findOneById( + async up() { + const oldNamesValidationSetting = await Settings.findOneById( 'UTF8_Names_Validation', ); const oldNamesValidationSettingValue = oldNamesValidationSetting?.value || '[0-9a-zA-Z-_.]+'; - Settings.upsert( + Settings.update( { _id: 'UTF8_User_Names_Validation', }, @@ -18,9 +18,12 @@ addMigration({ value: oldNamesValidationSettingValue, }, }, + { + upsert: true, + }, ); - Settings.upsert( + Settings.update( { _id: 'UTF8_Channel_Names_Validation', }, @@ -28,9 +31,11 @@ addMigration({ $set: { value: oldNamesValidationSettingValue, }, + }, { + upsert: true, }, ); - Settings.removeById('UTF8_Names_Validation'); + return Settings.removeById('UTF8_Names_Validation'); }, }); diff --git a/server/startup/migrations/v230.ts b/server/startup/migrations/v230.ts index 22c48c7762ffb..c3103291bd002 100644 --- a/server/startup/migrations/v230.ts +++ b/server/startup/migrations/v230.ts @@ -1,12 +1,12 @@ +import { Permissions } from '../../../app/models/server/raw'; import { addMigration } from '../../lib/migrations'; -import { Permissions } from '../../../app/models/server'; - -const roleName = 'app'; addMigration({ version: 230, up() { - Permissions.update({ _id: 'start-discussion' }, { $addToSet: { roles: roleName } }); - Permissions.update({ _id: 'start-discussion-other-user' }, { $addToSet: { roles: roleName } }); + return Promise.all([ + Permissions.addRole('start-discussion', 'app'), + Permissions.addRole('start-discussion-other-user', 'app'), + ]); }, }); diff --git a/server/startup/migrations/v231.ts b/server/startup/migrations/v231.ts index 67a897b6e3f40..70dbd39bd854f 100644 --- a/server/startup/migrations/v231.ts +++ b/server/startup/migrations/v231.ts @@ -5,12 +5,12 @@ import { addMigration } from '../../lib/migrations'; import { BannerPlatform } from '../../../definition/IBanner'; import { Banner } from '../../sdk'; import { settings } from '../../../app/settings/server'; -import { Settings } from '../../../app/models/server'; import { isEnterprise } from '../../../ee/app/license/server'; +import { Settings } from '../../../app/models/server/raw'; addMigration({ version: 231, - up() { + async up() { const LDAPEnabled = settings.get('LDAP_Enable'); const SAMLEnabled = settings.get('SAML_Custom_Default'); @@ -18,7 +18,7 @@ addMigration({ _id: { $in: [/^Accounts_OAuth_(Custom-)?([^-_]+)$/, 'Accounts_OAuth_GitHub_Enterprise'] }, value: true, }; - const CustomOauthEnabled = !!Settings.findOne(query); + const CustomOauthEnabled = !! await Settings.findOne(query); const isAuthServiceEnabled = LDAPEnabled || SAMLEnabled || CustomOauthEnabled; diff --git a/server/startup/migrations/v233.ts b/server/startup/migrations/v233.ts index 9d9068a588d8e..bf51873d9e19d 100644 --- a/server/startup/migrations/v233.ts +++ b/server/startup/migrations/v233.ts @@ -1,10 +1,10 @@ +import { Settings } from '../../../app/models/server/raw'; import { addMigration } from '../../lib/migrations'; -import { Settings } from '../../../app/models/server'; addMigration({ version: 233, up() { - Settings.remove({ _id: { $in: [ + return Settings.deleteMany({ _id: { $in: [ 'Log_Package', 'Log_File', ] } }); diff --git a/server/startup/migrations/v234.ts b/server/startup/migrations/v234.ts index 4d8dbe2db4120..8909a16e21c1e 100644 --- a/server/startup/migrations/v234.ts +++ b/server/startup/migrations/v234.ts @@ -1,10 +1,10 @@ +import { Settings } from '../../../app/models/server/raw'; import { addMigration } from '../../lib/migrations'; -import { Settings } from '../../../app/models/server'; addMigration({ version: 234, up() { - Settings.remove({ + return Settings.deleteMany({ _id: { $in: [ 'GoogleVision_Enable', diff --git a/server/startup/migrations/v235.ts b/server/startup/migrations/v235.ts index fe2308356e508..1b6fb0af8e1a4 100644 --- a/server/startup/migrations/v235.ts +++ b/server/startup/migrations/v235.ts @@ -1,10 +1,11 @@ import { addMigration } from '../../lib/migrations'; -import { Settings, Subscriptions, Users } from '../../../app/models/server'; +import { Subscriptions, Users } from '../../../app/models/server'; +import { Settings } from '../../../app/models/server/raw'; addMigration({ version: 235, - up() { - Settings.removeById('Accounts_Default_User_Preferences_audioNotifications'); + async up() { + await Settings.removeById('Accounts_Default_User_Preferences_audioNotifications'); // delete field from subscriptions Subscriptions.update({ diff --git a/server/startup/migrations/v236.ts b/server/startup/migrations/v236.ts index c1f8398964000..2f0ea88c17f26 100644 --- a/server/startup/migrations/v236.ts +++ b/server/startup/migrations/v236.ts @@ -1,13 +1,13 @@ +import { Settings } from '../../../app/models/server/raw'; import { addMigration } from '../../lib/migrations'; -import { Settings } from '../../../app/models/server'; addMigration({ version: 236, - up() { - Settings.removeById('Canned Responses'); - Settings.removeById('Canned_Responses'); + async up() { + await Settings.removeById('Canned Responses'); + await Settings.removeById('Canned_Responses'); - Settings.upsert( + await Settings.update( { _id: 'Canned_Responses_Enable', }, @@ -16,6 +16,9 @@ addMigration({ group: 'Omnichannel', }, }, + { + upsert: true, + }, ); }, }); diff --git a/server/startup/migrations/v237.ts b/server/startup/migrations/v237.ts index f53d8d0eb195a..155ae0b29a3e1 100644 --- a/server/startup/migrations/v237.ts +++ b/server/startup/migrations/v237.ts @@ -1,7 +1,7 @@ import { addMigration } from '../../lib/migrations'; import { settings } from '../../../app/settings/server'; -import { Settings } from '../../../app/models/server'; import { isEnterprise } from '../../../ee/app/license/server'; +import { Settings } from '../../../app/models/server/raw'; function copySettingValue(newName: string, oldName: string): void { const value = settings.get(oldName); @@ -9,12 +9,12 @@ function copySettingValue(newName: string, oldName: string): void { return; } - Settings.upsert({ _id: newName }, { $set: { value } }); + Settings.update({ _id: newName }, { $set: { value } }, { upsert: true }); } addMigration({ version: 237, - up() { + async up() { const isEE = isEnterprise(); // Override AD defaults with the previously configured values @@ -23,7 +23,9 @@ addMigration({ // If we're sure the server is AD, then select it - otherwise keep it as generic ldap const useAdDefaults = settings.get('LDAP_User_Search_Field') === 'sAMAccountName'; - Settings.upsert({ _id: 'LDAP_Server_Type' }, { $set: { value: useAdDefaults ? 'ad' : '' } }); + Settings.update({ _id: 'LDAP_Server_Type' }, { $set: { value: useAdDefaults ? 'ad' : '' } }, { + upsert: true, + }); // The setting to use the field map also determined if the user data was updated on login or not copySettingValue('LDAP_Update_Data_On_Login', 'LDAP_Sync_User_Data'); @@ -44,14 +46,14 @@ addMigration({ } if (fieldMap[key] === 'name') { - Settings.upsert({ _id: 'LDAP_Name_Field' }, { $set: { value: key } }); - Settings.upsert({ _id: 'LDAP_AD_Name_Field' }, { $set: { value: key } }); + Settings.update({ _id: 'LDAP_Name_Field' }, { $set: { value: key } }, { upsert: true }); + Settings.update({ _id: 'LDAP_AD_Name_Field' }, { $set: { value: key } }, { upsert: true }); continue; } if (fieldMap[key] === 'email') { - Settings.upsert({ _id: 'LDAP_Email_Field' }, { $set: { value: key } }); - Settings.upsert({ _id: 'LDAP_AD_Email_Field' }, { $set: { value: key } }); + Settings.update({ _id: 'LDAP_Email_Field' }, { $set: { value: key } }, { upsert: true }); + Settings.update({ _id: 'LDAP_AD_Email_Field' }, { $set: { value: key } }, { upsert: true }); continue; } @@ -60,10 +62,10 @@ addMigration({ if (isEE) { const newJson = JSON.stringify(newObject); - Settings.upsert({ _id: 'LDAP_CustomFieldMap' }, { $set: { value: newJson } }); + Settings.update({ _id: 'LDAP_CustomFieldMap' }, { $set: { value: newJson } }, { upsert: true }); const syncCustomFields = Object.keys(newObject).length > 0 && settings.get('LDAP_Sync_User_Data'); - Settings.upsert({ _id: 'LDAP_Sync_Custom_Fields' }, { $set: { value: syncCustomFields } }); + Settings.update({ _id: 'LDAP_Sync_Custom_Fields' }, { $set: { value: syncCustomFields } }, { upsert: true }); } } @@ -80,7 +82,7 @@ addMigration({ copySettingValue('LDAP_Sync_User_Data_Channels_Filter', 'LDAP_Sync_User_Data_Groups_Filter'); copySettingValue('LDAP_Sync_User_Data_Channels_BaseDN', 'LDAP_Sync_User_Data_Groups_BaseDN'); - Settings.remove({ + await Settings.deleteMany({ _id: { $in: [ 'LDAP_Sync_Now', diff --git a/server/startup/migrations/v240.ts b/server/startup/migrations/v240.ts index 4ce8f2c7d0acc..296040d649d7b 100644 --- a/server/startup/migrations/v240.ts +++ b/server/startup/migrations/v240.ts @@ -1,9 +1,9 @@ +import { Settings } from '../../../app/models/server/raw'; import { addMigration } from '../../lib/migrations'; -import { Settings } from '../../../app/models/server'; addMigration({ version: 240, up() { - Settings.removeById('Support_Cordova_App'); + return Settings.removeById('Support_Cordova_App'); }, }); diff --git a/server/startup/migrations/v242.ts b/server/startup/migrations/v242.ts index 86419e2763284..b94f3f6439d52 100644 --- a/server/startup/migrations/v242.ts +++ b/server/startup/migrations/v242.ts @@ -1,5 +1,6 @@ import { addMigration } from '../../lib/migrations'; -import { LivechatInquiry, Settings } from '../../../app/models/server'; +import { LivechatInquiry } from '../../../app/models/server'; +import { Settings } from '../../../app/models/server/raw'; function removeQueueTimeoutFromInquiries(): void { LivechatInquiry.update({ @@ -8,23 +9,23 @@ function removeQueueTimeoutFromInquiries(): void { }, { $unset: { estimatedInactivityCloseTimeAt: 1 } }, { multi: true }); } -function removeSetting(): void { - const oldSetting = Settings.findOneById('Livechat_max_queue_wait_time_action'); +async function removeSetting(): Promise { + const oldSetting = await Settings.findOneById('Livechat_max_queue_wait_time_action'); if (!oldSetting) { return; } const currentAction = oldSetting.value; if (currentAction === 'Nothing') { - Settings.upsert({ _id: 'Livechat_max_queue_wait_time' }, { $set: { value: -1 } }); + await Settings.update({ _id: 'Livechat_max_queue_wait_time' }, { $set: { value: -1 } }, { upsert: true }); } - Settings.removeById('Livechat_max_queue_wait_time_action'); + await Settings.removeById('Livechat_max_queue_wait_time_action'); } addMigration({ version: 242, up() { removeQueueTimeoutFromInquiries(); - removeSetting(); + return removeSetting(); }, }); diff --git a/server/startup/migrations/v243.ts b/server/startup/migrations/v243.ts index 89d3f9f03e707..71a2e7d97c75c 100644 --- a/server/startup/migrations/v243.ts +++ b/server/startup/migrations/v243.ts @@ -1,19 +1,22 @@ import { addMigration } from '../../lib/migrations'; -import { Settings, Users } from '../../../app/models/server'; +import { Users } from '../../../app/models/server'; +import { Settings } from '../../../app/models/server/raw'; addMigration({ version: 243, - up() { - const mobileNotificationsSetting = Settings.findOneById('Accounts_Default_User_Preferences_mobileNotifications'); + async up() { + const mobileNotificationsSetting = await Settings.findOneById('Accounts_Default_User_Preferences_mobileNotifications'); - Settings.removeById('Accounts_Default_User_Preferences_mobileNotifications'); + await Settings.removeById('Accounts_Default_User_Preferences_mobileNotifications'); if (mobileNotificationsSetting && mobileNotificationsSetting.value) { - Settings.upsert({ + Settings.update({ _id: 'Accounts_Default_User_Preferences_pushNotifications', }, { $set: { value: mobileNotificationsSetting.value, }, + }, { + upsert: true, }); } diff --git a/server/startup/migrations/v244.ts b/server/startup/migrations/v244.ts index 8ec31f2485e19..d5c39dffc20c2 100644 --- a/server/startup/migrations/v244.ts +++ b/server/startup/migrations/v244.ts @@ -1,9 +1,9 @@ +import { upsertPermissions } from '../../../app/authorization/server/functions/upsertPermissions'; import { addMigration } from '../../lib/migrations'; -import { Permissions } from '../../../app/models/server'; addMigration({ version: 244, up() { - Permissions.create('view-omnichannel-contact-center', ['livechat-manager', 'livechat-agent', 'livechat-monitor', 'admin']); + return upsertPermissions(); }, }); diff --git a/server/startup/presence.js b/server/startup/presence.js index 73fc47b2725c0..b476d431ed6b0 100644 --- a/server/startup/presence.js +++ b/server/startup/presence.js @@ -1,8 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { UserPresence } from 'meteor/konecty:user-presence'; -import InstanceStatusModel from '../../app/models/server/models/InstanceStatus'; -import UsersSessionsModel from '../../app/models/server/models/UsersSessions'; +import { InstanceStatus, UsersSessions } from '../../app/models/server/raw'; import { isPresenceMonitorEnabled } from '../lib/isPresenceMonitorEnabled'; Meteor.startup(function() { @@ -14,16 +13,8 @@ Meteor.startup(function() { // UserPresenceMonitor.start(); // Remove lost connections - const ids = InstanceStatusModel.find({}, { fields: { _id: 1 } }).fetch().map((id) => id._id); + const ids = Promise.await(InstanceStatus.find({}, { projection: { _id: 1 } }).toArray()) + .map((id) => id._id); - const update = { - $pull: { - connections: { - instanceId: { - $nin: ids, - }, - }, - }, - }; - UsersSessionsModel.update({}, update, { multi: true }); + Promise.await(UsersSessions.clearConnectionsFromInstanceId(ids)); }); diff --git a/server/stream/streamBroadcast.js b/server/stream/streamBroadcast.js index 4c94ccc59ab45..c866885b101be 100644 --- a/server/stream/streamBroadcast.js +++ b/server/stream/streamBroadcast.js @@ -5,11 +5,11 @@ import { check } from 'meteor/check'; import { DDP } from 'meteor/ddp'; import { Logger } from '../lib/logger/Logger'; -import { hasPermission } from '../../app/authorization'; +import { hasPermission } from '../../app/authorization/server'; import { settings } from '../../app/settings/server'; -import { isDocker, getURL } from '../../app/utils'; +import { isDocker, getURL } from '../../app/utils/server'; import { Users } from '../../app/models/server'; -import InstanceStatusModel from '../../app/models/server/models/InstanceStatus'; +import { InstanceStatus as InstanceStatusRaw } from '../../app/models/server/raw'; import { StreamerCentral } from '../modules/streamer/streamer.module'; import { isPresenceMonitorEnabled } from '../lib/isPresenceMonitorEnabled'; @@ -61,7 +61,7 @@ function startMatrixBroadcast() { } matrixBroadCastActions = { - added(record) { + added: Meteor.bindEnvironment((record) => { cache.set(record._id, record); const subPath = getURL('', { cdn: false, full: false }); @@ -100,7 +100,7 @@ function startMatrixBroadcast() { connections[instance].onReconnect = function() { return authorizeConnection(instance); }; - }, + }), removed(id) { const record = cache.get(id); @@ -129,19 +129,15 @@ function startMatrixBroadcast() { }, }; - const query = { + InstanceStatusRaw.find({ 'extraInformation.port': { $exists: true, }, - }; - - const options = { + }, { sort: { _createdAt: -1, }, - }; - - InstanceStatusModel.find(query, options).fetch().forEach(matrixBroadCastActions.added); + }).forEach(matrixBroadCastActions.added); } From 8650aa72e83af51d4aaf49ad07a9f5b10c891ac2 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Wed, 10 Nov 2021 18:32:48 -0300 Subject: [PATCH 062/137] [IMPROVE] Reduce complexity in some functions (#23387) --- app/utils/lib/getURL.tests.js | 113 ++++++------------ .../omnichannel/departments/EditDepartment.js | 77 ++++++------ .../EditDepartmentWithAllowedForwardData.js | 1 - .../departments/EditDepartmentWithData.js | 1 - 4 files changed, 72 insertions(+), 120 deletions(-) diff --git a/app/utils/lib/getURL.tests.js b/app/utils/lib/getURL.tests.js index 73d6d01865501..5ef7af6a4888f 100644 --- a/app/utils/lib/getURL.tests.js +++ b/app/utils/lib/getURL.tests.js @@ -1,4 +1,3 @@ -/* eslint-disable complexity */ /* eslint-env mocha */ import 'babel-polyfill'; import assert from 'assert'; @@ -89,12 +88,6 @@ const testCases = (options) => { } } } else if (options._cdn_prefix === '') { - if (options.full && !options.cdn && !options.cloud) { - it('should return with host if full: true', () => { - testPaths(options, (path) => _site_url + path); - }); - } - if (!options.full && options.cdn) { it('should return with cloud host if cdn: true', () => { testPaths(options, (path) => getCloudUrl(_site_url, path)); @@ -106,88 +99,54 @@ const testCases = (options) => { testPaths(options, (path) => getCloudUrl(_site_url, path)); }); } - - if (options.full && options.cdn && !options.cloud) { - it('should return with host if full: true and cdn: true', () => { - testPaths(options, (path) => _site_url + path); - }); - } - } else { - if (options.full && !options.cdn && !options.cloud) { - it('should return with host if full: true', () => { - testPaths(options, (path) => _site_url + path); - }); - } - - if (!options.full && options.cdn && !options.cloud) { - it('should return with cdn prefix if cdn: true', () => { - testPaths(options, (path) => options._cdn_prefix + path); - }); - } - - if (!options.full && !options.cdn) { - it('should return with cloud host if full: fase and cdn: false', () => { - testPaths(options, (path) => getCloudUrl(_site_url, path)); - }); - } - - if (options.full && options.cdn && !options.cloud) { - it('should return with host if full: true and cdn: true', () => { - testPaths(options, (path) => options._cdn_prefix + path); - }); - } + } else if (!options.full && !options.cdn) { + it('should return with cloud host if full: fase and cdn: false', () => { + testPaths(options, (path) => getCloudUrl(_site_url, path)); + }); } }; -const testOptions = (options) => { - testCases({ ...options, cdn: false, full: false, cloud: false }); - testCases({ ...options, cdn: true, full: false, cloud: false }); - testCases({ ...options, cdn: false, full: true, cloud: false }); - testCases({ ...options, cdn: false, full: false, cloud: true }); - testCases({ ...options, cdn: true, full: true, cloud: false }); - testCases({ ...options, cdn: false, full: true, cloud: true }); - testCases({ ...options, cdn: true, full: false, cloud: true }); - testCases({ ...options, cdn: true, full: true, cloud: true }); +const testCasesForOptions = (description, options) => { + describe(description, () => { + testCases({ ...options, cdn: false, full: false, cloud: false }); + testCases({ ...options, cdn: true, full: false, cloud: false }); + testCases({ ...options, cdn: false, full: true, cloud: false }); + testCases({ ...options, cdn: false, full: false, cloud: true }); + testCases({ ...options, cdn: true, full: true, cloud: false }); + testCases({ ...options, cdn: false, full: true, cloud: true }); + testCases({ ...options, cdn: true, full: false, cloud: true }); + testCases({ ...options, cdn: true, full: true, cloud: true }); + }); }; -describe('getURL', () => { - describe('getURL with no CDN, no PREFIX for http://localhost:3000/', () => { - testOptions({ - _cdn_prefix: '', - _root_url_path_prefix: '', - _site_url: 'http://localhost:3000/', - }); +describe.only('getURL', () => { + testCasesForOptions('getURL with no CDN, no PREFIX for http://localhost:3000/', { + _cdn_prefix: '', + _root_url_path_prefix: '', + _site_url: 'http://localhost:3000/', }); - describe('getURL with no CDN, no PREFIX for http://localhost:3000', () => { - testOptions({ - _cdn_prefix: '', - _root_url_path_prefix: '', - _site_url: 'http://localhost:3000', - }); + testCasesForOptions('getURL with no CDN, no PREFIX for http://localhost:3000', { + _cdn_prefix: '', + _root_url_path_prefix: '', + _site_url: 'http://localhost:3000', }); - describe('getURL with CDN, no PREFIX for http://localhost:3000/', () => { - testOptions({ - _cdn_prefix: 'https://cdn.com', - _root_url_path_prefix: '', - _site_url: 'http://localhost:3000/', - }); + testCasesForOptions('getURL with CDN, no PREFIX for http://localhost:3000/', { + _cdn_prefix: 'https://cdn.com', + _root_url_path_prefix: '', + _site_url: 'http://localhost:3000/', }); - describe('getURL with CDN, PREFIX for http://localhost:3000/', () => { - testOptions({ - _cdn_prefix: 'https://cdn.com', - _root_url_path_prefix: 'sub', - _site_url: 'http://localhost:3000/', - }); + testCasesForOptions('getURL with CDN, PREFIX for http://localhost:3000/', { + _cdn_prefix: 'https://cdn.com', + _root_url_path_prefix: 'sub', + _site_url: 'http://localhost:3000/', }); - describe('getURL with CDN, PREFIX for https://localhost:3000/', () => { - testOptions({ - _cdn_prefix: 'https://cdn.com', - _root_url_path_prefix: 'sub', - _site_url: 'https://localhost:3000/', - }); + testCasesForOptions('getURL with CDN, PREFIX for https://localhost:3000/', { + _cdn_prefix: 'https://cdn.com', + _root_url_path_prefix: 'sub', + _site_url: 'https://localhost:3000/', }); }); diff --git a/client/views/omnichannel/departments/EditDepartment.js b/client/views/omnichannel/departments/EditDepartment.js index 24885d0f53a62..3fbb653e25695 100644 --- a/client/views/omnichannel/departments/EditDepartment.js +++ b/client/views/omnichannel/departments/EditDepartment.js @@ -1,4 +1,3 @@ -/* eslint-disable complexity */ import { FieldGroup, Field, @@ -33,11 +32,7 @@ import DepartmentsAgentsTable from './DepartmentsAgentsTable'; function EditDepartment({ data, id, title, reload, allowedToForwardData }) { const t = useTranslation(); - const agentsRoute = useRoute('omnichannel-departments'); - const eeForms = useSubscription(formsSubscription); - const initialAgents = useRef((data && data.agents) || []); - - const router = useRoute('omnichannel-departments'); + const departmentsRoute = useRoute('omnichannel-departments'); const { useEeNumberInput = () => {}, @@ -45,7 +40,9 @@ function EditDepartment({ data, id, title, reload, allowedToForwardData }) { useEeTextAreaInput = () => {}, useDepartmentForwarding = () => {}, useDepartmentBusinessHours = () => {}, - } = eeForms; + } = useSubscription(formsSubscription); + + const initialAgents = useRef((data && data.agents) || []); const MaxChats = useEeNumberInput(); const VisitorInactivity = useEeNumberInput(); @@ -57,29 +54,23 @@ function EditDepartment({ data, id, title, reload, allowedToForwardData }) { const { department } = data || { department: {} }; - const [tags, setTags] = useState((department && department.chatClosingTags) || []); - const [tagsText, setTagsText] = useState(); + const [[tags, tagsText], setTagsState] = useState(() => [department?.chatClosingTags ?? [], '']); const { values, handlers, hasUnsavedChanges } = useForm({ - name: (department && department.name) || '', - email: (department && department.email) || '', - description: (department && department.description) || '', - enabled: !!(department && department.enabled), - maxNumberSimultaneousChat: (department && department.maxNumberSimultaneousChat) || undefined, - showOnRegistration: !!(department && department.showOnRegistration), - showOnOfflineForm: !!(department && department.showOnOfflineForm), - abandonedRoomsCloseCustomMessage: - (department && department.abandonedRoomsCloseCustomMessage) || '', - requestTagBeforeClosingChat: (department && department.requestTagBeforeClosingChat) || false, - offlineMessageChannelName: (department && department.offlineMessageChannelName) || '', - visitorInactivityTimeoutInSeconds: - (department && department.visitorInactivityTimeoutInSeconds) || undefined, - waitingQueueMessage: (department && department.waitingQueueMessage) || '', + name: department?.name || '', + email: department?.email || '', + description: department?.description || '', + enabled: !!department?.enabled, + maxNumberSimultaneousChat: department?.maxNumberSimultaneousChat || undefined, + showOnRegistration: !!department?.showOnRegistration, + showOnOfflineForm: !!department?.showOnOfflineForm, + abandonedRoomsCloseCustomMessage: department?.abandonedRoomsCloseCustomMessage || '', + requestTagBeforeClosingChat: department?.requestTagBeforeClosingChat || false, + offlineMessageChannelName: department?.offlineMessageChannelName || '', + visitorInactivityTimeoutInSeconds: department?.visitorInactivityTimeoutInSeconds || undefined, + waitingQueueMessage: department?.waitingQueueMessage || '', departmentsAllowedToForward: - (allowedToForwardData && - allowedToForwardData.departments && - allowedToForwardData.departments.map((dep) => ({ label: dep.name, value: dep._id }))) || - [], + allowedToForwardData?.departments?.map((dep) => ({ label: dep.name, value: dep._id })) || [], }); const { handleName, @@ -119,20 +110,25 @@ function EditDepartment({ data, id, title, reload, allowedToForwardData }) { const { phase: roomsPhase, items: roomsItems, itemCount: roomsTotal } = useRecordList(RoomsList); const handleTagChipClick = (tag) => () => { - setTags((tags) => tags.filter((_tag) => _tag !== tag)); + setTagsState(([tags, tagsText]) => [tags.filter((_tag) => _tag !== tag), tagsText]); }; const handleTagTextSubmit = useMutableCallback(() => { - if (!tags.includes(tagsText)) { - setTags([...tags, tagsText]); - setTagsText(''); - } - }); + setTagsState((state) => { + const [tags, tagsText] = state; + + if (tags.includes(tagsText)) { + return state; + } - const handleTagTextChange = useMutableCallback((e) => { - setTagsText(e.target.value); + return [[...tags, tagsText], '']; + }); }); + const handleTagTextChange = (e) => { + setTagsState(([tags]) => [tags, e.target.value]); + }; + const saveDepartmentInfo = useMethod('livechat:saveDepartment'); const saveDepartmentAgentsInfoOnEdit = useEndpoint('POST', `livechat/department/${id}/agents`); @@ -197,8 +193,7 @@ function EditDepartment({ data, id, title, reload, allowedToForwardData }) { visitorInactivityTimeoutInSeconds, abandonedRoomsCloseCustomMessage, waitingQueueMessage, - departmentsAllowedToForward: - departmentsAllowedToForward && departmentsAllowedToForward.map((dep) => dep.value).join(), + departmentsAllowedToForward: departmentsAllowedToForward?.map((dep) => dep.value).join(), }; const agentListPayload = { @@ -227,14 +222,14 @@ function EditDepartment({ data, id, title, reload, allowedToForwardData }) { } dispatchToastMessage({ type: 'success', message: t('Saved') }); reload(); - agentsRoute.push({}); + departmentsRoute.push({}); } catch (error) { dispatchToastMessage({ type: 'error', message: error }); } }); const handleReturn = useMutableCallback(() => { - router.push({}); + departmentsRoute.push({}); }); const invalidForm = @@ -438,7 +433,7 @@ function EditDepartment({ data, id, title, reload, allowedToForwardData }) { {t('Conversation_closing_tags_description')} - {tags && tags.length > 0 && ( + {tags?.length > 0 && ( {tags.map((tag, i) => ( @@ -451,7 +446,7 @@ function EditDepartment({ data, id, title, reload, allowedToForwardData }) { )} {DepartmentBusinessHours && ( - + )} diff --git a/client/views/omnichannel/departments/EditDepartmentWithAllowedForwardData.js b/client/views/omnichannel/departments/EditDepartmentWithAllowedForwardData.js index ff9c26f4ef570..a217c4ec8aaad 100644 --- a/client/views/omnichannel/departments/EditDepartmentWithAllowedForwardData.js +++ b/client/views/omnichannel/departments/EditDepartmentWithAllowedForwardData.js @@ -1,4 +1,3 @@ -/* eslint-disable complexity */ import { Box } from '@rocket.chat/fuselage'; import React, { useMemo } from 'react'; diff --git a/client/views/omnichannel/departments/EditDepartmentWithData.js b/client/views/omnichannel/departments/EditDepartmentWithData.js index ccdc6ab6e589f..d4bbc31c35ca2 100644 --- a/client/views/omnichannel/departments/EditDepartmentWithData.js +++ b/client/views/omnichannel/departments/EditDepartmentWithData.js @@ -1,4 +1,3 @@ -/* eslint-disable complexity */ import { Box } from '@rocket.chat/fuselage'; import React from 'react'; From f83a42288a604aee1e2c741e397e4d8c8d23630d Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Fri, 12 Nov 2021 09:57:57 -0300 Subject: [PATCH 063/137] Chore: Remove useCallbacks (#23696) --- .storybook/mocks/meteor.js | 4 ++++ .storybook/preview.ts | 2 +- client/hooks/useCallbacks.js | 3 --- client/methods/updateMessage.js | 2 +- client/providers/UserProvider.tsx | 2 +- client/sidebar/header/UserDropdown.js | 2 +- client/startup/renderMessage/autolinker.ts | 2 +- client/startup/renderMessage/autotranslate.ts | 2 +- client/startup/renderMessage/emoji.ts | 2 +- client/startup/renderMessage/hexcolor.ts | 2 +- client/startup/renderMessage/highlightWords.ts | 2 +- client/startup/renderMessage/issuelink.ts | 2 +- client/startup/renderMessage/katex.ts | 2 +- client/startup/renderMessage/markdown.ts | 2 +- client/startup/renderMessage/mentionsMessage.ts | 2 +- client/startup/renderNotification/markdown.ts | 2 +- client/startup/streamMessage/autotranslate.ts | 2 +- client/startup/userStatusManuallySet.ts | 2 +- client/views/setupWizard/steps/AdminUserInformationStep.js | 3 +-- 19 files changed, 21 insertions(+), 21 deletions(-) delete mode 100644 client/hooks/useCallbacks.js diff --git a/.storybook/mocks/meteor.js b/.storybook/mocks/meteor.js index e4746cb59f0e0..1b9d0e4f968cc 100644 --- a/.storybook/mocks/meteor.js +++ b/.storybook/mocks/meteor.js @@ -13,6 +13,10 @@ export const Meteor = { on: () => {}, removeListener: () => {}, }), + StreamerCentral: { + on: () => {}, + removeListener: () => {}, + }, startup: () => {}, methods: () => {}, call: () => {}, diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 2839f538c25ef..39ebd205bdb11 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,4 +1,4 @@ -import { DocsPage, DocsContainer } from '@storybook/addon-docs/blocks'; +import { DocsPage, DocsContainer } from '@storybook/addon-docs'; import { addDecorator, addParameters } from '@storybook/react'; import { rocketChatDecorator } from './decorators'; diff --git a/client/hooks/useCallbacks.js b/client/hooks/useCallbacks.js deleted file mode 100644 index b727689643bac..0000000000000 --- a/client/hooks/useCallbacks.js +++ /dev/null @@ -1,3 +0,0 @@ -import { callbacks } from '../../app/callbacks/lib/callbacks'; - -export const useCallbacks = () => callbacks; diff --git a/client/methods/updateMessage.js b/client/methods/updateMessage.js index 0aa84faefabc3..b94958452c333 100644 --- a/client/methods/updateMessage.js +++ b/client/methods/updateMessage.js @@ -5,7 +5,7 @@ import moment from 'moment'; import _ from 'underscore'; import { hasAtLeastOnePermission } from '../../app/authorization/client'; -import { callbacks } from '../../app/callbacks/client'; +import { callbacks } from '../../app/callbacks/lib/callbacks'; import { ChatMessage } from '../../app/models/client'; import { settings } from '../../app/settings/client'; import { t } from '../../app/utils/client'; diff --git a/client/providers/UserProvider.tsx b/client/providers/UserProvider.tsx index 45a91b6adf97a..ebf37f035fb7e 100644 --- a/client/providers/UserProvider.tsx +++ b/client/providers/UserProvider.tsx @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import React, { useMemo, FC } from 'react'; -import { callbacks } from '../../app/callbacks/client'; +import { callbacks } from '../../app/callbacks/lib/callbacks'; import { Subscriptions, Rooms } from '../../app/models/client'; import { getUserPreference } from '../../app/utils/client'; import { IRoom } from '../../definition/IRoom'; diff --git a/client/sidebar/header/UserDropdown.js b/client/sidebar/header/UserDropdown.js index c9a058bb50ebc..246ebe59ca511 100644 --- a/client/sidebar/header/UserDropdown.js +++ b/client/sidebar/header/UserDropdown.js @@ -3,7 +3,7 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { FlowRouter } from 'meteor/kadira:flow-router'; import React from 'react'; -import { callbacks } from '../../../app/callbacks/client'; +import { callbacks } from '../../../app/callbacks/lib/callbacks'; import { popover, AccountBox, SideNav } from '../../../app/ui-utils/client'; import { userStatus } from '../../../app/user-status/client'; import MarkdownText from '../../components/MarkdownText'; diff --git a/client/startup/renderMessage/autolinker.ts b/client/startup/renderMessage/autolinker.ts index bf240aab3ea76..174da6e915a0c 100644 --- a/client/startup/renderMessage/autolinker.ts +++ b/client/startup/renderMessage/autolinker.ts @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; -import { callbacks } from '../../../app/callbacks/client'; +import { callbacks } from '../../../app/callbacks/lib/callbacks'; import { settings } from '../../../app/settings/client'; Meteor.startup(() => { diff --git a/client/startup/renderMessage/autotranslate.ts b/client/startup/renderMessage/autotranslate.ts index 9142d4670a364..be727cd743db5 100644 --- a/client/startup/renderMessage/autotranslate.ts +++ b/client/startup/renderMessage/autotranslate.ts @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; import { hasPermission } from '../../../app/authorization/client'; -import { callbacks } from '../../../app/callbacks/client'; +import { callbacks } from '../../../app/callbacks/lib/callbacks'; import { settings } from '../../../app/settings/client'; Meteor.startup(() => { diff --git a/client/startup/renderMessage/emoji.ts b/client/startup/renderMessage/emoji.ts index a5abc9d827739..ddb3bf318b45f 100644 --- a/client/startup/renderMessage/emoji.ts +++ b/client/startup/renderMessage/emoji.ts @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; -import { callbacks } from '../../../app/callbacks/client'; +import { callbacks } from '../../../app/callbacks/lib/callbacks'; import { getUserPreference } from '../../../app/utils/client'; Meteor.startup(() => { diff --git a/client/startup/renderMessage/hexcolor.ts b/client/startup/renderMessage/hexcolor.ts index c24b9dc559e96..aba80d6f0a0ac 100644 --- a/client/startup/renderMessage/hexcolor.ts +++ b/client/startup/renderMessage/hexcolor.ts @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; -import { callbacks } from '../../../app/callbacks/client'; +import { callbacks } from '../../../app/callbacks/lib/callbacks'; import { settings } from '../../../app/settings/client'; Meteor.startup(() => { diff --git a/client/startup/renderMessage/highlightWords.ts b/client/startup/renderMessage/highlightWords.ts index 928565d238c54..d95887d18b3aa 100644 --- a/client/startup/renderMessage/highlightWords.ts +++ b/client/startup/renderMessage/highlightWords.ts @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; -import { callbacks } from '../../../app/callbacks/client'; +import { callbacks } from '../../../app/callbacks/lib/callbacks'; import { getUserPreference } from '../../../app/utils/client'; Meteor.startup(() => { diff --git a/client/startup/renderMessage/issuelink.ts b/client/startup/renderMessage/issuelink.ts index 7a465e15b19a8..643615a6b6845 100644 --- a/client/startup/renderMessage/issuelink.ts +++ b/client/startup/renderMessage/issuelink.ts @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; -import { callbacks } from '../../../app/callbacks/client'; +import { callbacks } from '../../../app/callbacks/lib/callbacks'; import { settings } from '../../../app/settings/client'; Meteor.startup(() => { diff --git a/client/startup/renderMessage/katex.ts b/client/startup/renderMessage/katex.ts index 4f5d042d1b3a5..d48bb471a9b55 100644 --- a/client/startup/renderMessage/katex.ts +++ b/client/startup/renderMessage/katex.ts @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; -import { callbacks } from '../../../app/callbacks/client'; +import { callbacks } from '../../../app/callbacks/lib/callbacks'; import { settings } from '../../../app/settings/client'; Meteor.startup(() => { diff --git a/client/startup/renderMessage/markdown.ts b/client/startup/renderMessage/markdown.ts index e38f62bb89bc5..e00aee6fb9378 100644 --- a/client/startup/renderMessage/markdown.ts +++ b/client/startup/renderMessage/markdown.ts @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; -import { callbacks } from '../../../app/callbacks/client'; +import { callbacks } from '../../../app/callbacks/lib/callbacks'; import { settings } from '../../../app/settings/client'; Meteor.startup(() => { diff --git a/client/startup/renderMessage/mentionsMessage.ts b/client/startup/renderMessage/mentionsMessage.ts index e7dc900bc038b..6db6445998957 100644 --- a/client/startup/renderMessage/mentionsMessage.ts +++ b/client/startup/renderMessage/mentionsMessage.ts @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; -import { callbacks } from '../../../app/callbacks/client'; +import { callbacks } from '../../../app/callbacks/lib/callbacks'; import { Users } from '../../../app/models/client'; import { settings } from '../../../app/settings/client'; diff --git a/client/startup/renderNotification/markdown.ts b/client/startup/renderNotification/markdown.ts index 80b28bed11cc4..c2f8179852b45 100644 --- a/client/startup/renderNotification/markdown.ts +++ b/client/startup/renderNotification/markdown.ts @@ -1,6 +1,6 @@ import { Meteor } from 'meteor/meteor'; -import { callbacks } from '../../../app/callbacks/client'; +import { callbacks } from '../../../app/callbacks/lib/callbacks'; import { settings } from '../../../app/settings/client'; Meteor.startup(() => { diff --git a/client/startup/streamMessage/autotranslate.ts b/client/startup/streamMessage/autotranslate.ts index c543998999b98..f788b1565109d 100644 --- a/client/startup/streamMessage/autotranslate.ts +++ b/client/startup/streamMessage/autotranslate.ts @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; import { hasPermission } from '../../../app/authorization/client'; -import { callbacks } from '../../../app/callbacks/client'; +import { callbacks } from '../../../app/callbacks/lib/callbacks'; import { settings } from '../../../app/settings/client'; Meteor.startup(() => { diff --git a/client/startup/userStatusManuallySet.ts b/client/startup/userStatusManuallySet.ts index 46c052c88395c..b969e621ba1d8 100644 --- a/client/startup/userStatusManuallySet.ts +++ b/client/startup/userStatusManuallySet.ts @@ -1,6 +1,6 @@ import { Meteor } from 'meteor/meteor'; -import { callbacks } from '../../app/callbacks/client'; +import { callbacks } from '../../app/callbacks/lib/callbacks'; import { UserStatus } from '../../definition/UserStatus'; import { fireGlobalEvent } from '../lib/utils/fireGlobalEvent'; diff --git a/client/views/setupWizard/steps/AdminUserInformationStep.js b/client/views/setupWizard/steps/AdminUserInformationStep.js index ce14cfe7f6099..dffa56414b646 100644 --- a/client/views/setupWizard/steps/AdminUserInformationStep.js +++ b/client/views/setupWizard/steps/AdminUserInformationStep.js @@ -10,13 +10,13 @@ import { import { useAutoFocus, useUniqueId } from '@rocket.chat/fuselage-hooks'; import React, { useMemo, useState, useEffect } from 'react'; +import { callbacks } from '../../../../app/callbacks/lib/callbacks'; import { useMethod } from '../../../contexts/ServerContext'; import { useSessionDispatch } from '../../../contexts/SessionContext'; import { useSetting } from '../../../contexts/SettingsContext'; import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext'; import { useTranslation } from '../../../contexts/TranslationContext'; import { useLoginWithPassword } from '../../../contexts/UserContext'; -import { useCallbacks } from '../../../hooks/useCallbacks'; import { Pager } from '../Pager'; import { Step } from '../Step'; import { StepHeader } from '../StepHeader'; @@ -27,7 +27,6 @@ function AdminUserInformationStep({ step, title, active }) { const defineUsername = useMethod('setUsername'); const setForceLogin = useSessionDispatch('forceLogin'); - const callbacks = useCallbacks(); const dispatchToastMessage = useToastMessageDispatch(); const registerAdminUser = async ({ From 8875f5019bd4e1f6486b7ad116105f9a3664f2d8 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Fri, 12 Nov 2021 09:59:34 -0300 Subject: [PATCH 064/137] Regression: Improve AggregationCursor types (#23692) --- app/models/server/raw/Analytics.ts | 44 ++++++++++++++++++++++++------ app/models/server/raw/Sessions.ts | 14 ++++++++-- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/app/models/server/raw/Analytics.ts b/app/models/server/raw/Analytics.ts index 943274853746f..681a8be0ef633 100644 --- a/app/models/server/raw/Analytics.ts +++ b/app/models/server/raw/Analytics.ts @@ -49,8 +49,14 @@ export class AnalyticsRaw extends BaseRaw { }); } - getMessagesSentTotalByDate({ start, end, options = {} }: { start: IAnalytic['date']; end: IAnalytic['date']; options?: { sort?: SortOptionObject; count?: number } }): AggregationCursor { - return this.col.aggregate([ + getMessagesSentTotalByDate({ start, end, options = {} }: { start: IAnalytic['date']; end: IAnalytic['date']; options?: { sort?: SortOptionObject; count?: number } }): AggregationCursor<{ + _id: IAnalytic['date']; + messages: number; + }> { + return this.col.aggregate<{ + _id: IAnalytic['date']; + messages: number; + }>([ { $match: { type: 'messages', @@ -68,7 +74,10 @@ export class AnalyticsRaw extends BaseRaw { ]); } - getMessagesOrigin({ start, end }: { start: IAnalytic['date']; end: IAnalytic['date'] }): AggregationCursor { + getMessagesOrigin({ start, end }: { start: IAnalytic['date']; end: IAnalytic['date'] }): AggregationCursor<{ + t: 'message'; + messages: number; + }> { const params = [ { $match: { @@ -90,11 +99,24 @@ export class AnalyticsRaw extends BaseRaw { }, }, ]; - return this.col.aggregate(params); + return this.col.aggregate<{ + t: 'message'; + messages: number; + }>(params); } - getMostPopularChannelsByMessagesSentQuantity({ start, end, options = {} }: { start: IAnalytic['date']; end: IAnalytic['date']; options?: { sort?: SortOptionObject; count?: number } }): AggregationCursor { - return this.col.aggregate([ + getMostPopularChannelsByMessagesSentQuantity({ start, end, options = {} }: { start: IAnalytic['date']; end: IAnalytic['date']; options?: { sort?: SortOptionObject; count?: number } }): AggregationCursor<{ + t: 'messages'; + name: string; + messages: number; + usernames: string[]; + }> { + return this.col.aggregate<{ + t: 'messages'; + name: string; + messages: number; + usernames: string[]; + }>([ { $match: { type: 'messages', @@ -121,8 +143,14 @@ export class AnalyticsRaw extends BaseRaw { ]); } - getTotalOfRegisteredUsersByDate({ start, end, options = {} }: { start: IAnalytic['date']; end: IAnalytic['date']; options?: { sort?: SortOptionObject; count?: number } }): AggregationCursor { - return this.col.aggregate([ + getTotalOfRegisteredUsersByDate({ start, end, options = {} }: { start: IAnalytic['date']; end: IAnalytic['date']; options?: { sort?: SortOptionObject; count?: number } }): AggregationCursor<{ + _id: IAnalytic['date']; + users: number; + }> { + return this.col.aggregate<{ + _id: IAnalytic['date']; + users: number; + }>([ { $match: { type: 'users', diff --git a/app/models/server/raw/Sessions.ts b/app/models/server/raw/Sessions.ts index 86e97ebe76a70..d79fd32c89133 100644 --- a/app/models/server/raw/Sessions.ts +++ b/app/models/server/raw/Sessions.ts @@ -112,8 +112,18 @@ const getProjectionByFullDate = (): { day: string; month: string; year: string } }); export const aggregates = { - dailySessionsOfYesterday(collection: Collection, { year, month, day }: DestructuredDate): AggregationCursor { - return collection.aggregate([{ + dailySessionsOfYesterday(collection: Collection, { year, month, day }: DestructuredDate): AggregationCursor & { + time: number; + sessions: number; + devices: T['device'][]; + _computedAt: string; + }> { + return collection.aggregate & { + time: number; + sessions: number; + devices: T['device'][]; + _computedAt: string; + }>([{ $match: { userId: { $exists: true }, lastActivityAt: { $exists: true }, From f5dd14505b31df9cb41be2203f261b1250f7046a Mon Sep 17 00:00:00 2001 From: Tiago Evangelista Pinto Date: Fri, 12 Nov 2021 10:27:15 -0300 Subject: [PATCH 065/137] fix (#23691) --- client/sidebar/sections/Omnichannel.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/client/sidebar/sections/Omnichannel.js b/client/sidebar/sections/Omnichannel.js index c829b44c11699..6cecae5f77667 100644 --- a/client/sidebar/sections/Omnichannel.js +++ b/client/sidebar/sections/Omnichannel.js @@ -3,12 +3,13 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import React, { memo } from 'react'; import { hasPermission } from '../../../app/authorization/client'; +import { useLayout } from '../../contexts/LayoutContext'; import { useOmnichannelShowQueueLink, useOmnichannelAgentAvailable, useOmnichannelQueueLink, - useOmnichannelDirectoryLink, } from '../../contexts/OmnichannelContext'; +import { useRoute } from '../../contexts/RouterContext'; import { useMethod } from '../../contexts/ServerContext'; import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; import { useTranslation } from '../../contexts/TranslationContext'; @@ -19,7 +20,8 @@ const OmnichannelSection = (props) => { const agentAvailable = useOmnichannelAgentAvailable(); const showOmnichannelQueueLink = useOmnichannelShowQueueLink(); const queueLink = useOmnichannelQueueLink(); - const directoryLink = useOmnichannelDirectoryLink(); + const { sidebar } = useLayout(); + const directoryRoute = useRoute('omnichannel-directory'); const dispatchToastMessage = useToastMessageDispatch(); const icon = { @@ -42,6 +44,11 @@ const OmnichannelSection = (props) => { } }); + const handleDirectory = useMutableCallback(() => { + sidebar.toggle(); + directoryRoute.push({}); + }); + return ( {t('Omnichannel')} @@ -51,7 +58,7 @@ const OmnichannelSection = (props) => { )} {hasPermission(['view-omnichannel-contact-center']) && ( - + )} From e150668e1bc72381e9dffc143c4517913ef6b86e Mon Sep 17 00:00:00 2001 From: Aditya Bhardwaj Date: Fri, 12 Nov 2021 19:01:48 +0530 Subject: [PATCH 066/137] [IMPROVE] Allow override of default department for SMS Livechat sessions (#23626) * Add departmentName query parameter and override default value * Map livechat SMS sessions based on visitor token and department ID * Update SMS endpoint admin description * Fallback to default department if the target department is invalid --- app/livechat/imports/server/rest/sms.js | 16 ++++++++++------ app/models/server/models/LivechatRooms.js | 11 +++++++++++ packages/rocketchat-i18n/i18n/en.i18n.json | 2 +- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/app/livechat/imports/server/rest/sms.js b/app/livechat/imports/server/rest/sms.js index 4f29e7e997e0d..95625ded26d34 100644 --- a/app/livechat/imports/server/rest/sms.js +++ b/app/livechat/imports/server/rest/sms.js @@ -30,7 +30,7 @@ const defineDepartment = (idOrName) => { return department && department._id; }; -const defineVisitor = (smsNumber) => { +const defineVisitor = (smsNumber, targetDepartment) => { const visitor = LivechatVisitors.findOneVisitorByPhone(smsNumber); let data = { token: (visitor && visitor.token) || Random.id(), @@ -45,9 +45,8 @@ const defineVisitor = (smsNumber) => { }); } - const department = defineDepartment(SMS.department); - if (department) { - data.department = department; + if (targetDepartment) { + data.department = targetDepartment; } const id = Livechat.registerGuest(data); @@ -70,10 +69,15 @@ API.v1.addRoute('livechat/sms-incoming/:service', { post() { const SMSService = SMS.getService(this.urlParams.service); const sms = SMSService.parse(this.bodyParams); + const { departmentName } = this.queryParams; + let targetDepartment = defineDepartment(departmentName || SMS.department); + if (!targetDepartment) { + targetDepartment = defineDepartment(SMS.department); + } - const visitor = defineVisitor(sms.from); + const visitor = defineVisitor(sms.from, targetDepartment); const { token } = visitor; - const room = LivechatRooms.findOneOpenByVisitorToken(token); + const room = LivechatRooms.findOneOpenByVisitorTokenAndDepartmentId(token, targetDepartment); const roomExists = !!room; const location = normalizeLocationSharing(sms); const rid = (room && room._id) || Random.id(); diff --git a/app/models/server/models/LivechatRooms.js b/app/models/server/models/LivechatRooms.js index 994733ad8bc7d..6ef6dd0f44e65 100644 --- a/app/models/server/models/LivechatRooms.js +++ b/app/models/server/models/LivechatRooms.js @@ -280,6 +280,17 @@ export class LivechatRooms extends Base { return this.findOne(query, options); } + findOneOpenByVisitorTokenAndDepartmentId(visitorToken, departmentId, options) { + const query = { + t: 'l', + open: true, + 'v.token': visitorToken, + departmentId, + }; + + return this.findOne(query, options); + } + findOpenByVisitorTokenAndDepartmentId(visitorToken, departmentId, options) { const query = { t: 'l', diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 91a0a21421532..6fd8880bbb617 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -3911,7 +3911,7 @@ "Smileys_and_People": "Smileys & People", "SMS": "SMS", "SMS_Default_Omnichannel_Department": "Omnichannel Department (Default)", - "SMS_Default_Omnichannel_Department_Description": "If set, all new incoming chats initiated by this integration will be routed to this department.", + "SMS_Default_Omnichannel_Department_Description": "If set, all new incoming chats initiated by this integration will be routed to this department.\nThis setting can be overwritten by passing departmentName query param in the request.\ne.g. https:///api/v1/livechat/sms-incoming/twilio?departmentName=< departmentName>.\nDepartment name should be URL safe.", "SMS_Enabled": "SMS Enabled", "SMTP": "SMTP", "SMTP_Host": "SMTP Host", From 687e17a6ab17aba1ec01b2c03d98c32bc04e102d Mon Sep 17 00:00:00 2001 From: Aman-Maheshwari <50165440+Aman-Maheshwari@users.noreply.github.com> Date: Fri, 12 Nov 2021 19:02:48 +0530 Subject: [PATCH 067/137] [FIX] Omnichannel business hours page breaking navigation (#23595) * fix business hour persisting UI * eslint * improvement * Update BusinessHoursRouter.js * Update EditBusinessHoursPage.js * remove useState Co-authored-by: Tiago Evangelista Pinto --- .../views/omnichannel/businessHours/BusinessHoursRouter.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/views/omnichannel/businessHours/BusinessHoursRouter.js b/client/views/omnichannel/businessHours/BusinessHoursRouter.js index f895667994eb3..c1b788ba4b0db 100644 --- a/client/views/omnichannel/businessHours/BusinessHoursRouter.js +++ b/client/views/omnichannel/businessHours/BusinessHoursRouter.js @@ -21,15 +21,15 @@ const BusinessHoursRouter = () => { const router = useRoute('omnichannel-businessHours'); useEffect(() => { - if (isSingleBH && (context !== 'edit' || type !== 'default')) { + if (isSingleBH) { router.push({ context: 'edit', type: 'default', }); } - }, [context, isSingleBH, router, type]); + }, [isSingleBH, router]); - if ((context === 'edit' && type) || (isSingleBH && (context !== 'edit' || type !== 'default'))) { + if (context === 'edit' || isSingleBH) { return type ? : null; } From 96426cdf94e64d185f26c7a297c9e9f18467c09b Mon Sep 17 00:00:00 2001 From: Aman-Maheshwari <50165440+Aman-Maheshwari@users.noreply.github.com> Date: Fri, 12 Nov 2021 19:03:41 +0530 Subject: [PATCH 068/137] fix omnichannel webhooks can't be saved (#23641) --- .../server/methods/saveIntegration.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/app/livechat/server/methods/saveIntegration.js b/app/livechat/server/methods/saveIntegration.js index 38288eab9e0d1..d9585643783eb 100644 --- a/app/livechat/server/methods/saveIntegration.js +++ b/app/livechat/server/methods/saveIntegration.js @@ -3,7 +3,6 @@ import s from 'underscore.string'; import { hasPermission } from '../../../authorization'; import { Settings } from '../../../models/server'; -import { settings } from '../../../settings'; Meteor.methods({ 'livechat:saveIntegration'(values) { @@ -16,39 +15,39 @@ Meteor.methods({ } if (typeof values.Livechat_secret_token !== 'undefined') { - settings.updateValueById('Livechat_secret_token', s.trim(values.Livechat_secret_token)); + Settings.updateValueById('Livechat_secret_token', s.trim(values.Livechat_secret_token)); } if (typeof values.Livechat_webhook_on_start !== 'undefined') { - settings.updateValueById('Livechat_webhook_on_start', !!values.Livechat_webhook_on_start); + Settings.updateValueById('Livechat_webhook_on_start', !!values.Livechat_webhook_on_start); } if (typeof values.Livechat_webhook_on_close !== 'undefined') { - settings.updateValueById('Livechat_webhook_on_close', !!values.Livechat_webhook_on_close); + Settings.updateValueById('Livechat_webhook_on_close', !!values.Livechat_webhook_on_close); } if (typeof values.Livechat_webhook_on_chat_taken !== 'undefined') { - settings.updateValueById('Livechat_webhook_on_chat_taken', !!values.Livechat_webhook_on_chat_taken); + Settings.updateValueById('Livechat_webhook_on_chat_taken', !!values.Livechat_webhook_on_chat_taken); } if (typeof values.Livechat_webhook_on_chat_queued !== 'undefined') { - settings.updateValueById('Livechat_webhook_on_chat_queued', !!values.Livechat_webhook_on_chat_queued); + Settings.updateValueById('Livechat_webhook_on_chat_queued', !!values.Livechat_webhook_on_chat_queued); } if (typeof values.Livechat_webhook_on_forward !== 'undefined') { - settings.updateValueById('Livechat_webhook_on_forward', !!values.Livechat_webhook_on_forward); + Settings.updateValueById('Livechat_webhook_on_forward', !!values.Livechat_webhook_on_forward); } if (typeof values.Livechat_webhook_on_offline_msg !== 'undefined') { - settings.updateValueById('Livechat_webhook_on_offline_msg', !!values.Livechat_webhook_on_offline_msg); + Settings.updateValueById('Livechat_webhook_on_offline_msg', !!values.Livechat_webhook_on_offline_msg); } if (typeof values.Livechat_webhook_on_visitor_message !== 'undefined') { - settings.updateValueById('Livechat_webhook_on_visitor_message', !!values.Livechat_webhook_on_visitor_message); + Settings.updateValueById('Livechat_webhook_on_visitor_message', !!values.Livechat_webhook_on_visitor_message); } if (typeof values.Livechat_webhook_on_agent_message !== 'undefined') { - settings.updateValueById('Livechat_webhook_on_agent_message', !!values.Livechat_webhook_on_agent_message); + Settings.updateValueById('Livechat_webhook_on_agent_message', !!values.Livechat_webhook_on_agent_message); } }, }); From 91c5ddc06005a6527d5560fcbf5eb17369a57718 Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Fri, 12 Nov 2021 11:31:03 -0300 Subject: [PATCH 069/137] [FIX] Horizontal rule render for inline marked options #23380 --- client/components/MarkdownText.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/components/MarkdownText.tsx b/client/components/MarkdownText.tsx index 4baefbab19d19..82f4e724347ae 100644 --- a/client/components/MarkdownText.tsx +++ b/client/components/MarkdownText.tsx @@ -31,6 +31,7 @@ const listItemMarked = (text: string): string => { const cleanText = text.replace(/|<\/p>/gi, ''); return `

  • ${cleanText}
  • `; }; +const horizontalRuleMarked = (): string => ''; documentRenderer.link = linkMarked; documentRenderer.listitem = listItemMarked; @@ -38,11 +39,13 @@ documentRenderer.listitem = listItemMarked; inlineRenderer.link = linkMarked; inlineRenderer.paragraph = paragraphMarked; inlineRenderer.listitem = listItemMarked; +inlineRenderer.hr = horizontalRuleMarked; inlineWithoutBreaks.link = linkMarked; inlineWithoutBreaks.paragraph = paragraphMarked; inlineWithoutBreaks.br = brMarked; inlineWithoutBreaks.listitem = listItemMarked; +inlineWithoutBreaks.hr = horizontalRuleMarked; const defaultOptions = { gfm: true, From f05d8932dbd114320d48d083e64433df58a7b6f0 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 15 Nov 2021 04:08:07 -0600 Subject: [PATCH 070/137] Replace all occurrences of a placeholder on string instead of just first one (#23703) --- .../server/hooks/onMessageSentParsePlaceholder.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ee/app/canned-responses/server/hooks/onMessageSentParsePlaceholder.ts b/ee/app/canned-responses/server/hooks/onMessageSentParsePlaceholder.ts index 7afe45463a938..c8a0006c7c074 100644 --- a/ee/app/canned-responses/server/hooks/onMessageSentParsePlaceholder.ts +++ b/ee/app/canned-responses/server/hooks/onMessageSentParsePlaceholder.ts @@ -29,6 +29,8 @@ const placeholderFields = { }, }; +const replaceAll = (text: string, old: string, replace: string): string => text.replace(new RegExp(old, 'g'), replace); + const handleBeforeSaveMessage = (message: IMessage, room: IOmnichannelRoom): any => { if (!message.msg || message.msg === '') { return message; @@ -50,7 +52,7 @@ const handleBeforeSaveMessage = (message: IMessage, room: IOmnichannelRoom): any const placeholderConfig = placeholderFields[field as keyof typeof placeholderFields]; const from = placeholderConfig.from === 'agent' ? agent : visitor; const data = get(from, placeholderConfig.dataKey, ''); - messageText = messageText.replace(templateKey, data); + messageText = replaceAll(messageText, templateKey, data); return messageText; }); From a858dbc237fc9c628bed2444ce0d47a2cf6d7897 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Mon, 15 Nov 2021 14:53:42 -0300 Subject: [PATCH 071/137] Chore: Api definitions (#23701) Co-authored-by: Tasso Evangelista --- app/api/server/api.d.ts | 116 ++++- app/api/server/api.js | 2 +- app/api/server/helpers/getPaginationItems.js | 2 +- app/api/server/v1/banners.ts | 23 +- app/api/server/v1/chat.js | 6 +- app/api/server/v1/dns.ts | 8 +- app/api/server/v1/instances.ts | 4 +- app/api/server/v1/ldap.ts | 12 +- app/api/server/v1/permissions.ts | 60 +-- app/api/server/v1/roles.ts | 171 +++---- app/api/server/v1/settings.ts | 101 ++-- app/api/server/v1/teams.ts | 233 ++++++---- .../server/functions/getUsersInRole.ts | 4 +- app/models/server/raw/BaseRaw.ts | 2 +- app/models/server/raw/Permissions.ts | 4 + app/models/server/raw/Roles.ts | 8 +- app/models/server/raw/Settings.ts | 2 +- .../contexts/ServerContext/ServerContext.ts | 2 +- client/contexts/ServerContext/endpoints.ts | 87 ---- .../contexts/ServerContext/endpoints/v1/dm.ts | 21 - .../ServerContext/endpoints/v1/teams.ts | 76 --- client/contexts/ServerContext/index.ts | 1 - client/hooks/useEndpointAction.ts | 2 +- client/hooks/useEndpointActionExperimental.ts | 2 +- client/hooks/useEndpointData.ts | 2 +- client/providers/ServerProvider.tsx | 5 +- .../channels/hooks/useTeamsChannelList.ts | 3 +- definition/IMessage/IMessage.ts | 10 +- definition/IRoom.ts | 1 + definition/IUser.ts | 2 +- .../apps.ts => definition/rest/apps/index.ts | 0 definition/rest/helpers/PaginatedRequest.ts | 4 + definition/rest/helpers/PaginatedResult.ts | 5 + definition/rest/index.ts | 97 ++++ definition/rest/v1/banners.ts | 26 ++ .../rest}/v1/channels.ts | 6 +- .../endpoints => definition/rest}/v1/chat.ts | 4 +- .../endpoints => definition/rest}/v1/cloud.ts | 0 .../rest}/v1/customUserStatus.ts | 0 definition/rest/v1/dm.ts | 21 + .../endpoints => definition/rest}/v1/dns.ts | 5 +- .../rest}/v1/emojiCustom.ts | 2 +- .../rest}/v1/groups.ts | 6 +- .../endpoints => definition/rest}/v1/im.ts | 14 +- definition/rest/v1/instances.ts | 16 + .../endpoints => definition/rest}/v1/ldap.ts | 6 +- .../rest}/v1/licenses.ts | 5 +- .../endpoints => definition/rest}/v1/misc.ts | 0 .../rest}/v1/omnichannel.ts | 18 +- definition/rest/v1/permissions.ts | 43 ++ definition/rest/v1/roles.ts | 186 ++++++++ .../endpoints => definition/rest}/v1/rooms.ts | 6 +- definition/rest/v1/settings.ts | 100 ++++ .../rest}/v1/statistics.ts | 2 +- .../v1/teams/TeamsAddMembersProps.test.ts | 71 +++ .../rest/v1/teams/TeamsAddMembersProps.ts | 80 ++++ .../teams/TeamsConvertToChannelProps.test.ts | 39 ++ .../v1/teams/TeamsConvertToChannelProps.ts | 54 +++ .../rest/v1/teams/TeamsDeleteProps.test.ts | 64 +++ definition/rest/v1/teams/TeamsDeleteProps.ts | 50 ++ .../rest/v1/teams/TeamsLeaveProps.test.ts | 64 +++ definition/rest/v1/teams/TeamsLeaveProps.ts | 51 ++ .../v1/teams/TeamsRemoveMemberProps.test.ts | 61 +++ .../rest/v1/teams/TeamsRemoveMemberProps.ts | 56 +++ .../v1/teams/TeamsRemoveRoomProps.test.ts | 27 ++ .../rest/v1/teams/TeamsRemoveRoomProps.ts | 40 ++ .../v1/teams/TeamsUpdateMemberProps.test.ts | 59 +++ .../rest/v1/teams/TeamsUpdateMemberProps.ts | 68 +++ .../rest/v1/teams/TeamsUpdateProps.test.ts | 161 +++++++ definition/rest/v1/teams/TeamsUpdateProps.ts | 75 +++ definition/rest/v1/teams/index.ts | 148 ++++++ .../endpoints => definition/rest}/v1/users.ts | 4 +- definition/utils.ts | 2 + ee/app/license/definitions/ILicense.ts | 11 + ee/app/license/definitions/ILicenseTag.ts | 4 + ee/app/license/server/license.ts | 17 +- .../server/api/business-hours.ts | 7 +- .../server/business-hour/Helper.ts | 2 +- .../endpoints/v1/engagementDashboard.ts | 10 - ee/definition/rest/index.ts | 4 + ee/definition/rest/v1/engagementDashboard.ts | 62 +++ .../rest/v1/omnichannel/businessHours.ts | 7 + ee/server/api/ldap.ts | 6 +- ee/server/api/licenses.ts | 3 +- ee/server/{index.js => index.ts} | 0 ee/server/lib/ldap/Manager.ts | 2 +- ee/server/services/package-lock.json | 39 ++ ee/server/services/package.json | 1 + package-lock.json | 80 +++- package.json | 2 + server/sdk/types/ITeamService.ts | 13 +- tests/end-to-end/api/13-roles.js | 440 ------------------ tsconfig.json | 5 +- 93 files changed, 2390 insertions(+), 1043 deletions(-) delete mode 100644 client/contexts/ServerContext/endpoints.ts delete mode 100644 client/contexts/ServerContext/endpoints/v1/dm.ts delete mode 100644 client/contexts/ServerContext/endpoints/v1/teams.ts rename client/contexts/ServerContext/endpoints/apps.ts => definition/rest/apps/index.ts (100%) create mode 100644 definition/rest/helpers/PaginatedRequest.ts create mode 100644 definition/rest/helpers/PaginatedResult.ts create mode 100644 definition/rest/index.ts create mode 100644 definition/rest/v1/banners.ts rename {client/contexts/ServerContext/endpoints => definition/rest}/v1/channels.ts (70%) rename {client/contexts/ServerContext/endpoints => definition/rest}/v1/chat.ts (84%) rename {client/contexts/ServerContext/endpoints => definition/rest}/v1/cloud.ts (100%) rename {client/contexts/ServerContext/endpoints => definition/rest}/v1/customUserStatus.ts (100%) create mode 100644 definition/rest/v1/dm.ts rename {client/contexts/ServerContext/endpoints => definition/rest}/v1/dns.ts (62%) rename {client/contexts/ServerContext/endpoints => definition/rest}/v1/emojiCustom.ts (73%) rename {client/contexts/ServerContext/endpoints => definition/rest}/v1/groups.ts (69%) rename {client/contexts/ServerContext/endpoints => definition/rest}/v1/im.ts (65%) create mode 100644 definition/rest/v1/instances.ts rename {client/contexts/ServerContext/endpoints => definition/rest}/v1/ldap.ts (64%) rename {client/contexts/ServerContext/endpoints => definition/rest}/v1/licenses.ts (64%) rename {client/contexts/ServerContext/endpoints => definition/rest}/v1/misc.ts (100%) rename {client/contexts/ServerContext/endpoints => definition/rest}/v1/omnichannel.ts (82%) create mode 100644 definition/rest/v1/permissions.ts create mode 100644 definition/rest/v1/roles.ts rename {client/contexts/ServerContext/endpoints => definition/rest}/v1/rooms.ts (81%) create mode 100644 definition/rest/v1/settings.ts rename {client/contexts/ServerContext/endpoints => definition/rest}/v1/statistics.ts (63%) create mode 100644 definition/rest/v1/teams/TeamsAddMembersProps.test.ts create mode 100644 definition/rest/v1/teams/TeamsAddMembersProps.ts create mode 100644 definition/rest/v1/teams/TeamsConvertToChannelProps.test.ts create mode 100644 definition/rest/v1/teams/TeamsConvertToChannelProps.ts create mode 100644 definition/rest/v1/teams/TeamsDeleteProps.test.ts create mode 100644 definition/rest/v1/teams/TeamsDeleteProps.ts create mode 100644 definition/rest/v1/teams/TeamsLeaveProps.test.ts create mode 100644 definition/rest/v1/teams/TeamsLeaveProps.ts create mode 100644 definition/rest/v1/teams/TeamsRemoveMemberProps.test.ts create mode 100644 definition/rest/v1/teams/TeamsRemoveMemberProps.ts create mode 100644 definition/rest/v1/teams/TeamsRemoveRoomProps.test.ts create mode 100644 definition/rest/v1/teams/TeamsRemoveRoomProps.ts create mode 100644 definition/rest/v1/teams/TeamsUpdateMemberProps.test.ts create mode 100644 definition/rest/v1/teams/TeamsUpdateMemberProps.ts create mode 100644 definition/rest/v1/teams/TeamsUpdateProps.test.ts create mode 100644 definition/rest/v1/teams/TeamsUpdateProps.ts create mode 100644 definition/rest/v1/teams/index.ts rename {client/contexts/ServerContext/endpoints => definition/rest}/v1/users.ts (71%) create mode 100644 ee/app/license/definitions/ILicense.ts create mode 100644 ee/app/license/definitions/ILicenseTag.ts delete mode 100644 ee/client/contexts/ServerContext/endpoints/v1/engagementDashboard.ts create mode 100644 ee/definition/rest/index.ts create mode 100644 ee/definition/rest/v1/engagementDashboard.ts create mode 100644 ee/definition/rest/v1/omnichannel/businessHours.ts rename ee/server/{index.js => index.ts} (100%) delete mode 100644 tests/end-to-end/api/13-roles.js diff --git a/app/api/server/api.d.ts b/app/api/server/api.d.ts index 9e968448bbc0c..94b44c774fe6f 100644 --- a/app/api/server/api.d.ts +++ b/app/api/server/api.d.ts @@ -1,4 +1,118 @@ -import { APIClass } from '.'; +import { Endpoints } from '../../../definition/rest'; +import { Awaited } from '../../../definition/utils'; +import { IUser } from '../../../definition/IUser'; + + +export type ChangeTypeOfKeys< + T extends object, + Keys extends keyof T, + NewType +> = { + [key in keyof T]: key extends Keys ? NewType : T[key] +} + +type This = { + getPaginationItems(): ({ + offset: number; + count: number; + }); + parseJsonQuery(): ({ + sort: Record; + fields: Record; + query: Record; + }); + readonly urlParams: Record; + getUserFromParams(): IUser; +} + +type ThisLoggedIn = { + readonly user: IUser; + readonly userId: string; +} + +type ThisLoggedOut = { + readonly user: null; + readonly userId: null; +} + +type EndpointWithExtraOptions any, A> = WrappedFunction | ({ action: WrappedFunction } & (A extends true ? { twoFactorRequired: boolean } : {})); + +export type Methods = { + [K in keyof T as `${Lowercase}`]: T[K] extends (...args: any) => any ? EndpointWithExtraOptions<(this: This & (A extends true ? ThisLoggedIn : ThisLoggedOut) & Params[0]>) => ReturnType, A> : never; +}; + +type Params = K extends 'GET' ? { readonly queryParams: Partial

    } : K extends 'POST' ? { readonly bodyParams: Partial

    } : never; + +type SuccessResult = { + statusCode: 200; + success: true; +} & T extends (undefined) ? {} : { body: T } + +type UnauthorizedResult = { + statusCode: 403; + body: { + success: false; + error: string; + }; +} + +type FailureResult = { + statusCode: 400; +} & FailureBody; + +type FailureBody = Exclude extends object ? { body: T & E } : { + body: E & { error: string } & E extends Error ? { details: string } : {} & ST extends undefined ? {} : { stack: string } & ET extends undefined ? {} : { errorType: string }; +} + +type Errors = FailureResult | FailureResult | FailureResult | UnauthorizedResult; + +type WrappedFunction any> = (this: ThisParameterType, ...args: Parameters) => ReturnTypes; + +type ReturnTypes any> = PromisedOrNot> | PromisedOrNot>; + +type PromisedOrNot = Promise | T; + +type Options = { + permissionsRequired?: string[]; + twoFactorOptions?: unknown; + twoFactorRequired?: boolean; + authRequired?: boolean; +} + +export type RestEndpoints

    = Methods; + +type ToLowerCaseKeys = { + [K in keyof T as `${Lowercase}`]: T[K]; +}; +type ToResultType = { + [K in keyof T]: T[K] extends (...args: any) => any ? Awaited> : never; +} +export type ResultTypeEndpoints

    = ToResultType>; + +declare class APIClass { + addRoute

    (route: P, endpoints: RestEndpoints

    ): void; + + addRoute

    (route: P, options: O, endpoints: RestEndpoints): void; + + unauthorized(msg?: string): UnauthorizedResult; + + failure(result: string, errorType?: ET, stack?: ST, error?: E): FailureResult; + + failure(result: object): FailureResult; + + failure(): FailureResult; + + success(): SuccessResult; + + success(result: T): SuccessResult; + + defaultFieldsToExclude: { + joinCode: 0; + members: 0; + importIds: 0; + e2e: 0; + } +} export declare const API: { v1: APIClass; diff --git a/app/api/server/api.js b/app/api/server/api.js index 3b1d6bdfbb9ed..01a71effe8a34 100644 --- a/app/api/server/api.js +++ b/app/api/server/api.js @@ -403,7 +403,7 @@ export class APIClass extends Restivus { api.processTwoFactor({ userId: this.userId, request: this.request, invocation, options: _options.twoFactorOptions, connection }); } - result = DDP._CurrentInvocation.withValue(invocation, () => originalAction.apply(this)) || API.v1.success(); + result = DDP._CurrentInvocation.withValue(invocation, () => Promise.await(originalAction.apply(this))) || API.v1.success(); log.http({ status: result.statusCode, diff --git a/app/api/server/helpers/getPaginationItems.js b/app/api/server/helpers/getPaginationItems.js index 0cff491a97636..259f79a1191a3 100644 --- a/app/api/server/helpers/getPaginationItems.js +++ b/app/api/server/helpers/getPaginationItems.js @@ -10,7 +10,7 @@ API.helperMethods.set('getPaginationItems', function _getPaginationItems() { const offset = this.queryParams.offset ? parseInt(this.queryParams.offset) : 0; let count = defaultCount; - // Ensure count is an appropiate amount + // Ensure count is an appropriate amount if (typeof this.queryParams.count !== 'undefined') { count = parseInt(this.queryParams.count); } else { diff --git a/app/api/server/v1/banners.ts b/app/api/server/v1/banners.ts index 5dd5089814b41..678d6e726cf24 100644 --- a/app/api/server/v1/banners.ts +++ b/app/api/server/v1/banners.ts @@ -52,7 +52,7 @@ import { BannerPlatform } from '../../../../definition/IBanner'; * $ref: '#/components/schemas/ApiFailureV1' */ API.v1.addRoute('banners.getNew', { authRequired: true }, { // deprecated - get() { + async get() { check(this.queryParams, Match.ObjectIncluding({ platform: String, bid: Match.Maybe(String), @@ -67,7 +67,7 @@ API.v1.addRoute('banners.getNew', { authRequired: true }, { // deprecated throw new Meteor.Error('error-unknown-platform', 'Platform is unknown.'); } - const banners = Promise.await(Banner.getBannersForUser(this.userId, platform, bannerId)); + const banners = await Banner.getBannersForUser(this.userId, platform, bannerId); return API.v1.success({ banners }); }, @@ -119,13 +119,18 @@ API.v1.addRoute('banners.getNew', { authRequired: true }, { // deprecated * schema: * $ref: '#/components/schemas/ApiFailureV1' */ -API.v1.addRoute('banners/:id', { authRequired: true }, { - get() { +API.v1.addRoute('banners/:id', { authRequired: true }, { // TODO: move to users/:id/banners + async get() { check(this.urlParams, Match.ObjectIncluding({ id: String, })); + check(this.queryParams, Match.ObjectIncluding({ + platform: String, + })); + const { platform } = this.queryParams; + if (!platform) { throw new Meteor.Error('error-missing-param', 'The required "platform" param is missing.'); } @@ -135,7 +140,7 @@ API.v1.addRoute('banners/:id', { authRequired: true }, { throw new Meteor.Error('error-missing-param', 'The required "id" param is missing.'); } - const banners = Promise.await(Banner.getBannersForUser(this.userId, platform, id)); + const banners = await Banner.getBannersForUser(this.userId, platform, id); return API.v1.success({ banners }); }, @@ -179,7 +184,7 @@ API.v1.addRoute('banners/:id', { authRequired: true }, { * $ref: '#/components/schemas/ApiFailureV1' */ API.v1.addRoute('banners', { authRequired: true }, { - get() { + async get() { check(this.queryParams, Match.ObjectIncluding({ platform: String, })); @@ -193,7 +198,7 @@ API.v1.addRoute('banners', { authRequired: true }, { throw new Meteor.Error('error-unknown-platform', 'Platform is unknown.'); } - const banners = Promise.await(Banner.getBannersForUser(this.userId, platform)); + const banners = await Banner.getBannersForUser(this.userId, platform); return API.v1.success({ banners }); }, @@ -233,7 +238,7 @@ API.v1.addRoute('banners', { authRequired: true }, { * $ref: '#/components/schemas/ApiFailureV1' */ API.v1.addRoute('banners.dismiss', { authRequired: true }, { - post() { + async post() { check(this.bodyParams, Match.ObjectIncluding({ bannerId: String, })); @@ -244,7 +249,7 @@ API.v1.addRoute('banners.dismiss', { authRequired: true }, { throw new Meteor.Error('error-missing-param', 'The required "bannerId" param is missing.'); } - Promise.await(Banner.dismiss(this.userId, bannerId)); + await Banner.dismiss(this.userId, bannerId); return API.v1.success(); }, }); diff --git a/app/api/server/v1/chat.js b/app/api/server/v1/chat.js index eba0e4e3f668e..fa3e917665fc1 100644 --- a/app/api/server/v1/chat.js +++ b/app/api/server/v1/chat.js @@ -697,7 +697,7 @@ API.v1.addRoute('chat.getSnippetedMessages', { authRequired: true }, { }); API.v1.addRoute('chat.getDiscussions', { authRequired: true }, { - get() { + async get() { const { roomId, text } = this.queryParams; const { sort } = this.parseJsonQuery(); const { offset, count } = this.getPaginationItems(); @@ -705,7 +705,7 @@ API.v1.addRoute('chat.getDiscussions', { authRequired: true }, { if (!roomId) { throw new Meteor.Error('error-invalid-params', 'The required "roomId" query param is missing.'); } - const messages = Promise.await(findDiscussionsFromRoom({ + const messages = await findDiscussionsFromRoom({ uid: this.userId, roomId, text, @@ -714,7 +714,7 @@ API.v1.addRoute('chat.getDiscussions', { authRequired: true }, { count, sort, }, - })); + }); return API.v1.success(messages); }, }); diff --git a/app/api/server/v1/dns.ts b/app/api/server/v1/dns.ts index 902ef90d47821..a0b0fa5788e6f 100644 --- a/app/api/server/v1/dns.ts +++ b/app/api/server/v1/dns.ts @@ -48,7 +48,7 @@ import { resolveSRV, resolveTXT } from '../../../federation/server/functions/res * $ref: '#/components/schemas/ApiFailureV1' */ API.v1.addRoute('dns.resolve.srv', { authRequired: true }, { - get() { + async get() { check(this.queryParams, Match.ObjectIncluding({ url: String, })); @@ -58,7 +58,7 @@ API.v1.addRoute('dns.resolve.srv', { authRequired: true }, { throw new Meteor.Error('error-missing-param', 'The required "url" param is missing.'); } - const resolved = Promise.await(resolveSRV(url)); + const resolved = await resolveSRV(url); return API.v1.success({ resolved }); }, @@ -99,7 +99,7 @@ API.v1.addRoute('dns.resolve.srv', { authRequired: true }, { * $ref: '#/components/schemas/ApiFailureV1' */ API.v1.addRoute('dns.resolve.txt', { authRequired: true }, { - post() { + async post() { check(this.queryParams, Match.ObjectIncluding({ url: String, })); @@ -109,7 +109,7 @@ API.v1.addRoute('dns.resolve.txt', { authRequired: true }, { throw new Meteor.Error('error-missing-param', 'The required "url" param is missing.'); } - const resolved = Promise.await(resolveTXT(url)); + const resolved = await resolveTXT(url); return API.v1.success({ resolved }); }, diff --git a/app/api/server/v1/instances.ts b/app/api/server/v1/instances.ts index d3db3489537a3..54bd2a563d14f 100644 --- a/app/api/server/v1/instances.ts +++ b/app/api/server/v1/instances.ts @@ -5,12 +5,12 @@ import { InstanceStatus } from '../../../models/server/raw'; import { IInstanceStatus } from '../../../../definition/IInstanceStatus'; API.v1.addRoute('instances.get', { authRequired: true }, { - get() { + async get() { if (!hasPermission(this.userId, 'view-statistics')) { return API.v1.unauthorized(); } - const instances = Promise.await(InstanceStatus.find().toArray()); + const instances = await InstanceStatus.find().toArray(); return API.v1.success({ instances: instances.map((instance: IInstanceStatus) => { diff --git a/app/api/server/v1/ldap.ts b/app/api/server/v1/ldap.ts index ee98484d17915..c424342d97128 100644 --- a/app/api/server/v1/ldap.ts +++ b/app/api/server/v1/ldap.ts @@ -7,7 +7,7 @@ import { SystemLogger } from '../../../../server/lib/logger/system'; import { LDAP } from '../../../../server/sdk'; API.v1.addRoute('ldap.testConnection', { authRequired: true }, { - post() { + async post() { if (!this.userId) { throw new Error('error-invalid-user'); } @@ -21,20 +21,20 @@ API.v1.addRoute('ldap.testConnection', { authRequired: true }, { } try { - Promise.await(LDAP.testConnection()); + await LDAP.testConnection(); } catch (error) { SystemLogger.error(error); throw new Error('Connection_failed'); } return API.v1.success({ - message: 'Connection_success', + message: 'Connection_success' as const, }); }, }); API.v1.addRoute('ldap.testSearch', { authRequired: true }, { - post() { + async post() { check(this.bodyParams, Match.ObjectIncluding({ username: String, })); @@ -51,10 +51,10 @@ API.v1.addRoute('ldap.testSearch', { authRequired: true }, { throw new Error('LDAP_disabled'); } - Promise.await(LDAP.testSearch(this.bodyParams.username)); + await LDAP.testSearch(this.bodyParams.username); return API.v1.success({ - message: 'LDAP_User_Found', + message: 'LDAP_User_Found' as const, }); }, }); diff --git a/app/api/server/v1/permissions.ts b/app/api/server/v1/permissions.ts index c2aab9afda543..988f4907e351f 100644 --- a/app/api/server/v1/permissions.ts +++ b/app/api/server/v1/permissions.ts @@ -1,12 +1,13 @@ import { Meteor } from 'meteor/meteor'; -import { Match, check } from 'meteor/check'; import { hasPermission } from '../../../authorization/server'; import { API } from '../api'; import { Permissions, Roles } from '../../../models/server/raw'; +import { IPermission } from '../../../../definition/IPermission'; +import { isBodyParamsValidPermissionUpdate } from '../../../../definition/rest/v1/permissions'; API.v1.addRoute('permissions.listAll', { authRequired: true }, { - get() { + async get() { const { updatedSince } = this.queryParams; let updatedSinceDate: Date | undefined; @@ -17,7 +18,10 @@ API.v1.addRoute('permissions.listAll', { authRequired: true }, { updatedSinceDate = new Date(updatedSince); } - const result = Promise.await(Meteor.call('permissions/get', updatedSinceDate)); + const result = await Meteor.call('permissions/get', updatedSinceDate) as { + update: IPermission[]; + remove: IPermission[]; + }; if (Array.isArray(result)) { return API.v1.success({ @@ -31,51 +35,37 @@ API.v1.addRoute('permissions.listAll', { authRequired: true }, { }); API.v1.addRoute('permissions.update', { authRequired: true }, { - post() { + async post() { if (!hasPermission(this.userId, 'access-permissions')) { return API.v1.failure('Editing permissions is not allowed', 'error-edit-permissions-not-allowed'); } - check(this.bodyParams, { - permissions: [ - Match.ObjectIncluding({ - _id: String, - roles: [String], - }), - ], - }); + const { bodyParams } = this; - let permissionNotFound = false; - let roleNotFound = false; - Object.keys(this.bodyParams.permissions).forEach((key) => { - const element = this.bodyParams.permissions[key]; + if (!isBodyParamsValidPermissionUpdate(bodyParams)) { + return API.v1.failure('Invalid body params', 'error-invalid-body-params'); + } - if (!Promise.await(Permissions.findOneById(element._id))) { - permissionNotFound = true; - } + const permissionKeys = bodyParams.permissions.map(({ _id }) => _id); + const permissions = await Permissions.find({ _id: { $in: permissionKeys } }).toArray(); - Object.keys(element.roles).forEach((key) => { - const subElement = element.roles[key]; + if (permissions.length !== bodyParams.permissions.length) { + return API.v1.failure('Invalid permission', 'error-invalid-permission'); + } - if (!Promise.await(Roles.findOneById(subElement))) { - roleNotFound = true; - } - }); - }); + const roleKeys = [...new Set(bodyParams.permissions.flatMap((p) => p.roles))]; - if (permissionNotFound) { - return API.v1.failure('Invalid permission', 'error-invalid-permission'); - } if (roleNotFound) { + const roles = await Roles.find({ _id: { $in: roleKeys } }).toArray(); + + if (roles.length !== roleKeys.length) { return API.v1.failure('Invalid role', 'error-invalid-role'); } - Object.keys(this.bodyParams.permissions).forEach((key) => { - const element = this.bodyParams.permissions[key]; - - Permissions.createOrUpdate(element._id, element.roles); - }); + for await (const permission of bodyParams.permissions) { + await Permissions.setRoles(permission._id, permission.roles); + } - const result = Promise.await(Meteor.call('permissions/get')); + const result = await Meteor.call('permissions/get') as IPermission[]; return API.v1.success({ permissions: result, diff --git a/app/api/server/v1/roles.ts b/app/api/server/v1/roles.ts index 8d87ac7f25c79..95c4b34a5d7f3 100644 --- a/app/api/server/v1/roles.ts +++ b/app/api/server/v1/roles.ts @@ -1,23 +1,25 @@ import { Meteor } from 'meteor/meteor'; -import { Match, check } from 'meteor/check'; import { Users } from '../../../models/server'; import { API } from '../api'; -import { getUsersInRole, hasPermission, hasRole } from '../../../authorization/server'; +import { getUsersInRole, hasRole } from '../../../authorization/server'; import { settings } from '../../../settings/server/index'; import { api } from '../../../../server/sdk/api'; import { Roles } from '../../../models/server/raw'; +import { hasRoleAsync } from '../../../authorization/server/functions/hasRole'; +import { isRoleAddUserToRoleProps, isRoleCreateProps, isRoleDeleteProps, isRoleRemoveUserFromRoleProps, isRoleUpdateProps } from '../../../../definition/rest/v1/roles'; +import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; API.v1.addRoute('roles.list', { authRequired: true }, { - get() { - const roles = Promise.await(Roles.find({}, { fields: { _updatedAt: 0 } }).toArray()); + async get() { + const roles = await Roles.find({}, { projection: { _updatedAt: 0 } }).toArray(); return API.v1.success({ roles }); }, }); API.v1.addRoute('roles.sync', { authRequired: true }, { - get() { + async get() { const { updatedSince } = this.queryParams; if (isNaN(Date.parse(updatedSince))) { @@ -26,21 +28,18 @@ API.v1.addRoute('roles.sync', { authRequired: true }, { return API.v1.success({ roles: { - update: Promise.await(Roles.findByUpdatedDate(new Date(updatedSince), { fields: API.v1.defaultFieldsToExclude }).toArray()), - remove: Promise.await(Roles.trashFindDeletedAfter(new Date(updatedSince)).toArray()), + update: await Roles.findByUpdatedDate(new Date(updatedSince)).toArray(), + remove: await Roles.trashFindDeletedAfter(new Date(updatedSince)).toArray(), }, }); }, }); API.v1.addRoute('roles.create', { authRequired: true }, { - post() { - check(this.bodyParams, { - name: String, - scope: Match.Maybe(String), - description: Match.Maybe(String), - mandatory2fa: Match.Maybe(Boolean), - }); + async post() { + if (!isRoleCreateProps(this.bodyParams)) { + throw new Meteor.Error('error-invalid-role-properties', 'The role properties are invalid.'); + } const roleData = { name: this.bodyParams.name, @@ -49,19 +48,18 @@ API.v1.addRoute('roles.create', { authRequired: true }, { mandatory2fa: this.bodyParams.mandatory2fa, }; - if (!hasPermission(Meteor.userId(), 'access-permissions')) { + if (!await hasPermissionAsync(Meteor.userId(), 'access-permissions')) { throw new Meteor.Error('error-action-not-allowed', 'Accessing permissions is not allowed'); } - if (Promise.await(Roles.findOneByIdOrName(roleData.name))) { + if (await Roles.findOneByIdOrName(roleData.name)) { throw new Meteor.Error('error-duplicate-role-names-not-allowed', 'Role name already exists'); } if (['Users', 'Subscriptions'].includes(roleData.scope) === false) { roleData.scope = 'Users'; } - const a = Roles.createWithRandomId(roleData.name, roleData.scope, roleData.description, false, roleData.mandatory2fa); - const roleId = Promise.await(a).insertedId; + const roleId = (await Roles.createWithRandomId(roleData.name, roleData.scope, roleData.description, false, roleData.mandatory2fa)).insertedId; if (settings.get('UI_DisplayRoles')) { api.broadcast('user.roleUpdate', { @@ -70,19 +68,23 @@ API.v1.addRoute('roles.create', { authRequired: true }, { }); } + const role = await Roles.findOneByIdOrName(roleId); + + if (!role) { + return API.v1.failure('error-role-not-found', 'Role not found'); + } + return API.v1.success({ - role: Promise.await(Roles.findOneByIdOrName(roleId, { fields: API.v1.defaultFieldsToExclude })), + role, }); }, }); API.v1.addRoute('roles.addUserToRole', { authRequired: true }, { - post() { - check(this.bodyParams, { - roleName: String, - username: String, - roomId: Match.Maybe(String), - }); + async post() { + if (!isRoleAddUserToRoleProps(this.bodyParams)) { + throw new Meteor.Error('error-invalid-role-properties', isRoleAddUserToRoleProps.errors?.map((error) => error.message).join('\n')); + } const user = this.getUserFromParams(); const { roleName, roomId } = this.bodyParams; @@ -91,22 +93,26 @@ API.v1.addRoute('roles.addUserToRole', { authRequired: true }, { throw new Meteor.Error('error-user-already-in-role', 'User already in role'); } - Meteor.runAsUser(this.userId, () => { - Meteor.call('authorization:addUserToRole', roleName, user.username, roomId); - }); + await Meteor.call('authorization:addUserToRole', roleName, user.username, roomId); + + const role = await Roles.findOneByIdOrName(roleName); + + if (!role) { + return API.v1.failure('error-role-not-found', 'Role not found'); + } return API.v1.success({ - role: Promise.await(Roles.findOneByIdOrName(this.bodyParams.roleName, { fields: API.v1.defaultFieldsToExclude })), + role, }); }, }); API.v1.addRoute('roles.getUsersInRole', { authRequired: true }, { - get() { + async get() { const { roomId, role } = this.queryParams; const { offset, count = 50 } = this.getPaginationItems(); - const fields = { + const projection = { name: 1, username: 1, emails: 1, @@ -116,42 +122,39 @@ API.v1.addRoute('roles.getUsersInRole', { authRequired: true }, { if (!role) { throw new Meteor.Error('error-param-not-provided', 'Query param "role" is required'); } - if (!hasPermission(this.userId, 'access-permissions')) { + if (!await hasPermissionAsync(this.userId, 'access-permissions')) { throw new Meteor.Error('error-not-allowed', 'Not allowed'); } - if (roomId && !hasPermission(this.userId, 'view-other-user-channels')) { + if (roomId && !await hasPermissionAsync(this.userId, 'view-other-user-channels')) { throw new Meteor.Error('error-not-allowed', 'Not allowed'); } - const users = Promise.await(getUsersInRole(role, roomId, { - limit: count, + const users = await getUsersInRole(role, roomId, { + limit: count as number, sort: { username: 1 }, - skip: offset, - fields, - })); + skip: offset as number, + projection, + }); - return API.v1.success({ users: Promise.await(users.toArray()), total: Promise.await(users.count()) }); + return API.v1.success({ users: await users.toArray(), total: await users.count() }); }, }); API.v1.addRoute('roles.update', { authRequired: true }, { - post() { - check(this.bodyParams, { - roleId: String, - name: Match.Maybe(String), - scope: Match.Maybe(String), - description: Match.Maybe(String), - mandatory2fa: Match.Maybe(Boolean), - }); + async post() { + const { bodyParams } = this; + if (!isRoleUpdateProps(bodyParams)) { + throw new Meteor.Error('error-invalid-role-properties', 'The role properties are invalid.'); + } const roleData = { - roleId: this.bodyParams.roleId, - name: this.bodyParams.name, - scope: this.bodyParams.scope, - description: this.bodyParams.description, - mandatory2fa: this.bodyParams.mandatory2fa, + roleId: bodyParams.roleId, + name: bodyParams.name, + scope: bodyParams.scope || 'Users', + description: bodyParams.description, + mandatory2fa: bodyParams.mandatory2fa, }; - const role = Promise.await(Roles.findOneByIdOrName(roleData.roleId)); + const role = await Roles.findOneByIdOrName(roleData.roleId); if (!role) { throw new Meteor.Error('error-invalid-roleId', 'This role does not exist'); @@ -162,19 +165,17 @@ API.v1.addRoute('roles.update', { authRequired: true }, { } if (roleData.name) { - const otherRole = Promise.await(Roles.findOneByIdOrName(roleData.name)); + const otherRole = await Roles.findOneByIdOrName(roleData.name); if (otherRole && otherRole._id !== role._id) { throw new Meteor.Error('error-duplicate-role-names-not-allowed', 'Role name already exists'); } } - if (roleData.scope) { - if (['Users', 'Subscriptions'].includes(roleData.scope) === false) { - roleData.scope = 'Users'; - } + if (['Users', 'Subscriptions'].includes(roleData.scope) === false) { + throw new Meteor.Error('error-invalid-scope', 'Invalid scope'); } - Promise.await(Roles.updateById(roleData.roleId, roleData.name, roleData.scope, roleData.description, roleData.mandatory2fa)); + await Roles.updateById(roleData.roleId, roleData.name, roleData.scope, roleData.description, roleData.mandatory2fa); if (settings.get('UI_DisplayRoles')) { api.broadcast('user.roleUpdate', { @@ -183,23 +184,30 @@ API.v1.addRoute('roles.update', { authRequired: true }, { }); } + const updatedRole = await Roles.findOneByIdOrName(roleData.roleId); + + if (!updatedRole) { + return API.v1.failure(); + } + return API.v1.success({ - role: Promise.await(Roles.findOneByIdOrName(roleData.roleId, { fields: API.v1.defaultFieldsToExclude })), + role: updatedRole, }); }, }); API.v1.addRoute('roles.delete', { authRequired: true }, { - post() { - check(this.bodyParams, { - roleId: String, - }); + async post() { + const { bodyParams } = this; + if (!isRoleDeleteProps(bodyParams)) { + throw new Meteor.Error('error-invalid-role-properties', 'The role properties are invalid.'); + } - if (!hasPermission(this.userId, 'access-permissions')) { + if (!await hasPermissionAsync(this.userId, 'access-permissions')) { throw new Meteor.Error('error-action-not-allowed', 'Accessing permissions is not allowed'); } - const role = Promise.await(Roles.findOneByIdOrName(this.bodyParams.roleId)); + const role = await Roles.findOneByIdOrName(bodyParams.roleId); if (!role) { throw new Meteor.Error('error-invalid-roleId', 'This role does not exist'); @@ -209,33 +217,32 @@ API.v1.addRoute('roles.delete', { authRequired: true }, { throw new Meteor.Error('error-role-protected', 'Cannot delete a protected role'); } - const existingUsers = Promise.await(Roles.findUsersInRole(role.name, role.scope)); + const existingUsers = await Roles.findUsersInRole(role.name, role.scope); - if (existingUsers && Promise.await(existingUsers.count()) > 0) { + if (existingUsers && await existingUsers.count() > 0) { throw new Meteor.Error('error-role-in-use', 'Cannot delete role because it\'s in use'); } - Promise.await(Roles.removeById(role._id)); + await Roles.removeById(role._id); return API.v1.success(); }, }); API.v1.addRoute('roles.removeUserFromRole', { authRequired: true }, { - post() { - check(this.bodyParams, { - roleName: String, - username: String, - scope: Match.Maybe(String), - }); + async post() { + const { bodyParams } = this; + if (!isRoleRemoveUserFromRoleProps(bodyParams)) { + throw new Meteor.Error('error-invalid-role-properties', 'The role properties are invalid.'); + } const data = { - roleName: this.bodyParams.roleName, - username: this.bodyParams.username, + roleName: bodyParams.roleName, + username: bodyParams.username, scope: this.bodyParams.scope, }; - if (!hasPermission(this.userId, 'access-permissions')) { + if (!await hasPermissionAsync(this.userId, 'access-permissions')) { throw new Meteor.Error('error-not-allowed', 'Accessing permissions is not allowed'); } @@ -245,24 +252,24 @@ API.v1.addRoute('roles.removeUserFromRole', { authRequired: true }, { throw new Meteor.Error('error-invalid-user', 'There is no user with this username'); } - const role = Promise.await(Roles.findOneByIdOrName(data.roleName)); + const role = await Roles.findOneByIdOrName(data.roleName); if (!role) { throw new Meteor.Error('error-invalid-roleId', 'This role does not exist'); } - if (!hasRole(user._id, role.name, data.scope)) { + if (!await hasRoleAsync(user._id, role.name, data.scope)) { throw new Meteor.Error('error-user-not-in-role', 'User is not in this role'); } if (role._id === 'admin') { - const adminCount = Promise.await(Promise.await(Roles.findUsersInRole('admin')).count()); + const adminCount = await (await Roles.findUsersInRole('admin')).count(); if (adminCount === 1) { throw new Meteor.Error('error-admin-required', 'You need to have at least one admin'); } } - Promise.await(Roles.removeUserRoles(user._id, [role.name], data.scope)); + await Roles.removeUserRoles(user._id, [role.name], data.scope); if (settings.get('UI_DisplayRoles')) { api.broadcast('user.roleUpdate', { diff --git a/app/api/server/v1/settings.ts b/app/api/server/v1/settings.ts index 1f60f2ea2c589..b9233b1e09d13 100644 --- a/app/api/server/v1/settings.ts +++ b/app/api/server/v1/settings.ts @@ -1,14 +1,14 @@ import { Meteor } from 'meteor/meteor'; -import { Match, check } from 'meteor/check'; import { ServiceConfiguration } from 'meteor/service-configuration'; import _ from 'underscore'; import { Settings } from '../../../models/server/raw'; import { hasPermission } from '../../../authorization/server'; -import { API } from '../api'; +import { API, ResultTypeEndpoints } from '../api'; import { SettingsEvents, settings } from '../../../settings/server'; import { setValue } from '../../../settings/server/raw'; import { ISetting, ISettingColor, isSettingAction, isSettingColor } from '../../../../definition/ISetting'; +import { isOauthCustomConfiguration, isSettingsUpdatePropDefault, isSettingsUpdatePropsActions, isSettingsUpdatePropsColor } from '../../../../definition/rest/v1/settings'; const fetchSettings = async (query: Parameters[0], sort: Parameters[1]['sort'], offset: Parameters[1]['skip'], count: Parameters[1]['limit'], fields: Parameters[1]['projection']): Promise => { @@ -24,74 +24,35 @@ const fetchSettings = async (query: Parameters[0], sort: P return settings; }; -type OauthCustomConfiguration = { - _id: string; - clientId?: string; - custom: unknown; - service?: string; - serverURL: unknown; - tokenPath: unknown; - identityPath: unknown; - authorizePath: unknown; - scope: unknown; - loginStyle: unknown; - tokenSentVia: unknown; - identityTokenSentVia: unknown; - keyField: unknown; - usernameField: unknown; - emailField: unknown; - nameField: unknown; - avatarField: unknown; - rolesClaim: unknown; - groupsClaim: unknown; - mapChannels: unknown; - channelsMap: unknown; - channelsAdmin: unknown; - mergeUsers: unknown; - mergeRoles: unknown; - accessTokenParam: unknown; - showButton: unknown; - - appId: unknown; - consumerKey: unknown; - - clientConfig: unknown; - buttonLabelText: unknown; - buttonLabelColor: unknown; - buttonColor: unknown; -} -const isOauthCustomConfiguration = (config: any): config is OauthCustomConfiguration => Boolean(config); - // settings endpoints API.v1.addRoute('settings.public', { authRequired: false }, { - get() { + async get() { const { offset, count } = this.getPaginationItems(); const { sort, fields, query } = this.parseJsonQuery(); - let ourQuery = { + const ourQuery = { + ...query, hidden: { $ne: true }, public: true, }; - ourQuery = Object.assign({}, query, ourQuery); - - const settings = Promise.await(fetchSettings(ourQuery, sort, offset, count, fields)); + const settings = await fetchSettings(ourQuery, sort, offset, count, fields); return API.v1.success({ settings, count: settings.length, offset, - total: Settings.find(ourQuery).count(), + total: await Settings.find(ourQuery).count(), }); }, }); API.v1.addRoute('settings.oauth', { authRequired: false }, { get() { - const mountOAuthServices = (): object => { - const oAuthServicesEnabled = ServiceConfiguration.configurations.find({}, { fields: { secret: 0 } }).fetch(); + const oAuthServicesEnabled = ServiceConfiguration.configurations.find({}, { fields: { secret: 0 } }).fetch(); - return oAuthServicesEnabled.map((service) => { + return API.v1.success({ + services: oAuthServicesEnabled.map((service) => { if (!isOauthCustomConfiguration(service)) { return service; } @@ -109,32 +70,25 @@ API.v1.addRoute('settings.oauth', { authRequired: false }, { buttonLabelColor: service.buttonLabelColor || '', custom: false, }; - }); - }; - - return API.v1.success({ - services: mountOAuthServices(), + }), }); }, }); API.v1.addRoute('settings.addCustomOAuth', { authRequired: true, twoFactorRequired: true }, { - post() { - if (!this.requestParams().name || !this.requestParams().name.trim()) { + async post() { + if (!this.bodyParams.name || !this.bodyParams.name.trim()) { throw new Meteor.Error('error-name-param-not-provided', 'The parameter "name" is required'); } - Meteor.runAsUser(this.userId, () => { - Meteor.call('addOAuthService', this.requestParams().name, this.userId); - }); - + await Meteor.call('addOAuthService', this.bodyParams.name, this.userId); return API.v1.success(); }, }); API.v1.addRoute('settings', { authRequired: true }, { - get() { + async get() { const { offset, count } = this.getPaginationItems(); const { sort, fields, query } = this.parseJsonQuery(); @@ -148,7 +102,7 @@ API.v1.addRoute('settings', { authRequired: true }, { ourQuery = Object.assign({}, query, ourQuery); - const settings = Promise.await(fetchSettings(ourQuery, sort, offset, count, fields)); + const settings = await fetchSettings(ourQuery, sort, offset, count, fields); return API.v1.success({ settings, @@ -160,11 +114,11 @@ API.v1.addRoute('settings', { authRequired: true }, { }); API.v1.addRoute('settings/:_id', { authRequired: true }, { - get() { + async get() { if (!hasPermission(this.userId, 'view-privileged-setting')) { return API.v1.unauthorized(); } - const setting = Promise.await(Settings.findOneNotHiddenById(this.urlParams._id)); + const setting = await Settings.findOneNotHiddenById(this.urlParams._id); if (!setting) { return API.v1.failure(); } @@ -172,35 +126,36 @@ API.v1.addRoute('settings/:_id', { authRequired: true }, { }, post: { twoFactorRequired: true, - action(this: any): void { + async action(): Promise['post']> { if (!hasPermission(this.userId, 'edit-privileged-setting')) { return API.v1.unauthorized(); } + if (typeof this.urlParams._id !== 'string') { + throw new Meteor.Error('error-id-param-not-provided', 'The parameter "id" is required'); + } + // allow special handling of particular setting types - const setting = Promise.await(Settings.findOneNotHiddenById(this.urlParams._id)); + const setting = await Settings.findOneNotHiddenById(this.urlParams._id); if (!setting) { return API.v1.failure(); } - if (isSettingAction(setting) && this.bodyParams && this.bodyParams.execute) { + if (isSettingAction(setting) && isSettingsUpdatePropsActions(this.bodyParams) && this.bodyParams.execute) { // execute the configured method Meteor.call(setting.value); return API.v1.success(); } - if (isSettingColor(setting) && this.bodyParams && this.bodyParams.editor && this.bodyParams.value) { + if (isSettingColor(setting) && isSettingsUpdatePropsColor(this.bodyParams)) { Settings.updateOptionsById(this.urlParams._id, { editor: this.bodyParams.editor }); Settings.updateValueNotHiddenById(this.urlParams._id, this.bodyParams.value); return API.v1.success(); } - check(this.bodyParams, { - value: Match.Any, - }); - if (Promise.await(Settings.updateValueNotHiddenById(this.urlParams._id, this.bodyParams.value))) { - const s = Promise.await(Settings.findOneNotHiddenById(this.urlParams._id)); + if (isSettingsUpdatePropDefault(this.bodyParams) && await Settings.updateValueNotHiddenById(this.urlParams._id, this.bodyParams.value)) { + const s = await Settings.findOneNotHiddenById(this.urlParams._id); if (!s) { return API.v1.failure(); } diff --git a/app/api/server/v1/teams.ts b/app/api/server/v1/teams.ts index e2a24d08413f1..dab3ac1e0a82f 100644 --- a/app/api/server/v1/teams.ts +++ b/app/api/server/v1/teams.ts @@ -9,13 +9,22 @@ import { hasAtLeastOnePermission, hasPermission } from '../../../authorization/s import { Users } from '../../../models/server'; import { removeUserFromRoom } from '../../../lib/server/functions/removeUserFromRoom'; import { IUser } from '../../../../definition/IUser'; +import { isTeamPropsWithTeamName, isTeamPropsWithTeamId } from '../../../../definition/rest/v1/teams'; +import { isTeamsConvertToChannelProps } from '../../../../definition/rest/v1/teams/TeamsConvertToChannelProps'; +import { isTeamsRemoveRoomProps } from '../../../../definition/rest/v1/teams/TeamsRemoveRoomProps'; +import { isTeamsUpdateMemberProps } from '../../../../definition/rest/v1/teams/TeamsUpdateMemberProps'; +import { isTeamsRemoveMemberProps } from '../../../../definition/rest/v1/teams/TeamsRemoveMemberProps'; +import { isTeamsAddMembersProps } from '../../../../definition/rest/v1/teams/TeamsAddMembersProps'; +import { isTeamsDeleteProps } from '../../../../definition/rest/v1/teams/TeamsDeleteProps'; +import { isTeamsLeaveProps } from '../../../../definition/rest/v1/teams/TeamsLeaveProps'; +import { isTeamsUpdateProps } from '../../../../definition/rest/v1/teams/TeamsUpdateProps'; API.v1.addRoute('teams.list', { authRequired: true }, { - get() { + async get() { const { offset, count } = this.getPaginationItems(); const { sort, query } = this.parseJsonQuery(); - const { records, total } = Promise.await(Team.list(this.userId, { offset, count }, { sort, query })); + const { records, total } = await Team.list(this.userId, { offset, count }, { sort, query }); return API.v1.success({ teams: records, @@ -27,14 +36,14 @@ API.v1.addRoute('teams.list', { authRequired: true }, { }); API.v1.addRoute('teams.listAll', { authRequired: true }, { - get() { + async get() { if (!hasPermission(this.userId, 'view-all-teams')) { return API.v1.unauthorized(); } - const { offset, count } = this.getPaginationItems(); + const { offset, count } = this.getPaginationItems() as { offset: number; count: number }; - const { records, total } = Promise.await(Team.listAll({ offset, count })); + const { records, total } = await Team.listAll({ offset, count }); return API.v1.success({ teams: records, @@ -46,7 +55,7 @@ API.v1.addRoute('teams.listAll', { authRequired: true }, { }); API.v1.addRoute('teams.create', { authRequired: true }, { - post() { + async post() { if (!hasPermission(this.userId, 'create-team')) { return API.v1.unauthorized(); } @@ -56,7 +65,7 @@ API.v1.addRoute('teams.create', { authRequired: true }, { return API.v1.failure('Body param "name" is required'); } - const team = Promise.await(Team.create(this.userId, { + const team = await Team.create(this.userId, { team: { name, type, @@ -64,26 +73,27 @@ API.v1.addRoute('teams.create', { authRequired: true }, { room, members, owner, - })); + }); return API.v1.success({ team }); }, }); API.v1.addRoute('teams.convertToChannel', { authRequired: true }, { - post() { - check(this.bodyParams, Match.ObjectIncluding({ - teamId: Match.Maybe(String), - teamName: Match.Maybe(String), - roomsToRemove: Match.Maybe([String]), - })); - const { roomsToRemove, teamId, teamName } = this.bodyParams; - - if (!teamId && !teamName) { - return API.v1.failure('missing-teamId-or-teamName'); + async post() { + if (!isTeamsConvertToChannelProps(this.bodyParams)) { + return API.v1.failure('invalid-body-params', isTeamsConvertToChannelProps.errors?.map((e) => e.message).join('\n ')); } - const team = teamId ? Promise.await(Team.getOneById(teamId)) : Promise.await(Team.getOneByName(teamName)); + const { bodyParams } = this; + + const { roomsToRemove = [] } = bodyParams; + + const team = await ( + (isTeamPropsWithTeamId(bodyParams) && Team.getOneById(bodyParams.teamId)) + || (isTeamPropsWithTeamName(bodyParams) && Team.getOneByName(bodyParams.teamName)) + ); + if (!team) { return API.v1.failure('team-does-not-exist'); } @@ -92,7 +102,7 @@ API.v1.addRoute('teams.convertToChannel', { authRequired: true }, { return API.v1.unauthorized(); } - const rooms: string[] = Promise.await(Team.getMatchingTeamRooms(team._id, roomsToRemove)); + const rooms = await Team.getMatchingTeamRooms(team._id, roomsToRemove); if (rooms.length) { rooms.forEach((room) => { @@ -100,7 +110,7 @@ API.v1.addRoute('teams.convertToChannel', { authRequired: true }, { }); } - Promise.all([ + await Promise.all([ Team.unsetTeamIdOfRooms(team._id), Team.removeAllMembersFromTeam(team._id), Team.deleteById(team._id), @@ -111,14 +121,14 @@ API.v1.addRoute('teams.convertToChannel', { authRequired: true }, { }); API.v1.addRoute('teams.addRooms', { authRequired: true }, { - post() { + async post() { const { rooms, teamId, teamName } = this.bodyParams; if (!teamId && !teamName) { return API.v1.failure('missing-teamId-or-teamName'); } - const team = teamId ? Promise.await(Team.getOneById(teamId)) : Promise.await(Team.getOneByName(teamName)); + const team = await (teamId ? Team.getOneById(teamId) : Team.getOneByName(teamName)); if (!team) { return API.v1.failure('team-does-not-exist'); } @@ -127,17 +137,20 @@ API.v1.addRoute('teams.addRooms', { authRequired: true }, { return API.v1.unauthorized('error-no-permission-team-channel'); } - const validRooms = Promise.await(Team.addRooms(this.userId, rooms, team._id)); + const validRooms = await Team.addRooms(this.userId, rooms, team._id); return API.v1.success({ rooms: validRooms }); }, }); API.v1.addRoute('teams.removeRoom', { authRequired: true }, { - post() { + async post() { + if (!isTeamsRemoveRoomProps(this.bodyParams)) { + return API.v1.failure('body-params-invalid', isTeamsRemoveRoomProps.errors?.map((error) => error.message).join('\n ')); + } const { roomId, teamId, teamName } = this.bodyParams; - const team = teamId ? Promise.await(Team.getOneById(teamId)) : Promise.await(Team.getOneByName(teamName)); + const team = await (teamId ? Team.getOneById(teamId) : Team.getOneByName(teamName)); if (!team) { return API.v1.failure('team-does-not-exist'); } @@ -148,17 +161,17 @@ API.v1.addRoute('teams.removeRoom', { authRequired: true }, { const canRemoveAny = !!hasPermission(this.userId, 'view-all-team-channels', team.roomId); - const room = Promise.await(Team.removeRoom(this.userId, roomId, team._id, canRemoveAny)); + const room = await Team.removeRoom(this.userId, roomId, team._id, canRemoveAny); return API.v1.success({ room }); }, }); API.v1.addRoute('teams.updateRoom', { authRequired: true }, { - post() { + async post() { const { roomId, isDefault } = this.bodyParams; - const team = Promise.await(Team.getOneByRoomId(roomId)); + const team = await Team.getOneByRoomId(roomId); if (!team) { return API.v1.failure('team-does-not-exist'); } @@ -168,18 +181,18 @@ API.v1.addRoute('teams.updateRoom', { authRequired: true }, { } const canUpdateAny = !!hasPermission(this.userId, 'view-all-team-channels', team.roomId); - const room = Promise.await(Team.updateRoom(this.userId, roomId, isDefault, canUpdateAny)); + const room = await Team.updateRoom(this.userId, roomId, isDefault, canUpdateAny); return API.v1.success({ room }); }, }); API.v1.addRoute('teams.listRooms', { authRequired: true }, { - get() { + async get() { const { teamId, teamName, filter, type } = this.queryParams; const { offset, count } = this.getPaginationItems(); - const team = teamId ? Promise.await(Team.getOneById(teamId)) : Promise.await(Team.getOneByName(teamName)); + const team = await (teamId ? Team.getOneById(teamId) : Team.getOneByName(teamName)); if (!team) { return API.v1.failure('team-does-not-exist'); } @@ -198,7 +211,7 @@ API.v1.addRoute('teams.listRooms', { authRequired: true }, { allowPrivateTeam, }; - const { records, total } = Promise.await(Team.listRooms(this.userId, team._id, listFilter, { offset, count })); + const { records, total } = await Team.listRooms(this.userId, team._id, listFilter, { offset, count }); return API.v1.success({ rooms: records, @@ -210,11 +223,17 @@ API.v1.addRoute('teams.listRooms', { authRequired: true }, { }); API.v1.addRoute('teams.listRoomsOfUser', { authRequired: true }, { - get() { + async get() { const { offset, count } = this.getPaginationItems(); const { teamId, teamName, userId, canUserDelete = false } = this.queryParams; - const team = teamId ? Promise.await(Team.getOneById(teamId)) : Promise.await(Team.getOneByName(teamName)); + + if (!teamId && !teamName) { + return API.v1.failure('missing-teamId-or-teamName'); + } + + const team = await (teamId ? Team.getOneById(teamId) : Team.getOneByName(teamName!)); + if (!team) { return API.v1.failure('team-does-not-exist'); } @@ -225,7 +244,7 @@ API.v1.addRoute('teams.listRoomsOfUser', { authRequired: true }, { return API.v1.unauthorized(); } - const { records, total } = Promise.await(Team.listRoomsOfUser(this.userId, team._id, userId, allowPrivateTeam, canUserDelete, { offset, count })); + const { records, total } = await Team.listRoomsOfUser(this.userId, team._id, userId, allowPrivateTeam, canUserDelete, { offset, count }); return API.v1.success({ rooms: records, @@ -237,7 +256,7 @@ API.v1.addRoute('teams.listRoomsOfUser', { authRequired: true }, { }); API.v1.addRoute('teams.members', { authRequired: true }, { - get() { + async get() { const { offset, count } = this.getPaginationItems(); check(this.queryParams, Match.ObjectIncluding({ @@ -253,7 +272,7 @@ API.v1.addRoute('teams.members', { authRequired: true }, { return API.v1.failure('missing-teamId-or-teamName'); } - const team = teamId ? Promise.await(Team.getOneById(teamId)) : Promise.await(Team.getOneByName(teamName)); + const team = await (teamId ? Team.getOneById(teamId) : Team.getOneByName(teamName)); if (!team) { return API.v1.failure('team-does-not-exist'); } @@ -265,7 +284,7 @@ API.v1.addRoute('teams.members', { authRequired: true }, { status: status ? { $in: status } : undefined, } as FilterQuery; - const { records, total } = Promise.await(Team.members(this.userId, team._id, canSeeAllMembers, { offset, count }, query)); + const { records, total } = await Team.members(this.userId, team._id, canSeeAllMembers, { offset, count }, query); return API.v1.success({ members: records, @@ -277,10 +296,19 @@ API.v1.addRoute('teams.members', { authRequired: true }, { }); API.v1.addRoute('teams.addMembers', { authRequired: true }, { - post() { - const { teamId, teamName, members } = this.bodyParams; + async post() { + if (!isTeamsAddMembersProps(this.bodyParams)) { + return API.v1.failure('invalid-params'); + } + + const { bodyParams } = this; + const { members } = bodyParams; + + const team = await ( + (isTeamPropsWithTeamId(bodyParams) && Team.getOneById(bodyParams.teamId)) + || (isTeamPropsWithTeamName(bodyParams) && Team.getOneByName(bodyParams.teamName)) + ); - const team = teamId ? Promise.await(Team.getOneById(teamId)) : Promise.await(Team.getOneByName(teamName)); if (!team) { return API.v1.failure('team-does-not-exist'); } @@ -289,17 +317,25 @@ API.v1.addRoute('teams.addMembers', { authRequired: true }, { return API.v1.unauthorized(); } - Promise.await(Team.addMembers(this.userId, team._id, members)); + await Team.addMembers(this.userId, team._id, members); return API.v1.success(); }, }); API.v1.addRoute('teams.updateMember', { authRequired: true }, { - post() { - const { teamId, teamName, member } = this.bodyParams; + async post() { + if (!isTeamsUpdateMemberProps(this.bodyParams)) { + return API.v1.failure('invalid-params', isTeamsUpdateMemberProps.errors?.map((e) => e.message).join('\n ')); + } - const team = teamId ? Promise.await(Team.getOneById(teamId)) : Promise.await(Team.getOneByName(teamName)); + const { bodyParams } = this; + const { member } = bodyParams; + + const team = await ( + (isTeamPropsWithTeamId(bodyParams) && Team.getOneById(bodyParams.teamId)) + || (isTeamPropsWithTeamName(bodyParams) && Team.getOneByName(bodyParams.teamName)) + ); if (!team) { return API.v1.failure('team-does-not-exist'); } @@ -308,17 +344,26 @@ API.v1.addRoute('teams.updateMember', { authRequired: true }, { return API.v1.unauthorized(); } - Promise.await(Team.updateMember(team._id, member)); + await Team.updateMember(team._id, member); return API.v1.success(); }, }); API.v1.addRoute('teams.removeMember', { authRequired: true }, { - post() { - const { teamId, teamName, userId, rooms } = this.bodyParams; + async post() { + if (!isTeamsRemoveMemberProps(this.bodyParams)) { + return API.v1.failure('invalid-params', isTeamsRemoveMemberProps.errors?.map((e) => e.message).join('\n ')); + } + + const { bodyParams } = this; + const { userId, rooms } = bodyParams; + + const team = await ( + (isTeamPropsWithTeamId(bodyParams) && Team.getOneById(bodyParams.teamId)) + || (isTeamPropsWithTeamName(bodyParams) && Team.getOneByName(bodyParams.teamName)) + ); - const team = teamId ? Promise.await(Team.getOneById(teamId)) : Promise.await(Team.getOneByName(teamName)); if (!team) { return API.v1.failure('team-does-not-exist'); } @@ -332,12 +377,12 @@ API.v1.addRoute('teams.removeMember', { authRequired: true }, { return API.v1.failure('invalid-user'); } - if (!Promise.await(Team.removeMembers(this.userId, team._id, [{ userId }]))) { + if (!await Team.removeMembers(this.userId, team._id, [{ userId }])) { return API.v1.failure(); } if (rooms?.length) { - const roomsFromTeam: string[] = Promise.await(Team.getMatchingTeamRooms(team._id, rooms)); + const roomsFromTeam: string[] = await Team.getMatchingTeamRooms(team._id, rooms); roomsFromTeam.forEach((rid) => { removeUserFromRoom(rid, user, { @@ -350,20 +395,30 @@ API.v1.addRoute('teams.removeMember', { authRequired: true }, { }); API.v1.addRoute('teams.leave', { authRequired: true }, { - post() { - const { teamId, teamName, rooms } = this.bodyParams; + async post() { + if (!isTeamsLeaveProps(this.bodyParams)) { + return API.v1.failure('invalid-params', isTeamsLeaveProps.errors?.map((e) => e.message).join('\n ')); + } + + const { bodyParams } = this; + + const { rooms = [] } = bodyParams; + + const team = await ( + (isTeamPropsWithTeamId(bodyParams) && Team.getOneById(bodyParams.teamId)) + || (isTeamPropsWithTeamName(bodyParams) && Team.getOneByName(bodyParams.teamName)) + ); - const team = teamId ? Promise.await(Team.getOneById(teamId)) : Promise.await(Team.getOneByName(teamName)); if (!team) { return API.v1.failure('team-does-not-exist'); } - Promise.await(Team.removeMembers(this.userId, team._id, [{ + await Team.removeMembers(this.userId, team._id, [{ userId: this.userId, - }])); + }]); - if (rooms?.length) { - const roomsFromTeam: string[] = Promise.await(Team.getMatchingTeamRooms(team._id, rooms)); + if (rooms.length) { + const roomsFromTeam: string[] = await Team.getMatchingTeamRooms(team._id, rooms); roomsFromTeam.forEach((rid) => { removeUserFromRoom(rid, this.user); @@ -375,16 +430,16 @@ API.v1.addRoute('teams.leave', { authRequired: true }, { }); API.v1.addRoute('teams.info', { authRequired: true }, { - get() { + async get() { const { teamId, teamName } = this.queryParams; if (!teamId && !teamName) { return API.v1.failure('Provide either the "teamId" or "teamName"'); } - const teamInfo = teamId - ? Promise.await(Team.getInfoById(teamId)) - : Promise.await(Team.getInfoByName(teamName)); + const teamInfo = await (teamId + ? Team.getInfoById(teamId) + : Team.getInfoByName(teamName)); if (!teamInfo) { return API.v1.failure('Team not found'); @@ -395,18 +450,19 @@ API.v1.addRoute('teams.info', { authRequired: true }, { }); API.v1.addRoute('teams.delete', { authRequired: true }, { - post() { - const { teamId, teamName, roomsToRemove } = this.bodyParams; + async post() { + const { bodyParams } = this; + const { roomsToRemove = [] } = this.bodyParams; - if (!teamId && !teamName) { - return API.v1.failure('Provide either the "teamId" or "teamName"'); + if (!isTeamsDeleteProps(bodyParams)) { + return API.v1.failure('invalid-params', isTeamsDeleteProps.errors?.map((e) => e.message).join('\n ')); } - if (roomsToRemove && !Array.isArray(roomsToRemove)) { - return API.v1.failure('The list of rooms to remove is invalid.'); - } + const team = await ( + (isTeamPropsWithTeamId(bodyParams) && Team.getOneById(bodyParams.teamId)) + || (isTeamPropsWithTeamName(bodyParams) && Team.getOneByName(bodyParams.teamName)) + ); - const team = teamId ? Promise.await(Team.getOneById(teamId)) : Promise.await(Team.getOneByName(teamName)); if (!team) { return API.v1.failure('Team not found.'); } @@ -415,7 +471,7 @@ API.v1.addRoute('teams.delete', { authRequired: true }, { return API.v1.unauthorized(); } - const rooms: string[] = Promise.await(Team.getMatchingTeamRooms(team._id, roomsToRemove)); + const rooms: string[] = await Team.getMatchingTeamRooms(team._id, roomsToRemove); // Remove the team's main room Meteor.call('eraseRoom', team.roomId); @@ -428,41 +484,42 @@ API.v1.addRoute('teams.delete', { authRequired: true }, { } // Move every other room back to the workspace - Promise.await(Team.unsetTeamIdOfRooms(team._id)); + await Team.unsetTeamIdOfRooms(team._id); // Delete all team memberships - Team.removeAllMembersFromTeam(teamId); + Team.removeAllMembersFromTeam(team._id); // And finally delete the team itself - Promise.await(Team.deleteById(team._id)); + await Team.deleteById(team._id); return API.v1.success(); }, }); API.v1.addRoute('teams.autocomplete', { authRequired: true }, { - get() { + async get() { const { name } = this.queryParams; - const teams = Promise.await(Team.autocomplete(this.userId, name)); + const teams = await Team.autocomplete(this.userId, name); return API.v1.success({ teams }); }, }); API.v1.addRoute('teams.update', { authRequired: true }, { - post() { - check(this.bodyParams, { - teamId: String, - data: { - name: Match.Maybe(String), - type: Match.Maybe(Number), - }, - }); + async post() { + const { bodyParams } = this; + if (!isTeamsUpdateProps(bodyParams)) { + return API.v1.failure('invalid-params', isTeamsUpdateProps.errors?.map((e) => e.message).join('\n ')); + } + + const { data } = bodyParams; - const { teamId, data } = this.bodyParams; + const team = await ( + (isTeamPropsWithTeamId(bodyParams) && Team.getOneById(bodyParams.teamId)) + || (isTeamPropsWithTeamName(bodyParams) && Team.getOneByName(bodyParams.teamName)) + ); - const team = teamId && Promise.await(Team.getOneById(teamId)); if (!team) { return API.v1.failure('team-does-not-exist'); } @@ -471,7 +528,7 @@ API.v1.addRoute('teams.update', { authRequired: true }, { return API.v1.unauthorized(); } - Promise.await(Team.update(this.userId, teamId, { name: data.name, type: data.type })); + await Team.update(this.userId, team._id, data); return API.v1.success(); }, diff --git a/app/authorization/server/functions/getUsersInRole.ts b/app/authorization/server/functions/getUsersInRole.ts index 8a8bcadf3dba2..740431af3f068 100644 --- a/app/authorization/server/functions/getUsersInRole.ts +++ b/app/authorization/server/functions/getUsersInRole.ts @@ -9,6 +9,6 @@ export function getUsersInRole(name: IRole['name'], scope?: string): Promise>): Promise>; -export function getUsersInRole

    (name: IRole['name'], scope: string | undefined, options: FindOneOptions

    ): Promise>; +export function getUsersInRole

    (name: IRole['name'], scope: string | undefined, options: FindOneOptions

    ): Promise>; -export function getUsersInRole

    (name: IRole['name'], scope: string | undefined, options?: any | undefined): Promise> { return Roles.findUsersInRole(name, scope, options); } +export function getUsersInRole

    (name: IRole['name'], scope: string | undefined, options?: any | undefined): Promise> { return Roles.findUsersInRole(name, scope, options); } diff --git a/app/models/server/raw/BaseRaw.ts b/app/models/server/raw/BaseRaw.ts index 500fe55d6e1a0..1385bf890c6f4 100644 --- a/app/models/server/raw/BaseRaw.ts +++ b/app/models/server/raw/BaseRaw.ts @@ -166,7 +166,7 @@ export class BaseRaw = undefined> implements IBase find(query: FilterQuery, options: WithoutProjection>): Cursor>; - find

    (query: FilterQuery, options: FindOneOptions

    ): Cursor

    ; + find

    (query: FilterQuery, options: FindOneOptions

    ): Cursor

    ; find

    (query: FilterQuery | undefined = {}, options?: any): Cursor

    | Cursor { const optionsDef = this.doNotMixInclusionAndExclusionFields(options); diff --git a/app/models/server/raw/Permissions.ts b/app/models/server/raw/Permissions.ts index 0c5ae1f8533e4..1b0bacacc53bb 100644 --- a/app/models/server/raw/Permissions.ts +++ b/app/models/server/raw/Permissions.ts @@ -30,6 +30,10 @@ export class PermissionsRaw extends BaseRaw { await this.update({ _id: permission, roles: { $ne: role } }, { $addToSet: { roles: role } }); } + async setRoles(permission: string, roles: string[]): Promise { + await this.update({ _id: permission }, { $set: { roles } }); + } + async removeRole(permission: string, role: string): Promise { await this.update({ _id: permission, roles: role }, { $pull: { roles: role } }); } diff --git a/app/models/server/raw/Roles.ts b/app/models/server/raw/Roles.ts index db7913eb7e2a4..c858d2445ce15 100644 --- a/app/models/server/raw/Roles.ts +++ b/app/models/server/raw/Roles.ts @@ -17,12 +17,12 @@ export class RolesRaw extends BaseRaw { } - findByUpdatedDate

    (updatedAfterDate: Date, options: FindOneOptions

    ): Cursor

    | Cursor { + findByUpdatedDate(updatedAfterDate: Date, options?: FindOneOptions): Cursor { const query = { _updatedAt: { $gte: new Date(updatedAfterDate) }, }; - return this.find(query, options); + return options ? this.find(query, options) : this.find(query); } @@ -135,7 +135,7 @@ export class RolesRaw extends BaseRaw { return this.findOne(query, options); } - updateById(_id: IRole['_id'], name: IRole['name'], scope: IRole['scope'], description: IRole['description'], mandatory2fa: IRole['mandatory2fa']): Promise { + updateById(_id: IRole['_id'], name: IRole['name'], scope: IRole['scope'], description: IRole['description'] = '', mandatory2fa: IRole['mandatory2fa'] = false): Promise { const queryData = { name, scope, @@ -151,7 +151,7 @@ export class RolesRaw extends BaseRaw { findUsersInRole(name: IRole['name'], scope: string | undefined, options: WithoutProjection>): Promise>; - findUsersInRole

    (name: IRole['name'], scope: string | undefined, options: FindOneOptions

    ): Promise>; + findUsersInRole

    (name: IRole['name'], scope: string | undefined, options: FindOneOptions

    ): Promise>; async findUsersInRole

    (name: IRole['name'], scope: string | undefined, options?: any | undefined): Promise | Cursor

    > { const role = await this.findOne({ name }, { scope: 1 } as FindOneOptions); diff --git a/app/models/server/raw/Settings.ts b/app/models/server/raw/Settings.ts index 2d3a11b2ccea7..7e84d539b05eb 100644 --- a/app/models/server/raw/Settings.ts +++ b/app/models/server/raw/Settings.ts @@ -116,7 +116,7 @@ export class SettingsRaw extends BaseRaw { filter._id = { $in: ids }; } - return this.find(filter, { fields: { _id: 1, value: 1, editor: 1, enterprise: 1, invalidValue: 1, modules: 1, requiredOnWizard: 1 } }) as any; + return this.find(filter, { projection: { _id: 1, value: 1, editor: 1, enterprise: 1, invalidValue: 1, modules: 1, requiredOnWizard: 1 } }); } findSetupWizardSettings(): Cursor { diff --git a/client/contexts/ServerContext/ServerContext.ts b/client/contexts/ServerContext/ServerContext.ts index d791fea0d2d43..15b8c50f97f51 100644 --- a/client/contexts/ServerContext/ServerContext.ts +++ b/client/contexts/ServerContext/ServerContext.ts @@ -2,7 +2,7 @@ import { createContext, useCallback, useContext, useMemo } from 'react'; import { IServerInfo } from '../../../definition/IServerInfo'; import type { Serialized } from '../../../definition/Serialized'; -import type { PathFor, Params, Return, Method } from './endpoints'; +import type { PathFor, Params, Return, Method } from '../../../definition/rest'; import { ServerMethodFunction, ServerMethodName, diff --git a/client/contexts/ServerContext/endpoints.ts b/client/contexts/ServerContext/endpoints.ts deleted file mode 100644 index 0a57ef4479d02..0000000000000 --- a/client/contexts/ServerContext/endpoints.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { ExtractKeys, ValueOf } from '../../../definition/utils'; -import type { EngagementDashboardEndpoints } from '../../../ee/client/contexts/ServerContext/endpoints/v1/engagementDashboard'; -import type { AppsEndpoints } from './endpoints/apps'; -import type { ChannelsEndpoints } from './endpoints/v1/channels'; -import type { ChatEndpoints } from './endpoints/v1/chat'; -import type { CloudEndpoints } from './endpoints/v1/cloud'; -import type { CustomUserStatusEndpoints } from './endpoints/v1/customUserStatus'; -import type { DmEndpoints } from './endpoints/v1/dm'; -import type { DnsEndpoints } from './endpoints/v1/dns'; -import type { EmojiCustomEndpoints } from './endpoints/v1/emojiCustom'; -import type { GroupsEndpoints } from './endpoints/v1/groups'; -import type { ImEndpoints } from './endpoints/v1/im'; -import type { LDAPEndpoints } from './endpoints/v1/ldap'; -import type { LicensesEndpoints } from './endpoints/v1/licenses'; -import type { MiscEndpoints } from './endpoints/v1/misc'; -import type { OmnichannelEndpoints } from './endpoints/v1/omnichannel'; -import type { RoomsEndpoints } from './endpoints/v1/rooms'; -import type { StatisticsEndpoints } from './endpoints/v1/statistics'; -import type { TeamsEndpoints } from './endpoints/v1/teams'; -import type { UsersEndpoints } from './endpoints/v1/users'; - -type Endpoints = ChatEndpoints & - ChannelsEndpoints & - CloudEndpoints & - CustomUserStatusEndpoints & - DmEndpoints & - DnsEndpoints & - EmojiCustomEndpoints & - GroupsEndpoints & - ImEndpoints & - LDAPEndpoints & - RoomsEndpoints & - TeamsEndpoints & - UsersEndpoints & - EngagementDashboardEndpoints & - AppsEndpoints & - OmnichannelEndpoints & - StatisticsEndpoints & - LicensesEndpoints & - MiscEndpoints; - -type Endpoint = UnionizeEndpoints; - -type UnionizeEndpoints = ValueOf< - { - [P in keyof EE]: UnionizeMethods; - } ->; - -type ExtractOperations = ExtractKeys any>; - -type UnionizeMethods = ValueOf< - { - [M in keyof OO as ExtractOperations]: ( - method: M, - path: OO extends { path: string } ? OO['path'] : P, - ...params: Parameters any>> - ) => ReturnType any>>; - } ->; - -export type Method = Parameters[0]; -export type Path = Parameters[1]; - -export type MethodFor

    = P extends any - ? Parameters any>>[0] - : never; -export type PathFor = M extends any - ? Parameters any>>[1] - : never; - -type Operation> = M extends any - ? P extends any - ? Extract any> - : never - : never; - -type ExtractParams = Q extends [any, any] - ? [undefined?] - : Q extends [any, any, any, ...any[]] - ? [Q[2]] - : never; - -export type Params> = ExtractParams< - Parameters> ->; -export type Return> = ReturnType>; diff --git a/client/contexts/ServerContext/endpoints/v1/dm.ts b/client/contexts/ServerContext/endpoints/v1/dm.ts deleted file mode 100644 index 0b20aad1819cb..0000000000000 --- a/client/contexts/ServerContext/endpoints/v1/dm.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { IRoom } from '../../../../../definition/IRoom'; -import type { IUser } from '../../../../../definition/IUser'; - -export type DmEndpoints = { - 'dm.create': { - POST: ( - params: ( - | { - username: Exclude; - } - | { - usernames: string; - } - ) & { - excludeSelf?: boolean; - }, - ) => { - room: IRoom & { rid: IRoom['_id'] }; - }; - }; -}; diff --git a/client/contexts/ServerContext/endpoints/v1/teams.ts b/client/contexts/ServerContext/endpoints/v1/teams.ts deleted file mode 100644 index 70f8a7c10b12d..0000000000000 --- a/client/contexts/ServerContext/endpoints/v1/teams.ts +++ /dev/null @@ -1,76 +0,0 @@ -import type { IRoom } from '../../../../../definition/IRoom'; -import type { IRecordsWithTotal, ITeam } from '../../../../../definition/ITeam'; -import type { IUser } from '../../../../../definition/IUser'; - -export type TeamsEndpoints = { - 'teams.addRooms': { - POST: (params: { rooms: IRoom['_id'][]; teamId: string }) => void; - }; - 'teams.info': { - GET: (params: { teamId: IRoom['teamId'] }) => { teamInfo: ITeam }; - }; - 'teams.listRooms': { - GET: (params: { - teamId: ITeam['_id']; - offset?: number; - count?: number; - filter: string; - type: string; - }) => Omit, 'records'> & { - count: number; - offset: number; - rooms: IRecordsWithTotal['records']; - }; - }; - 'teams.listRoomsOfUser': { - GET: (params: { - teamId: ITeam['_id']; - teamName?: string; - userId?: string; - canUserDelete?: boolean; - offset?: number; - count?: number; - }) => Omit, 'records'> & { - count: number; - offset: number; - rooms: IRecordsWithTotal['records']; - }; - }; - 'teams.create': { - POST: (params: { - name: ITeam['name']; - type?: ITeam['type']; - members?: IUser['_id'][]; - room: { - id?: string; - name?: IRoom['name']; - members?: IUser['_id'][]; - readOnly?: boolean; - extraData?: { - teamId?: string; - teamMain?: boolean; - } & { [key: string]: string | boolean }; - options?: { - nameValidationRegex?: string; - creator: string; - subscriptionExtra?: { - open: boolean; - ls: Date; - prid: IRoom['_id']; - }; - } & { - [key: string]: - | string - | { - open: boolean; - ls: Date; - prid: IRoom['_id']; - }; - }; - }; - owner?: IUser['_id']; - }) => { - team: ITeam; - }; - }; -}; diff --git a/client/contexts/ServerContext/index.ts b/client/contexts/ServerContext/index.ts index a2807467fddf4..cd41c0b16c179 100644 --- a/client/contexts/ServerContext/index.ts +++ b/client/contexts/ServerContext/index.ts @@ -1,3 +1,2 @@ export * from './ServerContext'; -export * from './endpoints'; export * from './methods'; diff --git a/client/hooks/useEndpointAction.ts b/client/hooks/useEndpointAction.ts index 65a7f2ccaed32..0deb6cdd7d7c4 100644 --- a/client/hooks/useEndpointAction.ts +++ b/client/hooks/useEndpointAction.ts @@ -1,8 +1,8 @@ import { useCallback } from 'react'; import { Serialized } from '../../definition/Serialized'; +import { Method, Params, PathFor, Return } from '../../definition/rest'; import { useEndpoint } from '../contexts/ServerContext'; -import { Method, Params, PathFor, Return } from '../contexts/ServerContext/endpoints'; import { useToastMessageDispatch } from '../contexts/ToastMessagesContext'; export const useEndpointAction = >( diff --git a/client/hooks/useEndpointActionExperimental.ts b/client/hooks/useEndpointActionExperimental.ts index b6cb286f46aef..0e82f6d428306 100644 --- a/client/hooks/useEndpointActionExperimental.ts +++ b/client/hooks/useEndpointActionExperimental.ts @@ -1,8 +1,8 @@ import { useCallback } from 'react'; import { Serialized } from '../../definition/Serialized'; +import { Method, Params, PathFor, Return } from '../../definition/rest'; import { useEndpoint } from '../contexts/ServerContext'; -import { Method, Params, PathFor, Return } from '../contexts/ServerContext/endpoints'; import { useToastMessageDispatch } from '../contexts/ToastMessagesContext'; export const useEndpointActionExperimental = >( diff --git a/client/hooks/useEndpointData.ts b/client/hooks/useEndpointData.ts index 6412cd9d7f3ba..1e8f5ebc880cd 100644 --- a/client/hooks/useEndpointData.ts +++ b/client/hooks/useEndpointData.ts @@ -1,8 +1,8 @@ import { useCallback, useEffect } from 'react'; import { Serialized } from '../../definition/Serialized'; +import { Params, PathFor, Return } from '../../definition/rest'; import { useEndpoint } from '../contexts/ServerContext'; -import { Params, PathFor, Return } from '../contexts/ServerContext/endpoints'; import { useToastMessageDispatch } from '../contexts/ToastMessagesContext'; import { AsyncState, useAsyncState } from './useAsyncState'; diff --git a/client/providers/ServerProvider.tsx b/client/providers/ServerProvider.tsx index 89b1c28b9599c..43f600d2262eb 100644 --- a/client/providers/ServerProvider.tsx +++ b/client/providers/ServerProvider.tsx @@ -3,11 +3,8 @@ import React, { FC } from 'react'; import { Info as info, APIClient } from '../../app/utils/client'; import { Serialized } from '../../definition/Serialized'; +import { Method, Params, Return, PathFor } from '../../definition/rest'; import { - Method, - Params, - PathFor, - Return, ServerContext, ServerMethodName, ServerMethodParameters, diff --git a/client/views/teams/contextualBar/channels/hooks/useTeamsChannelList.ts b/client/views/teams/contextualBar/channels/hooks/useTeamsChannelList.ts index 6f144e0400a8f..c310c75b73ad7 100644 --- a/client/views/teams/contextualBar/channels/hooks/useTeamsChannelList.ts +++ b/client/views/teams/contextualBar/channels/hooks/useTeamsChannelList.ts @@ -41,9 +41,10 @@ export const useTeamsChannelList = ( }); return { - items: rooms.map(({ _updatedAt, lastMessage, lm, jitsiTimeout, ...room }) => ({ + items: rooms.map(({ _updatedAt, lastMessage, lm, ts, jitsiTimeout, ...room }) => ({ jitsiTimeout: new Date(jitsiTimeout), ...(lm && { lm: new Date(lm) }), + ...(ts && { ts: new Date(ts) }), _updatedAt: new Date(_updatedAt), ...(lastMessage && { lastMessage: mapMessageFromApi(lastMessage) }), ...room, diff --git a/definition/IMessage/IMessage.ts b/definition/IMessage/IMessage.ts index 55f46c2838684..8fa8b66c348df 100644 --- a/definition/IMessage/IMessage.ts +++ b/definition/IMessage/IMessage.ts @@ -1,11 +1,11 @@ import { MessageSurfaceLayout } from '@rocket.chat/ui-kit'; import { parser } from '@rocket.chat/message-parser'; -import { IRocketChatRecord } from '../IRocketChatRecord'; -import { IUser } from '../IUser'; -import { ChannelName, RoomID } from '../IRoom'; -import { MessageAttachment } from './MessageAttachment/MessageAttachment'; -import { FileProp } from './MessageAttachment/Files/FileProp'; +import type { IRocketChatRecord } from '../IRocketChatRecord'; +import type { IUser } from '../IUser'; +import type { ChannelName, RoomID } from '../IRoom'; +import type { MessageAttachment } from './MessageAttachment/MessageAttachment'; +import type { FileProp } from './MessageAttachment/Files/FileProp'; type MentionType = 'user' | 'team'; diff --git a/definition/IRoom.ts b/definition/IRoom.ts index 8597a9035e82d..04e94c1cb4041 100644 --- a/definition/IRoom.ts +++ b/definition/IRoom.ts @@ -63,6 +63,7 @@ export interface IRoom extends IRocketChatRecord { muted?: string[]; usernames?: string[]; + ts?: Date; } export interface ICreatedRoom extends IRoom { diff --git a/definition/IUser.ts b/definition/IUser.ts index e40908cec83af..55d1b62b29b9c 100644 --- a/definition/IUser.ts +++ b/definition/IUser.ts @@ -95,7 +95,7 @@ export interface IRole { name: string; protected: boolean; // scope?: string; - scope?: 'Users' | 'Subscriptions'; + scope: 'Users' | 'Subscriptions'; _id: string; } diff --git a/client/contexts/ServerContext/endpoints/apps.ts b/definition/rest/apps/index.ts similarity index 100% rename from client/contexts/ServerContext/endpoints/apps.ts rename to definition/rest/apps/index.ts diff --git a/definition/rest/helpers/PaginatedRequest.ts b/definition/rest/helpers/PaginatedRequest.ts new file mode 100644 index 0000000000000..44962dea20628 --- /dev/null +++ b/definition/rest/helpers/PaginatedRequest.ts @@ -0,0 +1,4 @@ +export type PaginatedRequest = { + count: number; + offset: number; +}; diff --git a/definition/rest/helpers/PaginatedResult.ts b/definition/rest/helpers/PaginatedResult.ts new file mode 100644 index 0000000000000..e78980c0d1e70 --- /dev/null +++ b/definition/rest/helpers/PaginatedResult.ts @@ -0,0 +1,5 @@ +export type PaginatedResult = { + count: number; + offset: number; + total: number; +}; diff --git a/definition/rest/index.ts b/definition/rest/index.ts new file mode 100644 index 0000000000000..b7bad4b5ffdae --- /dev/null +++ b/definition/rest/index.ts @@ -0,0 +1,97 @@ +import type { EnterpriseEndpoints } from '../../ee/definition/rest'; +import type { ExtractKeys, ValueOf } from '../utils'; +import type { AppsEndpoints } from './apps'; +import { BannersEndpoints } from './v1/banners'; +import type { ChannelsEndpoints } from './v1/channels'; +import type { ChatEndpoints } from './v1/chat'; +import type { CloudEndpoints } from './v1/cloud'; +import type { CustomUserStatusEndpoints } from './v1/customUserStatus'; +import type { DmEndpoints } from './v1/dm'; +import type { DnsEndpoints } from './v1/dns'; +import type { EmojiCustomEndpoints } from './v1/emojiCustom'; +import type { GroupsEndpoints } from './v1/groups'; +import type { ImEndpoints } from './v1/im'; +import { InstancesEndpoints } from './v1/instances'; +import type { LDAPEndpoints } from './v1/ldap'; +import type { LicensesEndpoints } from './v1/licenses'; +import type { MiscEndpoints } from './v1/misc'; +import type { OmnichannelEndpoints } from './v1/omnichannel'; +import { PermissionsEndpoints } from './v1/permissions'; +import { RolesEndpoints } from './v1/roles'; +import type { RoomsEndpoints } from './v1/rooms'; +import { SettingsEndpoints } from './v1/settings'; +import type { StatisticsEndpoints } from './v1/statistics'; +import type { TeamsEndpoints } from './v1/teams'; +import type { UsersEndpoints } from './v1/users'; + +type CommunityEndpoints = BannersEndpoints & ChatEndpoints & +ChannelsEndpoints & +CloudEndpoints & +CustomUserStatusEndpoints & +DmEndpoints & +DnsEndpoints & +EmojiCustomEndpoints & +GroupsEndpoints & +ImEndpoints & +LDAPEndpoints & +RoomsEndpoints & +RolesEndpoints & +TeamsEndpoints & +SettingsEndpoints & +UsersEndpoints & +AppsEndpoints & +OmnichannelEndpoints & +StatisticsEndpoints & +LicensesEndpoints & +MiscEndpoints & +PermissionsEndpoints & +InstancesEndpoints; + +export type Endpoints = CommunityEndpoints & EnterpriseEndpoints; + +type Endpoint = UnionizeEndpoints; + +type UnionizeEndpoints = ValueOf< +{ + [P in keyof EE]: UnionizeMethods; +} +>; + +type ExtractOperations = ExtractKeys any>; + +type UnionizeMethods = ValueOf< +{ + [M in keyof OO as ExtractOperations]: ( + method: M, + path: OO extends { path: string } ? OO['path'] : P, + ...params: Parameters any>> + ) => ReturnType any>>; +} +>; + +export type Method = Parameters[0]; +export type Path = Parameters[1]; + +export type MethodFor

    = P extends any + ? Parameters any>>[0] + : never; +export type PathFor = M extends any + ? Parameters any>>[1] + : never; + +type Operation> = M extends any + ? P extends any + ? Extract any> + : never + : never; + +type ExtractParams = Q extends [any, any] + ? [undefined?] + : Q extends [any, any, any, ...any[]] + ? [Q[2]] + : never; + +export type Params> = ExtractParams< +Parameters> +>; +export type Return> = ReturnType>; diff --git a/definition/rest/v1/banners.ts b/definition/rest/v1/banners.ts new file mode 100644 index 0000000000000..e448abb7b410f --- /dev/null +++ b/definition/rest/v1/banners.ts @@ -0,0 +1,26 @@ +import { IBanner } from '../../IBanner'; + +export type BannersEndpoints = { + /* @deprecated */ + 'banners.getNew': { + GET: () => ({ + banners: IBanner[]; + }); + }; + + 'banners/:id': { + GET: (params: { platform: string }) => ({ + banners: IBanner[]; + }); + }; + + 'banners': { + GET: () => ({ + banners: IBanner[]; + }); + }; + + 'banners.dismiss': { + POST: (params: { bannerId: string }) => void; + }; +}; diff --git a/client/contexts/ServerContext/endpoints/v1/channels.ts b/definition/rest/v1/channels.ts similarity index 70% rename from client/contexts/ServerContext/endpoints/v1/channels.ts rename to definition/rest/v1/channels.ts index 0e2abbf7751c7..1a5bbca27ba3a 100644 --- a/client/contexts/ServerContext/endpoints/v1/channels.ts +++ b/definition/rest/v1/channels.ts @@ -1,6 +1,6 @@ -import type { IMessage } from '../../../../../definition/IMessage/IMessage'; -import type { IRoom } from '../../../../../definition/IRoom'; -import type { IUser } from '../../../../../definition/IUser'; +import type { IMessage } from '../../IMessage/IMessage'; +import type { IRoom } from '../../IRoom'; +import type { IUser } from '../../IUser'; export type ChannelsEndpoints = { 'channels.files': { diff --git a/client/contexts/ServerContext/endpoints/v1/chat.ts b/definition/rest/v1/chat.ts similarity index 84% rename from client/contexts/ServerContext/endpoints/v1/chat.ts rename to definition/rest/v1/chat.ts index 0ed5c1e029800..134f832b64eef 100644 --- a/client/contexts/ServerContext/endpoints/v1/chat.ts +++ b/definition/rest/v1/chat.ts @@ -1,5 +1,5 @@ -import type { IMessage } from '../../../../../definition/IMessage'; -import type { IRoom } from '../../../../../definition/IRoom'; +import type { IMessage } from '../../IMessage'; +import type { IRoom } from '../../IRoom'; export type ChatEndpoints = { 'chat.getMessage': { diff --git a/client/contexts/ServerContext/endpoints/v1/cloud.ts b/definition/rest/v1/cloud.ts similarity index 100% rename from client/contexts/ServerContext/endpoints/v1/cloud.ts rename to definition/rest/v1/cloud.ts diff --git a/client/contexts/ServerContext/endpoints/v1/customUserStatus.ts b/definition/rest/v1/customUserStatus.ts similarity index 100% rename from client/contexts/ServerContext/endpoints/v1/customUserStatus.ts rename to definition/rest/v1/customUserStatus.ts diff --git a/definition/rest/v1/dm.ts b/definition/rest/v1/dm.ts new file mode 100644 index 0000000000000..4ce71b05469b1 --- /dev/null +++ b/definition/rest/v1/dm.ts @@ -0,0 +1,21 @@ +import type { IRoom } from '../../IRoom'; +import type { IUser } from '../../IUser'; + +export type DmEndpoints = { + 'dm.create': { + POST: ( + params: ( + | { + username: Exclude; + } + | { + usernames: string; + } + ) & { + excludeSelf?: boolean; + }, + ) => { + room: IRoom & { rid: IRoom['_id'] }; + }; + }; +}; diff --git a/client/contexts/ServerContext/endpoints/v1/dns.ts b/definition/rest/v1/dns.ts similarity index 62% rename from client/contexts/ServerContext/endpoints/v1/dns.ts rename to definition/rest/v1/dns.ts index 136e8d698a987..b2d553e036f20 100644 --- a/client/contexts/ServerContext/endpoints/v1/dns.ts +++ b/definition/rest/v1/dns.ts @@ -5,8 +5,9 @@ export type DnsEndpoints = { }; }; 'dns.resolve.txt': { - GET: (params: { url: string }) => { - resolved: Record; + POST: (params: { url: string }) => { + resolved: string; + // resolved: Record; }; }; }; diff --git a/client/contexts/ServerContext/endpoints/v1/emojiCustom.ts b/definition/rest/v1/emojiCustom.ts similarity index 73% rename from client/contexts/ServerContext/endpoints/v1/emojiCustom.ts rename to definition/rest/v1/emojiCustom.ts index 63648e6f3e26c..0a49286ecfd53 100644 --- a/client/contexts/ServerContext/endpoints/v1/emojiCustom.ts +++ b/definition/rest/v1/emojiCustom.ts @@ -1,4 +1,4 @@ -import type { ICustomEmojiDescriptor } from '../../../../../definition/ICustomEmojiDescriptor'; +import type { ICustomEmojiDescriptor } from '../../ICustomEmojiDescriptor'; export type EmojiCustomEndpoints = { 'emoji-custom.list': { diff --git a/client/contexts/ServerContext/endpoints/v1/groups.ts b/definition/rest/v1/groups.ts similarity index 69% rename from client/contexts/ServerContext/endpoints/v1/groups.ts rename to definition/rest/v1/groups.ts index 5b01d443d451a..3b5a584795ad4 100644 --- a/client/contexts/ServerContext/endpoints/v1/groups.ts +++ b/definition/rest/v1/groups.ts @@ -1,6 +1,6 @@ -import type { IMessage } from '../../../../../definition/IMessage'; -import type { IRoom } from '../../../../../definition/IRoom'; -import type { IUser } from '../../../../../definition/IUser'; +import type { IMessage } from '../../IMessage'; +import type { IRoom } from '../../IRoom'; +import type { IUser } from '../../IUser'; export type GroupsEndpoints = { 'groups.files': { diff --git a/client/contexts/ServerContext/endpoints/v1/im.ts b/definition/rest/v1/im.ts similarity index 65% rename from client/contexts/ServerContext/endpoints/v1/im.ts rename to definition/rest/v1/im.ts index 700cdecfda447..b88a5b2be0c77 100644 --- a/client/contexts/ServerContext/endpoints/v1/im.ts +++ b/definition/rest/v1/im.ts @@ -1,17 +1,17 @@ -import type { IMessage } from '../../../../../definition/IMessage'; -import type { IRoom } from '../../../../../definition/IRoom'; -import type { IUser } from '../../../../../definition/IUser'; +import type { IMessage } from '../../IMessage'; +import type { IRoom } from '../../IRoom'; +import type { IUser } from '../../IUser'; export type ImEndpoints = { 'im.create': { POST: ( params: ( | { - username: Exclude; - } + username: Exclude; + } | { - usernames: string; - } + usernames: string; + } ) & { excludeSelf?: boolean; }, diff --git a/definition/rest/v1/instances.ts b/definition/rest/v1/instances.ts new file mode 100644 index 0000000000000..c792fc9b5fd54 --- /dev/null +++ b/definition/rest/v1/instances.ts @@ -0,0 +1,16 @@ +import { IInstanceStatus } from '../../IInstanceStatus'; + +export type InstancesEndpoints = { + 'instances.get': { + GET: () => ({ + instances: (IInstanceStatus | { + connection: { + address: unknown; + currentStatus: unknown; + instanceRecord: unknown; + broadcastAuth: unknown; + }; + })[]; + }); + }; +}; diff --git a/client/contexts/ServerContext/endpoints/v1/ldap.ts b/definition/rest/v1/ldap.ts similarity index 64% rename from client/contexts/ServerContext/endpoints/v1/ldap.ts rename to definition/rest/v1/ldap.ts index 09b19d5637a3d..7553d749eaf9c 100644 --- a/client/contexts/ServerContext/endpoints/v1/ldap.ts +++ b/definition/rest/v1/ldap.ts @@ -1,4 +1,4 @@ -import type { TranslationKey } from '../../../TranslationContext'; +import type { TranslationKey } from '../../../client/contexts/TranslationContext'; export type LDAPEndpoints = { 'ldap.testConnection': { @@ -7,9 +7,9 @@ export type LDAPEndpoints = { }; }; 'ldap.testSearch': { - POST: (params: { username: string }) => { + POST: (params: { username: string }) => ({ message: TranslationKey; - }; + }); }; 'ldap.syncNow': { POST: () => { diff --git a/client/contexts/ServerContext/endpoints/v1/licenses.ts b/definition/rest/v1/licenses.ts similarity index 64% rename from client/contexts/ServerContext/endpoints/v1/licenses.ts rename to definition/rest/v1/licenses.ts index 5d78d69dd5ed8..cc4b0dba981d3 100644 --- a/client/contexts/ServerContext/endpoints/v1/licenses.ts +++ b/definition/rest/v1/licenses.ts @@ -1,9 +1,12 @@ -import type { ILicense } from '../../../../../ee/app/license/server/license'; +import type { ILicense } from '../../../ee/app/license/definitions/ILicense'; export type LicensesEndpoints = { 'licenses.get': { GET: () => { licenses: Array }; }; + 'licenses.add': { + POST: (params: { license: string }) => void; + }; 'licenses.maxActiveUsers': { GET: () => { maxActiveUsers: number | null; activeUsers: number }; }; diff --git a/client/contexts/ServerContext/endpoints/v1/misc.ts b/definition/rest/v1/misc.ts similarity index 100% rename from client/contexts/ServerContext/endpoints/v1/misc.ts rename to definition/rest/v1/misc.ts diff --git a/client/contexts/ServerContext/endpoints/v1/omnichannel.ts b/definition/rest/v1/omnichannel.ts similarity index 82% rename from client/contexts/ServerContext/endpoints/v1/omnichannel.ts rename to definition/rest/v1/omnichannel.ts index 4254db71253ad..1598355cb5bac 100644 --- a/client/contexts/ServerContext/endpoints/v1/omnichannel.ts +++ b/definition/rest/v1/omnichannel.ts @@ -1,10 +1,10 @@ -import { ILivechatDepartment } from '../../../../../definition/ILivechatDepartment'; -import { ILivechatMonitor } from '../../../../../definition/ILivechatMonitor'; -import { ILivechatTag } from '../../../../../definition/ILivechatTag'; -import { IOmnichannelCannedResponse } from '../../../../../definition/IOmnichannelCannedResponse'; -import { IOmnichannelRoom, IRoom } from '../../../../../definition/IRoom'; -import { ISetting } from '../../../../../definition/ISetting'; -import { IUser } from '../../../../../definition/IUser'; +import { ILivechatDepartment } from '../../ILivechatDepartment'; +import { ILivechatMonitor } from '../../ILivechatMonitor'; +import { ILivechatTag } from '../../ILivechatTag'; +import { IOmnichannelCannedResponse } from '../../IOmnichannelCannedResponse'; +import { IOmnichannelRoom, IRoom } from '../../IRoom'; +import { ISetting } from '../../ISetting'; +import { IUser } from '../../IUser'; export type OmnichannelEndpoints = { 'livechat/appearance': { @@ -49,7 +49,7 @@ export type OmnichannelEndpoints = { }; }; 'livechat/department/:_id': { - path: `livechat/department/${string}`; + path: `livechat/department/${ string }`; GET: () => { department: ILivechatDepartment; }; @@ -138,7 +138,7 @@ export type OmnichannelEndpoints = { DELETE: (params: { _id: IOmnichannelCannedResponse['_id'] }) => void; }; 'canned-responses/:_id': { - path: `canned-responses/${string}`; + path: `canned-responses/${ string }`; GET: () => { cannedResponse: IOmnichannelCannedResponse; }; diff --git a/definition/rest/v1/permissions.ts b/definition/rest/v1/permissions.ts new file mode 100644 index 0000000000000..b9ec5ca958647 --- /dev/null +++ b/definition/rest/v1/permissions.ts @@ -0,0 +1,43 @@ +import Ajv, { JSONSchemaType } from 'ajv'; + +import { IPermission } from '../../IPermission'; + +const ajv = new Ajv(); + +type PermissionsUpdateProps = { permissions: { _id: string; roles: string[] }[] }; + +const permissionUpdatePropsSchema: JSONSchemaType = { + type: 'object', + properties: { + permissions: { + type: 'array', + items: { + type: 'object', + properties: { + _id: { type: 'string' }, + roles: { type: 'array', items: { type: 'string' }, uniqueItems: true }, + }, + additionalProperties: false, + required: ['_id', 'roles'], + }, + }, + }, + required: ['permissions'], + additionalProperties: false, +}; + +export const isBodyParamsValidPermissionUpdate = ajv.compile(permissionUpdatePropsSchema); + +export type PermissionsEndpoints = { + 'permissions.listAll': { + GET: (params: { updatedSince?: string }) => ({ + update: IPermission[]; + remove: IPermission[]; + }); + }; + 'permissions.update': { + POST: (params: PermissionsUpdateProps) => ({ + permissions: IPermission[]; + }); + }; +}; diff --git a/definition/rest/v1/roles.ts b/definition/rest/v1/roles.ts new file mode 100644 index 0000000000000..844d125083b3d --- /dev/null +++ b/definition/rest/v1/roles.ts @@ -0,0 +1,186 @@ +import Ajv, { JSONSchemaType } from 'ajv'; + +import { IRole, IUser } from '../../IUser'; + +const ajv = new Ajv(); + +type RoleCreateProps = Pick & Partial>; + +const roleCreatePropsSchema: JSONSchemaType = { + type: 'object', + properties: { + name: { + type: 'string', + }, + description: { + type: 'string', + nullable: true, + }, + scope: { + type: 'string', + enum: ['Users', 'Subscriptions'], + nullable: true, + }, + mandatory2fa: { + type: 'boolean', + nullable: true, + }, + }, + required: ['name'], + additionalProperties: false, +}; + +export const isRoleCreateProps = ajv.compile(roleCreatePropsSchema); + +type RoleUpdateProps = { roleId: IRole['_id']; name: IRole['name'] } & Partial; + +const roleUpdatePropsSchema: JSONSchemaType = { + type: 'object', + properties: { + roleId: { + type: 'string', + }, + name: { + type: 'string', + }, + description: { + type: 'string', + nullable: true, + }, + scope: { + type: 'string', + enum: ['Users', 'Subscriptions'], + nullable: true, + }, + mandatory2fa: { + type: 'boolean', + nullable: true, + }, + }, + required: ['roleId', 'name'], + additionalProperties: false, +}; + +export const isRoleUpdateProps = ajv.compile(roleUpdatePropsSchema); + +type RoleDeleteProps = { roleId: IRole['_id'] }; + +const roleDeletePropsSchema: JSONSchemaType = { + type: 'object', + properties: { + roleId: { + type: 'string', + }, + }, + required: ['roleId'], + additionalProperties: false, +}; + +export const isRoleDeleteProps = ajv.compile(roleDeletePropsSchema); + +type RoleAddUserToRoleProps = { + username: string; + roleName: string; + roomId?: string; +} + +const roleAddUserToRolePropsSchema: JSONSchemaType = { + type: 'object', + properties: { + username: { + type: 'string', + }, + roleName: { + type: 'string', + }, + roomId: { + type: 'string', + nullable: true, + }, + }, + required: ['username', 'roleName'], + additionalProperties: false, +}; + + +export const isRoleAddUserToRoleProps = ajv.compile(roleAddUserToRolePropsSchema); + +type RoleRemoveUserFromRoleProps = { + username: string; + roleName: string; + roomId?: string; +} + +const roleRemoveUserFromRolePropsSchema: JSONSchemaType = { + type: 'object', + properties: { + username: { + type: 'string', + }, + roleName: { + type: 'string', + }, + roomId: { + type: 'string', + nullable: true, + }, + }, + required: ['username', 'roleName'], + additionalProperties: false, +}; + +export const isRoleRemoveUserFromRoleProps = ajv.compile(roleRemoveUserFromRolePropsSchema); + +type RoleSyncProps = { + updatedSince?: string; +} + +export type RolesEndpoints = { + 'roles.list': { + GET: () => ({ + roles: IRole[]; + }); + }; + 'roles.sync': { + GET: (params: RoleSyncProps) => ({ + roles: { + update: IRole[]; + remove: IRole[]; + }; + }); + }; + 'roles.create': { + POST: (params: RoleCreateProps) => ({ + role: IRole; + }); + }; + + 'roles.addUserToRole': { + POST: (params: RoleAddUserToRoleProps) => ({ + role: IRole; + }); + }; + + 'roles.getUsersInRole': { + GET: (params: { roomId: string; role: string; offset: number; count: number }) => ({ + users: IUser[]; + total: number; + }); + }; + + 'roles.update': { + POST: (role: RoleUpdateProps) => ({ + role: IRole; + }); + }; + + 'roles.delete': { + POST: (prop: RoleDeleteProps) => void; + }; + + 'roles.removeUserFromRole': { + POST: (props: RoleRemoveUserFromRoleProps) => ({ + role: IRole; + }); + }; +}; diff --git a/client/contexts/ServerContext/endpoints/v1/rooms.ts b/definition/rest/v1/rooms.ts similarity index 81% rename from client/contexts/ServerContext/endpoints/v1/rooms.ts rename to definition/rest/v1/rooms.ts index 960610ec558af..92f0bc5895cb0 100644 --- a/client/contexts/ServerContext/endpoints/v1/rooms.ts +++ b/definition/rest/v1/rooms.ts @@ -1,6 +1,6 @@ -import type { IMessage } from '../../../../../definition/IMessage'; -import type { IRoom } from '../../../../../definition/IRoom'; -import type { IUser } from '../../../../../definition/IUser'; +import type { IMessage } from '../../IMessage'; +import type { IRoom } from '../../IRoom'; +import type { IUser } from '../../IUser'; export type RoomsEndpoints = { 'rooms.autocomplete.channelAndPrivate': { diff --git a/definition/rest/v1/settings.ts b/definition/rest/v1/settings.ts new file mode 100644 index 0000000000000..71ffab95bbf20 --- /dev/null +++ b/definition/rest/v1/settings.ts @@ -0,0 +1,100 @@ +import { ISetting, ISettingColor } from '../../ISetting'; +import { PaginatedResult } from '../helpers/PaginatedResult'; + +type SettingsUpdateProps = SettingsUpdatePropDefault | SettingsUpdatePropsActions | SettingsUpdatePropsColor; + +type SettingsUpdatePropsActions = { + execute: boolean; +} + +export type OauthCustomConfiguration = { + _id: string; + clientId?: string; + custom: unknown; + service?: string; + serverURL: unknown; + tokenPath: unknown; + identityPath: unknown; + authorizePath: unknown; + scope: unknown; + loginStyle: unknown; + tokenSentVia: unknown; + identityTokenSentVia: unknown; + keyField: unknown; + usernameField: unknown; + emailField: unknown; + nameField: unknown; + avatarField: unknown; + rolesClaim: unknown; + groupsClaim: unknown; + mapChannels: unknown; + channelsMap: unknown; + channelsAdmin: unknown; + mergeUsers: unknown; + mergeRoles: unknown; + accessTokenParam: unknown; + showButton: unknown; + + appId: unknown; + consumerKey?: string; + + clientConfig: unknown; + buttonLabelText: unknown; + buttonLabelColor: unknown; + buttonColor: unknown; +} + +export const isOauthCustomConfiguration = (config: any): config is OauthCustomConfiguration => Boolean(config); + +export const isSettingsUpdatePropsActions = (props: Partial): props is SettingsUpdatePropsActions => 'execute' in props; + +type SettingsUpdatePropsColor = { + editor: ISettingColor['editor']; + value: ISetting['value']; +} + +export const isSettingsUpdatePropsColor = (props: Partial): props is SettingsUpdatePropsColor => 'editor' in props && 'value' in props; + +type SettingsUpdatePropDefault = { + value: ISetting['value']; +} + +export const isSettingsUpdatePropDefault = (props: Partial): props is SettingsUpdatePropDefault => 'value' in props; + +export type SettingsEndpoints = { + 'settings.public': { + GET: () => PaginatedResult & { + settings: Array; + }; + }; + + 'settings.oauth': { + GET: () => ({ + services: Partial[]; + }); + }; + + 'settings.addCustomOAuth': { + POST: (params: { name: string }) => void; + }; + + 'settings': { + GET: () => ({ + settings: ISetting[]; + }); + }; + + 'settings/:_id': { + GET: () => Pick; + POST: (params: SettingsUpdateProps) => void; + }; + + 'service.configurations': { + GET: () => { + configurations: Array<{ + appId: string; + secret: string; + }>; + }; + }; +}; diff --git a/client/contexts/ServerContext/endpoints/v1/statistics.ts b/definition/rest/v1/statistics.ts similarity index 63% rename from client/contexts/ServerContext/endpoints/v1/statistics.ts rename to definition/rest/v1/statistics.ts index 178d8f5d66b87..5820d2be290b5 100644 --- a/client/contexts/ServerContext/endpoints/v1/statistics.ts +++ b/definition/rest/v1/statistics.ts @@ -1,4 +1,4 @@ -import type { IStats } from '../../../../../definition/IStats'; +import type { IStats } from '../../IStats'; export type StatisticsEndpoints = { statistics: { diff --git a/definition/rest/v1/teams/TeamsAddMembersProps.test.ts b/definition/rest/v1/teams/TeamsAddMembersProps.test.ts new file mode 100644 index 0000000000000..f2d7ec71bc25f --- /dev/null +++ b/definition/rest/v1/teams/TeamsAddMembersProps.test.ts @@ -0,0 +1,71 @@ +/* eslint-env mocha */ +import chai from 'chai'; + +import { isTeamsAddMembersProps } from './TeamsAddMembersProps'; + +describe('TeamsAddMemberProps (definition/rest/v1)', () => { + describe('isTeamsAddMembersProps', () => { + it('should be a function', () => { + chai.assert.isFunction(isTeamsAddMembersProps); + }); + it('should return false if the parameter is empty', () => { + chai.assert.isFalse(isTeamsAddMembersProps({})); + }); + + it('should return false if teamId is provided but no member was provided', () => { + chai.assert.isFalse(isTeamsAddMembersProps({ teamId: '123' })); + }); + + it('should return false if teamName is provided but no member was provided', () => { + chai.assert.isFalse(isTeamsAddMembersProps({ teamName: '123' })); + }); + + it('should return false if members is provided but no teamId or teamName were provided', () => { + chai.assert.isFalse(isTeamsAddMembersProps({ members: [{ userId: '123' }] })); + }); + + it('should return false if teamName was provided but members are empty', () => { + chai.assert.isFalse(isTeamsAddMembersProps({ teamName: '123', members: [] })); + }); + + it('should return false if teamId was provided but members are empty', () => { + chai.assert.isFalse(isTeamsAddMembersProps({ teamId: '123', members: [] })); + }); + + it('should return false if members with role is provided but no teamId or teamName were provided', () => { + chai.assert.isFalse(isTeamsAddMembersProps({ members: [{ userId: '123', roles: ['123'] }] })); + }); + + it('should return true if members is provided and teamId is provided', () => { + chai.assert.isTrue(isTeamsAddMembersProps({ members: [{ userId: '123' }], teamId: '123' })); + }); + + it('should return true if members is provided and teamName is provided', () => { + chai.assert.isTrue(isTeamsAddMembersProps({ members: [{ userId: '123' }], teamName: '123' })); + }); + + it('should return true if members with role is provided and teamId is provided', () => { + chai.assert.isTrue(isTeamsAddMembersProps({ members: [{ userId: '123', roles: ['123'] }], teamId: '123' })); + }); + + it('should return true if members with role is provided and teamName is provided', () => { + chai.assert.isTrue(isTeamsAddMembersProps({ members: [{ userId: '123', roles: ['123'] }], teamName: '123' })); + }); + + it('should return false if teamName was provided and members contains an invalid property', () => { + chai.assert.isFalse(isTeamsAddMembersProps({ teamName: '123', members: [{ userId: '123', roles: ['123'], invalid: true }] })); + }); + + it('should return false if teamId was provided and members contains an invalid property', () => { + chai.assert.isFalse(isTeamsAddMembersProps({ teamId: '123', members: [{ userId: '123', roles: ['123'], invalid: true }] })); + }); + + it('should return false if teamName informed but contains an invalid property', () => { + chai.assert.isFalse(isTeamsAddMembersProps({ member: [{ userId: '123', roles: ['123'] }], teamName: '123', invalid: true })); + }); + + it('should return false if teamId informed but contains an invalid property', () => { + chai.assert.isFalse(isTeamsAddMembersProps({ member: [{ userId: '123', roles: ['123'] }], teamId: '123', invalid: true })); + }); + }); +}); diff --git a/definition/rest/v1/teams/TeamsAddMembersProps.ts b/definition/rest/v1/teams/TeamsAddMembersProps.ts new file mode 100644 index 0000000000000..599989fbf7af2 --- /dev/null +++ b/definition/rest/v1/teams/TeamsAddMembersProps.ts @@ -0,0 +1,80 @@ +import Ajv, { JSONSchemaType } from 'ajv'; + +import { ITeamMemberParams } from '../../../../server/sdk/types/ITeamService'; + +const ajv = new Ajv(); + +export type TeamsAddMembersProps = ({ teamId: string } | { teamName: string }) & { members: ITeamMemberParams[] }; + +const teamsAddMembersPropsSchema: JSONSchemaType = { + oneOf: [ + { + type: 'object', + properties: { + teamId: { + type: 'string', + }, + members: { + type: 'array', + items: { + + type: 'object', + properties: { + userId: { + type: 'string', + }, + roles: { + type: 'array', + items: { + type: 'string', + }, + nullable: true, + }, + }, + required: ['userId'], + additionalProperties: false, + }, + minItems: 1, + uniqueItems: true, + }, + }, + required: ['teamId', 'members'], + additionalProperties: false, + }, + { + type: 'object', + properties: { + teamName: { + type: 'string', + }, + members: { + type: 'array', + items: { + + type: 'object', + properties: { + userId: { + type: 'string', + }, + roles: { + type: 'array', + items: { + type: 'string', + }, + nullable: true, + }, + }, + required: ['userId'], + additionalProperties: false, + }, + minItems: 1, + uniqueItems: true, + }, + }, + required: ['teamName', 'members'], + additionalProperties: false, + }, + ], +}; + +export const isTeamsAddMembersProps = ajv.compile(teamsAddMembersPropsSchema); diff --git a/definition/rest/v1/teams/TeamsConvertToChannelProps.test.ts b/definition/rest/v1/teams/TeamsConvertToChannelProps.test.ts new file mode 100644 index 0000000000000..5c292c6f1b308 --- /dev/null +++ b/definition/rest/v1/teams/TeamsConvertToChannelProps.test.ts @@ -0,0 +1,39 @@ +/* eslint-env mocha */ +import chai from 'chai'; + +import { isTeamsConvertToChannelProps } from './TeamsConvertToChannelProps'; + +describe('TeamsConvertToChannelProps (definition/rest/v1)', () => { + describe('isTeamsConvertToChannelProps', () => { + it('should be a function', () => { + chai.assert.isFunction(isTeamsConvertToChannelProps); + }); + it('should return false if neither teamName or teamId is provided', () => { + chai.assert.isFalse(isTeamsConvertToChannelProps({})); + }); + + it('should return true if teamName is provided', () => { + chai.assert.isTrue(isTeamsConvertToChannelProps({ teamName: 'teamName' })); + }); + + it('should return true if teamId is provided', () => { + chai.assert.isTrue(isTeamsConvertToChannelProps({ teamId: 'teamId' })); + }); + + it('should return false if both teamName and teamId are provided', () => { + chai.assert.isFalse(isTeamsConvertToChannelProps({ teamName: 'teamName', teamId: 'teamId' })); + }); + + it('should return false if teamName is not a string', () => { + chai.assert.isFalse(isTeamsConvertToChannelProps({ teamName: 1 })); + }); + + it('should return false if teamId is not a string', () => { + chai.assert.isFalse(isTeamsConvertToChannelProps({ teamId: 1 })); + }); + + it('should return false if an additionalProperties is provided', () => { + chai.assert.isFalse(isTeamsConvertToChannelProps({ teamName: 'teamName', additionalProperties: 'additionalProperties' })); + }); + }); +}); diff --git a/definition/rest/v1/teams/TeamsConvertToChannelProps.ts b/definition/rest/v1/teams/TeamsConvertToChannelProps.ts new file mode 100644 index 0000000000000..af93a57020938 --- /dev/null +++ b/definition/rest/v1/teams/TeamsConvertToChannelProps.ts @@ -0,0 +1,54 @@ +import Ajv, { JSONSchemaType } from 'ajv'; + + +const ajv = new Ajv(); + +export type TeamsConvertToChannelProps = { + roomsToRemove?: string[]; +} & ({ teamId: string } | { teamName: string }); + +const teamsConvertToTeamsPropsSchema: JSONSchemaType = { + oneOf: [ + { + type: 'object', + + properties: { + roomsToRemove: { + type: 'array', + items: { + type: 'string', + }, + nullable: true, + }, + teamId: { + type: 'string', + }, + }, + required: [ + 'teamId', + ], + additionalProperties: false, + }, + { + type: 'object', + properties: { + roomsToRemove: { + type: 'array', + items: { + type: 'string', + }, + nullable: true, + }, + teamName: { + type: 'string', + }, + }, + required: [ + 'teamName', + ], + additionalProperties: false, + }, + ], +}; + +export const isTeamsConvertToChannelProps = ajv.compile(teamsConvertToTeamsPropsSchema); diff --git a/definition/rest/v1/teams/TeamsDeleteProps.test.ts b/definition/rest/v1/teams/TeamsDeleteProps.test.ts new file mode 100644 index 0000000000000..9efc17cd1dec1 --- /dev/null +++ b/definition/rest/v1/teams/TeamsDeleteProps.test.ts @@ -0,0 +1,64 @@ +/* eslint-env mocha */ +import chai from 'chai'; + +import { isTeamsDeleteProps } from './TeamsDeleteProps'; + +describe('TeamsDeleteProps (definition/rest/v1)', () => { + describe('isTeamsDeleteProps', () => { + it('should be a function', () => { + chai.assert.isFunction(isTeamsDeleteProps); + }); + + it('should return false if neither teamName or teamId is provided', () => { + chai.assert.isFalse(isTeamsDeleteProps({})); + }); + + it('should return true if teamId is provided', () => { + chai.assert.isTrue(isTeamsDeleteProps({ teamId: 'teamId' })); + }); + + it('should return true if teamName is provided', () => { + chai.assert.isTrue(isTeamsDeleteProps({ teamName: 'teamName' })); + }); + + it('should return false if teamId and roomsToRemove are provided, but roomsToRemove is empty', () => { + chai.assert.isFalse(isTeamsDeleteProps({ teamId: 'teamId', roomsToRemove: [] })); + }); + + it('should return false if teamName and roomsToRemove are provided, but roomsToRemove is empty', () => { + chai.assert.isFalse(isTeamsDeleteProps({ teamName: 'teamName', roomsToRemove: [] })); + }); + + it('should return true if teamId and roomsToRemove are provided', () => { + chai.assert.isTrue(isTeamsDeleteProps({ teamId: 'teamId', roomsToRemove: ['roomId'] })); + }); + + it('should return true if teamName and roomsToRemove are provided', () => { + chai.assert.isTrue(isTeamsDeleteProps({ teamName: 'teamName', roomsToRemove: ['roomId'] })); + }); + + it('should return false if teamId and roomsToRemove are provided, but roomsToRemove is not an array', () => { + chai.assert.isFalse(isTeamsDeleteProps({ teamId: 'teamId', roomsToRemove: {} })); + }); + + it('should return false if teamName and roomsToRemove are provided, but roomsToRemove is not an array', () => { + chai.assert.isFalse(isTeamsDeleteProps({ teamName: 'teamName', roomsToRemove: {} })); + }); + + it('should return false if teamId and roomsToRemove are provided, but roomsToRemove is not an array of strings', () => { + chai.assert.isFalse(isTeamsDeleteProps({ teamId: 'teamId', roomsToRemove: [1] })); + }); + + it('should return false if teamName and roomsToRemove are provided, but roomsToRemove is not an array of strings', () => { + chai.assert.isFalse(isTeamsDeleteProps({ teamName: 'teamName', roomsToRemove: [1] })); + }); + + it('should return false if teamName and rooms are provided but an extra property is provided', () => { + chai.assert.isFalse(isTeamsDeleteProps({ teamName: 'teamName', roomsToRemove: ['roomsToRemove'], extra: 'extra' })); + }); + + it('should return false if teamId and rooms are provided but an extra property is provided', () => { + chai.assert.isFalse(isTeamsDeleteProps({ teamId: 'teamId', roomsToRemove: ['roomsToRemove'], extra: 'extra' })); + }); + }); +}); diff --git a/definition/rest/v1/teams/TeamsDeleteProps.ts b/definition/rest/v1/teams/TeamsDeleteProps.ts new file mode 100644 index 0000000000000..22582488d0aca --- /dev/null +++ b/definition/rest/v1/teams/TeamsDeleteProps.ts @@ -0,0 +1,50 @@ +import Ajv, { JSONSchemaType } from 'ajv'; + +const ajv = new Ajv(); + +export type TeamsDeleteProps = ({ teamId: string } | { teamName: string }) & { roomsToRemove?: string[] }; + +const teamsDeletePropsSchema: JSONSchemaType = { + oneOf: [ + { + type: 'object', + properties: { + teamId: { + type: 'string', + }, + roomsToRemove: { + type: 'array', + items: { + type: 'string', + }, + minItems: 1, + uniqueItems: true, + nullable: true, + }, + }, + required: ['teamId'], + additionalProperties: false, + }, + { + type: 'object', + properties: { + teamName: { + type: 'string', + }, + roomsToRemove: { + type: 'array', + items: { + type: 'string', + }, + minItems: 1, + uniqueItems: true, + nullable: true, + }, + }, + required: ['teamName'], + additionalProperties: false, + }, + ], +}; + +export const isTeamsDeleteProps = ajv.compile(teamsDeletePropsSchema); diff --git a/definition/rest/v1/teams/TeamsLeaveProps.test.ts b/definition/rest/v1/teams/TeamsLeaveProps.test.ts new file mode 100644 index 0000000000000..1b6255d979706 --- /dev/null +++ b/definition/rest/v1/teams/TeamsLeaveProps.test.ts @@ -0,0 +1,64 @@ +/* eslint-env mocha */ +import chai from 'chai'; + +import { isTeamsLeaveProps } from './TeamsLeaveProps'; + +describe('TeamsLeaveProps (definition/rest/v1)', () => { + describe('isTeamsLeaveProps', () => { + it('should be a function', () => { + chai.assert.isFunction(isTeamsLeaveProps); + }); + + it('should return false if neither teamName or teamId is provided', () => { + chai.assert.isFalse(isTeamsLeaveProps({})); + }); + + it('should return true if teamId is provided', () => { + chai.assert.isTrue(isTeamsLeaveProps({ teamId: 'teamId' })); + }); + + it('should return true if teamName is provided', () => { + chai.assert.isTrue(isTeamsLeaveProps({ teamName: 'teamName' })); + }); + + it('should return false if teamId and roomsToRemove are provided, but roomsToRemove is empty', () => { + chai.assert.isFalse(isTeamsLeaveProps({ teamId: 'teamId', rooms: [] })); + }); + + it('should return false if teamName and rooms are provided, but rooms is empty', () => { + chai.assert.isFalse(isTeamsLeaveProps({ teamName: 'teamName', rooms: [] })); + }); + + it('should return true if teamId and rooms are provided', () => { + chai.assert.isTrue(isTeamsLeaveProps({ teamId: 'teamId', rooms: ['roomId'] })); + }); + + it('should return true if teamName and rooms are provided', () => { + chai.assert.isTrue(isTeamsLeaveProps({ teamName: 'teamName', rooms: ['roomId'] })); + }); + + it('should return false if teamId and rooms are provided, but rooms is not an array', () => { + chai.assert.isFalse(isTeamsLeaveProps({ teamId: 'teamId', rooms: {} })); + }); + + it('should return false if teamName and rooms are provided, but rooms is not an array', () => { + chai.assert.isFalse(isTeamsLeaveProps({ teamName: 'teamName', rooms: {} })); + }); + + it('should return false if teamId and rooms are provided, but rooms is not an array of strings', () => { + chai.assert.isFalse(isTeamsLeaveProps({ teamId: 'teamId', rooms: [1] })); + }); + + it('should return false if teamName and rooms are provided, but rooms is not an array of strings', () => { + chai.assert.isFalse(isTeamsLeaveProps({ teamName: 'teamName', rooms: [1] })); + }); + + it('should return false if teamName and rooms are provided but an extra property is provided', () => { + chai.assert.isFalse(isTeamsLeaveProps({ teamName: 'teamName', rooms: ['rooms'], extra: 'extra' })); + }); + + it('should return false if teamId and rooms are provided but an extra property is provided', () => { + chai.assert.isFalse(isTeamsLeaveProps({ teamId: 'teamId', rooms: ['rooms'], extra: 'extra' })); + }); + }); +}); diff --git a/definition/rest/v1/teams/TeamsLeaveProps.ts b/definition/rest/v1/teams/TeamsLeaveProps.ts new file mode 100644 index 0000000000000..ac526886237bd --- /dev/null +++ b/definition/rest/v1/teams/TeamsLeaveProps.ts @@ -0,0 +1,51 @@ +import Ajv, { JSONSchemaType } from 'ajv'; + + +const ajv = new Ajv(); + +export type TeamsLeaveProps = ({ teamId: string } | { teamName: string }) & { rooms?: string[] }; + +const teamsLeavePropsSchema: JSONSchemaType = { + oneOf: [ + { + type: 'object', + properties: { + teamId: { + type: 'string', + }, + rooms: { + type: 'array', + items: { + type: 'string', + }, + minItems: 1, + uniqueItems: true, + nullable: true, + }, + }, + required: ['teamId'], + additionalProperties: false, + }, + { + type: 'object', + properties: { + teamName: { + type: 'string', + }, + rooms: { + type: 'array', + items: { + type: 'string', + }, + minItems: 1, + uniqueItems: true, + nullable: true, + }, + }, + required: ['teamName'], + additionalProperties: false, + }, + ], +}; + +export const isTeamsLeaveProps = ajv.compile(teamsLeavePropsSchema); diff --git a/definition/rest/v1/teams/TeamsRemoveMemberProps.test.ts b/definition/rest/v1/teams/TeamsRemoveMemberProps.test.ts new file mode 100644 index 0000000000000..f66c765e03480 --- /dev/null +++ b/definition/rest/v1/teams/TeamsRemoveMemberProps.test.ts @@ -0,0 +1,61 @@ +/* eslint-env mocha */ +import chai from 'chai'; + +import { isTeamsRemoveMemberProps } from './TeamsRemoveMemberProps'; + +describe('Teams (definition/rest/v1)', () => { + describe('isTeamsRemoveMemberProps', () => { + it('should be a function', () => { + chai.assert.isFunction(isTeamsRemoveMemberProps); + }); + it('should return false if parameter is empty', () => { + chai.assert.isFalse(isTeamsRemoveMemberProps({})); + }); + it('should return false if teamId is is informed but missing userId', () => { + chai.assert.isFalse(isTeamsRemoveMemberProps({ teamId: 'teamId' })); + }); + it('should return false if teamName is is informed but missing userId', () => { + chai.assert.isFalse(isTeamsRemoveMemberProps({ teamName: 'teamName' })); + }); + + it('should return true if teamId and userId are informed', () => { + chai.assert.isTrue(isTeamsRemoveMemberProps({ teamId: 'teamId', userId: 'userId' })); + }); + it('should return true if teamName and userId are informed', () => { + chai.assert.isTrue(isTeamsRemoveMemberProps({ teamName: 'teamName', userId: 'userId' })); + }); + + + it('should return false if teamName and userId are informed but rooms are empty', () => { + chai.assert.isFalse(isTeamsRemoveMemberProps({ teamName: 'teamName', userId: 'userId', rooms: [] })); + }); + + it('should return false if teamId and userId are informed and rooms are empty', () => { + chai.assert.isFalse(isTeamsRemoveMemberProps({ teamId: 'teamId', userId: 'userId', rooms: [] })); + }); + + it('should return false if teamId and userId are informed but rooms are empty', () => { + chai.assert.isFalse(isTeamsRemoveMemberProps({ teamId: 'teamId', userId: 'userId', rooms: [] })); + }); + + it('should return true if teamId and userId are informed and rooms are informed', () => { + chai.assert.isTrue(isTeamsRemoveMemberProps({ teamId: 'teamId', userId: 'userId', rooms: ['room'] })); + }); + + it('should return false if teamId and userId are informed and rooms are informed but rooms is not an array of strings', () => { + chai.assert.isFalse(isTeamsRemoveMemberProps({ teamId: 'teamId', userId: 'userId', rooms: [123] })); + }); + + it('should return false if teamName and userId are informed and rooms are informed but there is an extra property', () => { + chai.assert.isFalse(isTeamsRemoveMemberProps({ teamName: 'teamName', userId: 'userId', rooms: ['room'], extra: 'extra' })); + }); + + it('should return false if teamId and userId are informed and rooms are informed but there is an extra property', () => { + chai.assert.isFalse(isTeamsRemoveMemberProps({ teamId: 'teamId', userId: 'userId', rooms: ['room'], extra: 'extra' })); + }); + + it('should return false if teamName and userId are informed and rooms are informed but there is an extra property', () => { + chai.assert.isFalse(isTeamsRemoveMemberProps({ teamName: 'teamName', userId: 'userId', rooms: ['room'], extra: 'extra' })); + }); + }); +}); diff --git a/definition/rest/v1/teams/TeamsRemoveMemberProps.ts b/definition/rest/v1/teams/TeamsRemoveMemberProps.ts new file mode 100644 index 0000000000000..7518fd351c8e8 --- /dev/null +++ b/definition/rest/v1/teams/TeamsRemoveMemberProps.ts @@ -0,0 +1,56 @@ +import Ajv, { JSONSchemaType } from 'ajv'; + +const ajv = new Ajv(); + +export type TeamsRemoveMemberProps = ({ teamId: string } | { teamName: string }) & { userId: string; rooms?: Array }; + +const teamsRemoveMemberPropsSchema: JSONSchemaType = { + oneOf: [ + { + type: 'object', + properties: { + teamId: { + type: 'string', + }, + userId: { + type: 'string', + }, + rooms: { + type: 'array', + items: { + type: 'string', + }, + minItems: 1, + uniqueItems: true, + nullable: true, + }, + }, + required: ['teamId', 'userId'], + additionalProperties: false, + }, + { + type: 'object', + properties: { + teamName: { + type: 'string', + }, + userId: { + type: 'string', + }, + rooms: { + type: 'array', + items: { + type: 'string', + }, + minItems: 1, + uniqueItems: true, + nullable: true, + }, + }, + required: ['teamName', 'userId'], + additionalProperties: false, + }, + ], +}; + +export const isTeamsRemoveMemberProps = ajv.compile(teamsRemoveMemberPropsSchema); diff --git a/definition/rest/v1/teams/TeamsRemoveRoomProps.test.ts b/definition/rest/v1/teams/TeamsRemoveRoomProps.test.ts new file mode 100644 index 0000000000000..226185dcc9553 --- /dev/null +++ b/definition/rest/v1/teams/TeamsRemoveRoomProps.test.ts @@ -0,0 +1,27 @@ +/* eslint-env mocha */ +import chai from 'chai'; + +import { isTeamsRemoveRoomProps } from './TeamsRemoveRoomProps'; + +describe('TeamsRemoveRoomProps (definition/rest/v1)', () => { + describe('isTeamsRemoveRoomProps', () => { + it('should be a function', () => { + chai.assert.isFunction(isTeamsRemoveRoomProps); + }); + it('should return false if roomId is not provided', () => { + chai.assert.isFalse(isTeamsRemoveRoomProps({})); + }); + it('should return false if roomId is provided but no teamId or teamName were provided', () => { + chai.assert.isFalse(isTeamsRemoveRoomProps({ roomId: 'roomId' })); + }); + it('should return false if roomId is provided and teamId is provided', () => { + chai.assert.isTrue(isTeamsRemoveRoomProps({ roomId: 'roomId', teamId: 'teamId' })); + }); + it('should return true if roomId is provided and teamName is provided', () => { + chai.assert.isTrue(isTeamsRemoveRoomProps({ roomId: 'roomId', teamName: 'teamName' })); + }); + it('should return false if roomId and teamName are provided but an additional property is provided', () => { + chai.assert.isFalse(isTeamsRemoveRoomProps({ roomId: 'roomId', teamName: 'teamName', foo: 'bar' })); + }); + }); +}); diff --git a/definition/rest/v1/teams/TeamsRemoveRoomProps.ts b/definition/rest/v1/teams/TeamsRemoveRoomProps.ts new file mode 100644 index 0000000000000..a41db5538898b --- /dev/null +++ b/definition/rest/v1/teams/TeamsRemoveRoomProps.ts @@ -0,0 +1,40 @@ +import Ajv, { JSONSchemaType } from 'ajv'; + +import type { IRoom } from '../../../IRoom'; + +const ajv = new Ajv(); + +export type TeamsRemoveRoomProps = ({ teamId: string } | { teamName: string }) & { roomId: IRoom['_id'] }; + +export const teamsRemoveRoomPropsSchema: JSONSchemaType = { + oneOf: [ + { + type: 'object', + properties: { + teamId: { + type: 'string', + }, + roomId: { + type: 'string', + }, + }, + required: ['teamId', 'roomId'], + additionalProperties: false, + }, + { + type: 'object', + properties: { + teamName: { + type: 'string', + }, + roomId: { + type: 'string', + }, + }, + required: ['teamName', 'roomId'], + additionalProperties: false, + }, + ], +}; + +export const isTeamsRemoveRoomProps = ajv.compile(teamsRemoveRoomPropsSchema); diff --git a/definition/rest/v1/teams/TeamsUpdateMemberProps.test.ts b/definition/rest/v1/teams/TeamsUpdateMemberProps.test.ts new file mode 100644 index 0000000000000..ed6a329772a4d --- /dev/null +++ b/definition/rest/v1/teams/TeamsUpdateMemberProps.test.ts @@ -0,0 +1,59 @@ +/* eslint-env mocha */ +import chai from 'chai'; + +import { isTeamsUpdateMemberProps } from './TeamsUpdateMemberProps'; + +describe('TeamsUpdateMemberProps (definition/rest/v1)', () => { + describe('isTeamsUpdateMemberProps', () => { + it('should be a function', () => { + chai.assert.isFunction(isTeamsUpdateMemberProps); + }); + it('should return false if the parameter is empty', () => { + chai.assert.isFalse(isTeamsUpdateMemberProps({})); + }); + + it('should return false if teamId is provided but no member was provided', () => { + chai.assert.isFalse(isTeamsUpdateMemberProps({ teamId: '123' })); + }); + + it('should return false if teamName is provided but no member was provided', () => { + chai.assert.isFalse(isTeamsUpdateMemberProps({ teamName: '123' })); + }); + + it('should return false if member is provided but no teamId or teamName were provided', () => { + chai.assert.isFalse(isTeamsUpdateMemberProps({ member: { userId: '123' } })); + }); + + it('should return false if member with role is provided but no teamId or teamName were provided', () => { + chai.assert.isFalse(isTeamsUpdateMemberProps({ member: { userId: '123', roles: ['123'] } })); + }); + + it('should return true if member is provided and teamId is provided', () => { + chai.assert.isTrue(isTeamsUpdateMemberProps({ member: { userId: '123' }, teamId: '123' })); + }); + + it('should return true if member is provided and teamName is provided', () => { + chai.assert.isTrue(isTeamsUpdateMemberProps({ member: { userId: '123' }, teamName: '123' })); + }); + + it('should return true if member with role is provided and teamId is provided', () => { + chai.assert.isTrue(isTeamsUpdateMemberProps({ member: { userId: '123', roles: ['123'] }, teamId: '123' })); + }); + + it('should return true if member with role is provided and teamName is provided', () => { + chai.assert.isTrue(isTeamsUpdateMemberProps({ member: { userId: '123', roles: ['123'] }, teamName: '123' })); + }); + + it('should return false if teamName was provided and member contains an invalid property', () => { + chai.assert.isFalse(isTeamsUpdateMemberProps({ member: { userId: '123', invalid: '123' }, teamName: '123' })); + }); + + it('should return false if teamId was provided and member contains an invalid property', () => { + chai.assert.isFalse(isTeamsUpdateMemberProps({ member: { userId: '123', invalid: '123' }, teamId: '123' })); + }); + + it('should return false if contains an invalid property', () => { + chai.assert.isFalse(isTeamsUpdateMemberProps({ member: { userId: '123', roles: ['123'] }, teamName: '123', invalid: true })); + }); + }); +}); diff --git a/definition/rest/v1/teams/TeamsUpdateMemberProps.ts b/definition/rest/v1/teams/TeamsUpdateMemberProps.ts new file mode 100644 index 0000000000000..5a65fc6238ffd --- /dev/null +++ b/definition/rest/v1/teams/TeamsUpdateMemberProps.ts @@ -0,0 +1,68 @@ +import Ajv, { JSONSchemaType } from 'ajv'; + +import { ITeamMemberParams } from '../../../../server/sdk/types/ITeamService'; + +const ajv = new Ajv(); + +export type TeamsUpdateMemberProps = ({ teamId: string } | { teamName: string }) & { member: ITeamMemberParams }; + +const teamsUpdateMemberPropsSchema: JSONSchemaType = { + oneOf: [ + { + type: 'object', + properties: { + teamId: { + type: 'string', + }, + member: { + type: 'object', + properties: { + userId: { + type: 'string', + }, + roles: { + type: 'array', + items: { + type: 'string', + }, + nullable: true, + }, + }, + required: ['userId'], + additionalProperties: false, + }, + }, + required: ['teamId', 'member'], + additionalProperties: false, + }, + { + type: 'object', + properties: { + teamName: { + type: 'string', + }, + member: { + type: 'object', + properties: { + userId: { + type: 'string', + }, + roles: { + type: 'array', + items: { + type: 'string', + }, + nullable: true, + }, + }, + required: ['userId'], + additionalProperties: false, + }, + }, + required: ['teamName', 'member'], + additionalProperties: false, + }, + ], +}; + +export const isTeamsUpdateMemberProps = ajv.compile(teamsUpdateMemberPropsSchema); diff --git a/definition/rest/v1/teams/TeamsUpdateProps.test.ts b/definition/rest/v1/teams/TeamsUpdateProps.test.ts new file mode 100644 index 0000000000000..ce09985901d1d --- /dev/null +++ b/definition/rest/v1/teams/TeamsUpdateProps.test.ts @@ -0,0 +1,161 @@ +/* eslint-env mocha */ +import chai from 'chai'; + +import { isTeamsUpdateProps } from './TeamsUpdateProps'; + +describe('TeamsUpdateMemberProps (definition/rest/v1)', () => { + describe('isTeamsUpdateProps', () => { + it('should be a function', () => { + chai.assert.isFunction(isTeamsUpdateProps); + }); + it('should return false when provided anything that is not an TeamsUpdateProps', () => { + chai.assert.isFalse(isTeamsUpdateProps(undefined)); + chai.assert.isFalse(isTeamsUpdateProps(null)); + chai.assert.isFalse(isTeamsUpdateProps('')); + chai.assert.isFalse(isTeamsUpdateProps(123)); + chai.assert.isFalse(isTeamsUpdateProps({})); + chai.assert.isFalse(isTeamsUpdateProps([])); + chai.assert.isFalse(isTeamsUpdateProps(new Date())); + chai.assert.isFalse(isTeamsUpdateProps(new Error())); + }); + it('should return false when only teamName is provided to TeamsUpdateProps', () => { + chai.assert.isFalse(isTeamsUpdateProps({ + teamName: 'teamName', + })); + }); + + it('should return false when only teamId is provided to TeamsUpdateProps', () => { + chai.assert.isFalse(isTeamsUpdateProps({ + teamId: 'teamId', + })); + }); + + it('should return false when teamName and data are provided to TeamsUpdateProps but data is an empty object', () => { + chai.assert.isFalse(isTeamsUpdateProps({ + teamName: 'teamName', + data: {}, + })); + }); + + it('should return false when teamId and data are provided to TeamsUpdateProps but data is an empty object', () => { + chai.assert.isFalse(isTeamsUpdateProps({ + teamId: 'teamId', + data: {}, + })); + }); + + it('should return false when teamName and data are provided to TeamsUpdateProps but data is not an object', () => { + chai.assert.isFalse(isTeamsUpdateProps({ + teamName: 'teamName', + data: 'data', + })); + }); + + it('should return false when teamId and data are provided to TeamsUpdateProps but data is not an object', () => { + chai.assert.isFalse(isTeamsUpdateProps({ + teamId: 'teamId', + data: 'data', + })); + }); + + it('should return true when teamName and data.name are provided to TeamsUpdateProps', () => { + chai.assert.isTrue(isTeamsUpdateProps({ + teamName: 'teamName', + data: { + name: 'name', + }, + })); + }); + + it('should return true when teamId and data.name are provided to TeamsUpdateProps', () => { + chai.assert.isTrue(isTeamsUpdateProps({ + teamId: 'teamId', + data: { + name: 'name', + }, + })); + }); + + it('should return true when teamName and data.type are provided to TeamsUpdateProps', () => { + chai.assert.isTrue(isTeamsUpdateProps({ + teamName: 'teamName', + data: { + type: 0, + }, + })); + }); + + it('should return true when teamId and data.type are provided to TeamsUpdateProps', () => { + chai.assert.isTrue(isTeamsUpdateProps({ + teamId: 'teamId', + data: { + type: 0, + }, + })); + }); + + it('should return true when teamName and data.name and data.type are provided to TeamsUpdateProps', () => { + chai.assert.isTrue(isTeamsUpdateProps({ + teamName: 'teamName', + data: { + name: 'name', + type: 0, + }, + })); + }); + + it('should return true when teamId and data.name and data.type are provided to TeamsUpdateProps', () => { + chai.assert.isTrue(isTeamsUpdateProps({ + teamId: 'teamId', + data: { + name: 'name', + type: 0, + }, + })); + }); + + it('should return false when teamName, data.name, data.type are some more extra data are provided to TeamsUpdateProps', () => { + chai.assert.isFalse(isTeamsUpdateProps({ + teamName: 'teamName', + data: { + name: 'name', + type: 0, + extra: 'extra', + }, + })); + }); + + it('should return false when teamId, data.name, data.type are some more extra data are provided to TeamsUpdateProps', () => { + chai.assert.isFalse(isTeamsUpdateProps({ + teamId: 'teamId', + data: { + name: 'name', + type: 0, + extra: 'extra', + }, + })); + }); + + it('should return false when teamName, data.name, data.type are some more extra parameter are provided to TeamsUpdateProps', () => { + chai.assert.isFalse(isTeamsUpdateProps({ + teamName: 'teamName', + extra: 'extra', + data: { + name: 'name', + type: 0, + }, + })); + }); + + it('should return false when teamId, data.name, data.type are some more extra parameter are provided to TeamsUpdateProps', () => { + chai.assert.isFalse(isTeamsUpdateProps({ + teamId: 'teamId', + extra: 'extra', + data: { + name: 'name', + type: 0, + }, + })); + }); + }); +}); diff --git a/definition/rest/v1/teams/TeamsUpdateProps.ts b/definition/rest/v1/teams/TeamsUpdateProps.ts new file mode 100644 index 0000000000000..b019efdb1549b --- /dev/null +++ b/definition/rest/v1/teams/TeamsUpdateProps.ts @@ -0,0 +1,75 @@ +import Ajv, { JSONSchemaType } from 'ajv'; + +import { TEAM_TYPE } from '../../../ITeam'; + +const ajv = new Ajv(); + +export type TeamsUpdateProps = ({ teamId: string } | { teamName: string }) & { + data: ({ + name: string; + type?: TEAM_TYPE; + } | { + name?: string; + type: TEAM_TYPE; + }); +}; + +const teamsUpdatePropsSchema: JSONSchemaType = { + type: 'object', + properties: { + updateRoom: { + type: 'boolean', + nullable: true, + }, + teamId: { + type: 'string', + nullable: true, + }, + teamName: { + type: 'string', + nullable: true, + }, + data: { + type: 'object', + properties: { + name: { + type: 'string', + nullable: true, + }, + type: { + type: 'number', + enum: [ + TEAM_TYPE.PUBLIC, + TEAM_TYPE.PRIVATE, + ], + }, + }, + additionalProperties: false, + required: [], + anyOf: [ + { + required: ['name'], + }, + { + required: ['type'], + }, + ], + }, + name: { + type: 'string', + nullable: true, + }, + }, + required: [], + oneOf: [ + { + required: ['teamId', 'data'], + }, + { + required: ['teamName', 'data'], + }, + ], + additionalProperties: false, +}; + +export const isTeamsUpdateProps = ajv.compile(teamsUpdatePropsSchema); diff --git a/definition/rest/v1/teams/index.ts b/definition/rest/v1/teams/index.ts new file mode 100644 index 0000000000000..72694da7ea0c2 --- /dev/null +++ b/definition/rest/v1/teams/index.ts @@ -0,0 +1,148 @@ + + +import type { IRoom } from '../../../IRoom'; +import type { ITeam } from '../../../ITeam'; +import type { IUser } from '../../../IUser'; +import { PaginatedResult } from '../../helpers/PaginatedResult'; +import { PaginatedRequest } from '../../helpers/PaginatedRequest'; +import { ITeamAutocompleteResult, ITeamMemberInfo } from '../../../../server/sdk/types/ITeamService'; +import { TeamsRemoveRoomProps } from './TeamsRemoveRoomProps'; +import { TeamsConvertToChannelProps } from './TeamsConvertToChannelProps'; +import { TeamsUpdateMemberProps } from './TeamsUpdateMemberProps'; +import { TeamsAddMembersProps } from './TeamsAddMembersProps'; +import { TeamsRemoveMemberProps } from './TeamsRemoveMemberProps'; +import { TeamsDeleteProps } from './TeamsDeleteProps'; +import { TeamsLeaveProps } from './TeamsLeaveProps'; +import { TeamsUpdateProps } from './TeamsUpdateProps'; + + +type TeamProps = + | TeamsRemoveRoomProps + | TeamsConvertToChannelProps + | TeamsUpdateMemberProps + | TeamsAddMembersProps + | TeamsRemoveMemberProps + | TeamsDeleteProps + | TeamsLeaveProps + | TeamsUpdateProps; + +export const isTeamPropsWithTeamName = (props: T): props is T & { teamName: string } => 'teamName' in props; + +export const isTeamPropsWithTeamId = (props: T): props is T & { teamId: string } => 'teamId' in props; + +export type TeamsEndpoints = { + 'teams.list': { + GET: () => PaginatedResult & { teams: ITeam[] }; + }; + 'teams.listAll': { + GET: () => { teams: ITeam[] } & PaginatedResult; + }; + 'teams.create': { + POST: (params: { + name: ITeam['name']; + type?: ITeam['type']; + members?: IUser['_id'][]; + room: { + id?: string; + name?: IRoom['name']; + members?: IUser['_id'][]; + readOnly?: boolean; + extraData?: { + teamId?: string; + teamMain?: boolean; + } & { [key: string]: string | boolean }; + options?: { + nameValidationRegex?: string; + creator: string; + subscriptionExtra?: { + open: boolean; + ls: Date; + prid: IRoom['_id']; + }; + } & { + [key: string]: + | string + | { + open: boolean; + ls: Date; + prid: IRoom['_id']; + }; + }; + }; + owner?: IUser['_id']; + }) => { + team: ITeam; + }; + }; + + 'teams.convertToChannel': { + POST: (params: TeamsConvertToChannelProps) => void; + }; + + 'teams.addRooms': { + POST: (params: { rooms: IRoom['_id'][]; teamId: string } | { rooms: IRoom['_id'][]; teamName: string }) => ({ rooms: IRoom[] }); + }; + + 'teams.removeRoom': { + POST: (params: TeamsRemoveRoomProps) => ({ room: IRoom }); + }; + + 'teams.members': { + GET: (params: ({ teamId: string } | { teamName: string }) & { status?: string[]; username?: string; name?: string }) => (PaginatedResult & { members: ITeamMemberInfo[] }); + }; + + 'teams.addMembers': { + POST: (params: TeamsAddMembersProps) => void; + }; + + 'teams.updateMember': { + POST: (params: TeamsUpdateMemberProps) => void; + }; + + 'teams.removeMember': { + POST: (params: TeamsRemoveMemberProps) => void; + }; + + 'teams.leave': { + POST: (params: TeamsLeaveProps) => void; + }; + + + 'teams.info': { + GET: (params: ({ teamId: string } | { teamName: string }) & {}) => ({ teamInfo: Partial }); + }; + + 'teams.autocomplete': { + GET: (params: { name: string }) => ({ teams: ITeamAutocompleteResult[] }); + }; + + 'teams.update': { + POST: (params: TeamsUpdateProps) => void; + }; + + 'teams.delete': { + POST: (params: TeamsDeleteProps) => void; + }; + + 'teams.listRoomsOfUser': { + GET: (params: { + teamId: ITeam['_id']; + userId: IUser['_id']; + canUserDelete?: boolean; + } | { + teamName: ITeam['name']; + userId: IUser['_id']; + canUserDelete?: boolean; + } + ) => PaginatedResult & { rooms: IRoom[] }; + }; + + 'teams.listRooms': { + GET: (params: PaginatedRequest & ({ teamId: string } | { teamId: string }) & { filter?: string; type?: string }) => PaginatedResult & { rooms: IRoom[] }; + }; + + + 'teams.updateRoom': { + POST: (params: { roomId: IRoom['_id']; isDefault: boolean }) => ({ room: IRoom }); + }; +}; diff --git a/client/contexts/ServerContext/endpoints/v1/users.ts b/definition/rest/v1/users.ts similarity index 71% rename from client/contexts/ServerContext/endpoints/v1/users.ts rename to definition/rest/v1/users.ts index cbe8a74c8d404..337a2182f2e35 100644 --- a/client/contexts/ServerContext/endpoints/v1/users.ts +++ b/definition/rest/v1/users.ts @@ -1,5 +1,5 @@ -import type { ITeam } from '../../../../../definition/ITeam'; -import type { IUser } from '../../../../../definition/IUser'; +import type { ITeam } from '../../ITeam'; +import type { IUser } from '../../IUser'; export type UsersEndpoints = { 'users.2fa.sendEmailCode': { diff --git a/definition/utils.ts b/definition/utils.ts index 90e7f59df57d2..72339afa798a0 100644 --- a/definition/utils.ts +++ b/definition/utils.ts @@ -7,3 +7,5 @@ export type ValueOf = T[keyof T]; export type UnionToIntersection = (T extends any ? (x: T) => void : never) extends (x: infer U) => void ? U : never; + +export type Awaited = T extends PromiseLike ? Awaited : T; diff --git a/ee/app/license/definitions/ILicense.ts b/ee/app/license/definitions/ILicense.ts new file mode 100644 index 0000000000000..014912bef1add --- /dev/null +++ b/ee/app/license/definitions/ILicense.ts @@ -0,0 +1,11 @@ +import { ILicenseTag } from './ILicenseTag'; + +export interface ILicense { + url: string; + expiry: string; + maxActiveUsers: number; + modules: string[]; + maxGuestUsers: number; + maxRoomsPerGuest: number; + tag?: ILicenseTag; +} diff --git a/ee/app/license/definitions/ILicenseTag.ts b/ee/app/license/definitions/ILicenseTag.ts new file mode 100644 index 0000000000000..2f11fdebd5db5 --- /dev/null +++ b/ee/app/license/definitions/ILicenseTag.ts @@ -0,0 +1,4 @@ +export interface ILicenseTag { + name: string; + color: string; +} diff --git a/ee/app/license/server/license.ts b/ee/app/license/server/license.ts index ad0b962759f95..dd666753f6218 100644 --- a/ee/app/license/server/license.ts +++ b/ee/app/license/server/license.ts @@ -4,24 +4,11 @@ import { Users } from '../../../../app/models/server'; import { getBundleModules, isBundle, getBundleFromModule } from './bundles'; import decrypt from './decrypt'; import { getTagColor } from './getTagColor'; +import { ILicense } from '../definitions/ILicense'; +import { ILicenseTag } from '../definitions/ILicenseTag'; const EnterpriseLicenses = new EventEmitter(); -interface ILicenseTag { - name: string; - color: string; -} - -export interface ILicense { - url: string; - expiry: string; - maxActiveUsers: number; - modules: string[]; - maxGuestUsers: number; - maxRoomsPerGuest: number; - tag?: ILicenseTag; -} - export interface IValidLicense { valid?: boolean; license: ILicense; diff --git a/ee/app/livechat-enterprise/server/api/business-hours.ts b/ee/app/livechat-enterprise/server/api/business-hours.ts index aab6a58bb406c..4640ad58ae3c3 100644 --- a/ee/app/livechat-enterprise/server/api/business-hours.ts +++ b/ee/app/livechat-enterprise/server/api/business-hours.ts @@ -1,21 +1,20 @@ import { API } from '../../../../../app/api/server'; import { findBusinessHours } from '../business-hour/lib/business-hour'; -// @ts-ignore API.v1.addRoute('livechat/business-hours.list', { authRequired: true }, { - get() { + async get() { const { offset, count } = this.getPaginationItems(); const { sort } = this.parseJsonQuery(); const { name } = this.queryParams; // @ts-ignore - return API.v1.success(Promise.await(findBusinessHours( + return API.v1.success(await findBusinessHours( this.userId, { offset, count, sort, }, - name))); + name)); }, }); diff --git a/ee/app/livechat-enterprise/server/business-hour/Helper.ts b/ee/app/livechat-enterprise/server/business-hour/Helper.ts index ef729e8def20c..dd8da62936887 100644 --- a/ee/app/livechat-enterprise/server/business-hour/Helper.ts +++ b/ee/app/livechat-enterprise/server/business-hour/Helper.ts @@ -10,7 +10,7 @@ import { import { ILivechatBusinessHour, LivechatBusinessHourTypes } from '../../../../../definition/ILivechatBusinessHour'; const getAllAgentIdsWithoutDepartment = async (): Promise => { - const agentIdsWithDepartment = (await LivechatDepartmentAgents.find({}, { fields: { agentId: 1 } }).toArray()).map((dept: any) => dept.agentId); + const agentIdsWithDepartment = (await LivechatDepartmentAgents.find({}, { projection: { agentId: 1 } }).toArray()).map((dept: any) => dept.agentId); const agentIdsWithoutDepartment = (await Users.findUsersInRolesWithQuery('livechat-agent', { _id: { $nin: agentIdsWithDepartment }, }, { projection: { _id: 1 } }).toArray()).map((user: any) => user._id); diff --git a/ee/client/contexts/ServerContext/endpoints/v1/engagementDashboard.ts b/ee/client/contexts/ServerContext/endpoints/v1/engagementDashboard.ts deleted file mode 100644 index 1365da57e7f9b..0000000000000 --- a/ee/client/contexts/ServerContext/endpoints/v1/engagementDashboard.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { IDailyActiveUsers } from '../../../../../../definition/IUser'; -import { Serialized } from '../../../../../../definition/Serialized'; - -export type EngagementDashboardEndpoints = { - 'engagement-dashboard/users/active-users': { - GET: (params: { start: string; end: string }) => { - month: Serialized[]; - }; - }; -}; diff --git a/ee/definition/rest/index.ts b/ee/definition/rest/index.ts new file mode 100644 index 0000000000000..292ebd766aa7c --- /dev/null +++ b/ee/definition/rest/index.ts @@ -0,0 +1,4 @@ +import type { EngagementDashboardEndpoints } from './v1/engagementDashboard'; +import type { OmnichannelBusinessHoursEndpoints } from './v1/omnichannel/businessHours'; + +export type EnterpriseEndpoints = EngagementDashboardEndpoints & OmnichannelBusinessHoursEndpoints; diff --git a/ee/definition/rest/v1/engagementDashboard.ts b/ee/definition/rest/v1/engagementDashboard.ts new file mode 100644 index 0000000000000..f20ab3307f6e6 --- /dev/null +++ b/ee/definition/rest/v1/engagementDashboard.ts @@ -0,0 +1,62 @@ +import { IDirectMessageRoom, IRoom } from '../../../../definition/IRoom'; +import { IDailyActiveUsers } from '../../../../definition/IUser'; +import { Serialized } from '../../../../definition/Serialized'; + +export type EngagementDashboardEndpoints = { + '/v1/engagement-dashboard/channels/list': { + GET: (params: { start: Date; end: Date; offset: number; count: number }) => { + channels: { + room: { + _id: IRoom['_id']; + name: IRoom['name'] | IRoom['fname']; + ts: IRoom['ts']; + t: IRoom['t']; + _updatedAt: IRoom['_updatedAt']; + usernames?: IDirectMessageRoom['usernames']; + }; + messages: number; + lastWeekMessages: number; + diffFromLastWeek: number; + }[]; + count: number; + offset: number; + total: number; + }; + }; + 'engagement-dashboard/messages/origin': { + GET: (params: { start: Date; end: Date }) => { + origins: { + t: IRoom['t']; + messages: number; + }[]; + }; + }; + 'engagement-dashboard/messages/top-five-popular-channels': { + GET: (params: { start: Date; end: Date }) => { + channels: { + t: IRoom['t']; + messages: number; + name: IRoom['name'] | IRoom['fname']; + usernames?: IDirectMessageRoom['usernames']; + }[]; + }; + }; + 'engagement-dashboard/messages/messages-sent': { + GET: (params: { start: Date; end: Date }) => { + days: { day: Date; messages: number }[]; + period: { + count: number; + variation: number; + }; + yesterday: { + count: number; + variation: number; + }; + }; + }; + 'engagement-dashboard/users/active-users': { + GET: (params: { start: string; end: string }) => { + month: Serialized[]; + }; + }; +}; diff --git a/ee/definition/rest/v1/omnichannel/businessHours.ts b/ee/definition/rest/v1/omnichannel/businessHours.ts new file mode 100644 index 0000000000000..77b352c612599 --- /dev/null +++ b/ee/definition/rest/v1/omnichannel/businessHours.ts @@ -0,0 +1,7 @@ +import { ILivechatBusinessHour } from '../../../../../definition/ILivechatBusinessHour'; + +export type OmnichannelBusinessHoursEndpoints = { + 'livechat/business-hours.list': { + GET: () => ({ businessHours: ILivechatBusinessHour[] }); + }; +} diff --git a/ee/server/api/ldap.ts b/ee/server/api/ldap.ts index 1c4627e585d00..c51d21e588a49 100644 --- a/ee/server/api/ldap.ts +++ b/ee/server/api/ldap.ts @@ -5,7 +5,7 @@ import { LDAPEE } from '../sdk'; import { hasLicense } from '../../app/license/server/license'; API.v1.addRoute('ldap.syncNow', { authRequired: true }, { - post() { + async post() { if (!this.userId) { throw new Error('error-invalid-user'); } @@ -22,10 +22,10 @@ API.v1.addRoute('ldap.syncNow', { authRequired: true }, { throw new Error('LDAP_disabled'); } - LDAPEE.sync(); + await LDAPEE.sync(); return API.v1.success({ - message: 'Sync_in_progress', + message: 'Sync_in_progress' as const, }); }, }); diff --git a/ee/server/api/licenses.ts b/ee/server/api/licenses.ts index 0972584c3983f..c59ab4e40606c 100644 --- a/ee/server/api/licenses.ts +++ b/ee/server/api/licenses.ts @@ -1,9 +1,10 @@ import { check } from 'meteor/check'; -import { ILicense, getLicenses, validateFormat, flatModules, getMaxActiveUsers } from '../../app/license/server/license'; +import { getLicenses, validateFormat, flatModules, getMaxActiveUsers } from '../../app/license/server/license'; import { Settings, Users } from '../../../app/models/server'; import { API } from '../../../app/api/server/api'; import { hasPermission } from '../../../app/authorization/server'; +import { ILicense } from '../../app/license/definitions/ILicense'; function licenseTransform(license: ILicense): ILicense { return { diff --git a/ee/server/index.js b/ee/server/index.ts similarity index 100% rename from ee/server/index.js rename to ee/server/index.ts diff --git a/ee/server/lib/ldap/Manager.ts b/ee/server/lib/ldap/Manager.ts index 6b1c46b34eedb..b146244a18507 100644 --- a/ee/server/lib/ldap/Manager.ts +++ b/ee/server/lib/ldap/Manager.ts @@ -192,7 +192,7 @@ export class LDAPEEManager extends LDAPManager { } const roles = await Roles.find({}, { - fields: { + projection: { _updatedAt: 0, }, }).toArray() as Array; diff --git a/ee/server/services/package-lock.json b/ee/server/services/package-lock.json index 3b1cfe814ecef..110aa6323e6ef 100644 --- a/ee/server/services/package-lock.json +++ b/ee/server/services/package-lock.json @@ -419,6 +419,17 @@ "debug": "4" } }, + "ajv": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.7.1.tgz", + "integrity": "sha512-gPpOObTO1QjbnN1sVMjJcp1TF9nggMfO4MBR5uQl6ZVTOaEPq5i4oq/6R9q2alMMPB3eg53wFv1RuJBLuxf3Hw==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, "amp": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/amp/-/amp-0.3.1.tgz", @@ -1225,6 +1236,11 @@ } } }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, "fast-json-patch": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.0.tgz", @@ -1676,6 +1692,11 @@ "pako": "^0.2.5" } }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -2490,6 +2511,11 @@ "once": "^1.3.1" } }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", @@ -2564,6 +2590,11 @@ "ttl": "^1.3.0" } }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" + }, "require-in-the-middle": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-5.1.0.tgz", @@ -3070,6 +3101,14 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "requires": { + "punycode": "^2.1.0" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/ee/server/services/package.json b/ee/server/services/package.json index 5000e768d23cc..984e7ce794bff 100644 --- a/ee/server/services/package.json +++ b/ee/server/services/package.json @@ -19,6 +19,7 @@ "license": "MIT", "dependencies": { "@rocket.chat/string-helpers": "^0.29.0", + "ajv": "^8.7.1", "bcrypt": "^5.0.1", "body-parser": "^1.19.0", "colorette": "^1.3.0", diff --git a/package-lock.json b/package-lock.json index fb1db4f80cf91..7aa70cb89903c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9525,6 +9525,12 @@ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" }, + "@types/cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==", + "dev": true + }, "@types/dompurify": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-2.2.2.tgz", @@ -10167,6 +10173,25 @@ "integrity": "sha512-+mdBIb+pxJ9SLwtjc2DgolMm8U7CG6qBdCevkjSsFB7ehJ0EExFd2ltKQ6m9CoKitqXwe6Tx5h+fAcklGQD0Bw==", "dev": true }, + "@types/superagent": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.13.tgz", + "integrity": "sha512-YIGelp3ZyMiH0/A09PMAORO0EBGlF5xIKfDpK74wdYvWUs2o96b5CItJcWPdH409b7SAXIIG6p8NdU/4U2Maww==", + "dev": true, + "requires": { + "@types/cookiejar": "*", + "@types/node": "*" + } + }, + "@types/supertest": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.11.tgz", + "integrity": "sha512-uci4Esokrw9qGb9bvhhSVEjd6rkny/dk5PK/Qz4yxKiyppEI+dOPlNrZBahE3i+PoKFYyDxChVXZ/ysS/nrm1Q==", + "dev": true, + "requires": { + "@types/superagent": "*" + } + }, "@types/tapable": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.8.tgz", @@ -11097,14 +11122,21 @@ } }, "ajv": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.1.tgz", - "integrity": "sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww==", + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.7.1.tgz", + "integrity": "sha512-gPpOObTO1QjbnN1sVMjJcp1TF9nggMfO4MBR5uQl6ZVTOaEPq5i4oq/6R9q2alMMPB3eg53wFv1RuJBLuxf3Hw==", "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", "uri-js": "^4.2.2" + }, + "dependencies": { + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + } } }, "ajv-errors": { @@ -19119,9 +19151,9 @@ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-diff": { "version": "1.2.0", @@ -21771,6 +21803,19 @@ "requires": { "ajv": "^6.5.5", "har-schema": "^2.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + } } }, "hard-rejection": { @@ -32395,8 +32440,7 @@ "require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" }, "require_optional": { "version": "1.0.1", @@ -35390,6 +35434,20 @@ "ajv": "^6.1.0", "ajv-errors": "^1.0.0", "ajv-keywords": "^3.1.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + } } }, "source-map": { diff --git a/package.json b/package.json index c5865032264dd..38665a2c4d951 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,7 @@ "@types/semver": "^7.3.6", "@types/sharp": "^0.28.3", "@types/string-strip-html": "^5.0.0", + "@types/supertest": "^2.0.11", "@types/toastr": "^2.1.38", "@types/underscore.string": "0.0.38", "@types/use-subscription": "^1.0.0", @@ -182,6 +183,7 @@ "@types/lodash.debounce": "^4.0.6", "adm-zip": "0.4.14", "agenda": "github:RocketChat/agenda#3.1.2", + "ajv": "^8.7.1", "apn": "2.2.0", "archiver": "^3.1.1", "atlassian-crowd-patched": "^0.5.1", diff --git a/server/sdk/types/ITeamService.ts b/server/sdk/types/ITeamService.ts index 50396cd20552b..697a67e406418 100644 --- a/server/sdk/types/ITeamService.ts +++ b/server/sdk/types/ITeamService.ts @@ -47,11 +47,14 @@ export interface IListRoomsFilter { allowPrivateTeam: boolean; } -export interface ITeamUpdateData { - name: string; - type: TEAM_TYPE; - updateRoom?: boolean; // default is true -} +export type ITeamUpdateData = + { updateRoom?: boolean } & ({ + name: string; + type?: TEAM_TYPE; + } | { + name?: string; + type: TEAM_TYPE; + }) export type ITeamAutocompleteResult = Pick; diff --git a/tests/end-to-end/api/13-roles.js b/tests/end-to-end/api/13-roles.js deleted file mode 100644 index 162152a072141..0000000000000 --- a/tests/end-to-end/api/13-roles.js +++ /dev/null @@ -1,440 +0,0 @@ -import { expect } from 'chai'; - -import { - getCredentials, - api, - request, - credentials, - login, - apiRoleNameUsers, - apiRoleNameSubscriptions, - apiRoleScopeUsers, - apiRoleDescription, - apiRoleScopeSubscriptions, -} from '../../data/api-data.js'; -import { password } from '../../data/user'; -import { updatePermission } from '../../data/permissions.helper'; -import { createUser, login as doLogin } from '../../data/users.helper'; - -function createRole(name, scope, description) { - return new Promise((resolve) => { - request.post(api('roles.create')) - .set(credentials) - .send({ - name, - scope, - description, - }) - .end((err, req) => { - resolve(req.body.role); - }); - }); -} - -function addUserToRole(roleName, username, scope) { - return new Promise((resolve) => { - request.post(api('roles.addUserToRole')) - .set(credentials) - .send({ - roleName, - username, - roomId: scope, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - }) - .end((err, req) => { - resolve(req.body.role); - }); - }); -} - -describe('[Roles]', function() { - this.retries(0); - - before((done) => getCredentials(done)); - - describe('GET [/roles.list]', () => { - it('should return all roles', (done) => { - request.get(api('roles.list')) - .set(credentials) - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('roles').and.to.be.an('array'); - }) - .end(done); - }); - }); - - describe('GET [/roles.sync]', () => { - it('should return an array of roles which are updated after updatedSice date when search by "updatedSince" query parameter', (done) => { - request.get(api('roles.sync?updatedSince=2018-11-27T13:52:01Z')) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('roles'); - expect(res.body.roles).to.have.property('update').and.to.be.an('array'); - expect(res.body.roles).to.have.property('remove').and.to.be.an('array'); - }) - .end(done); - }); - - it('should return an error when updatedSince query parameter is not a valid ISODate string', (done) => { - request.get(api('roles.sync?updatedSince=fsafdf')) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - }) - .end(done); - }); - }); - - describe('POST [/roles.create]', () => { - it('should create a new role with Users scope', (done) => { - request.post(api('roles.create')) - .set(credentials) - .send({ - name: apiRoleNameUsers, - description: apiRoleDescription, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.nested.property('role._id'); - expect(res.body).to.have.nested.property('role.name', apiRoleNameUsers); - expect(res.body).to.have.nested.property('role.scope', apiRoleScopeUsers); - expect(res.body).to.have.nested.property('role.description', apiRoleDescription); - }) - .end(done); - }); - - it('should create a new role with Subscriptions scope', (done) => { - request.post(api('roles.create')) - .set(credentials) - .send({ - name: apiRoleNameSubscriptions, - scope: apiRoleScopeSubscriptions, - description: apiRoleDescription, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.nested.property('role._id'); - expect(res.body).to.have.nested.property('role.name', apiRoleNameSubscriptions); - expect(res.body).to.have.nested.property('role.scope', apiRoleScopeSubscriptions); - expect(res.body).to.have.nested.property('role.description', apiRoleDescription); - }) - .end(done); - }); - - it('should NOT create a new role with an existing role name', (done) => { - request.post(api('roles.create')) - .set(credentials) - .send({ - name: apiRoleNameUsers, - description: apiRoleDescription, - }) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.nested.property('error', 'Role name already exists [error-duplicate-role-names-not-allowed]'); - }) - .end(done); - }); - }); - - describe('POST [/roles.addUserToRole]', () => { - it('should assign a role with User scope to an user', (done) => { - request.post(api('roles.addUserToRole')) - .set(credentials) - .send({ - roleName: apiRoleNameUsers, - username: login.user, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.nested.property('role._id'); - expect(res.body).to.have.nested.property('role.name', apiRoleNameUsers); - expect(res.body).to.have.nested.property('role.scope', apiRoleScopeUsers); - }) - .end(done); - }); - - it('should assign a role with Subscriptions scope to an user', (done) => { - request.post(api('roles.addUserToRole')) - .set(credentials) - .send({ - roleName: apiRoleNameSubscriptions, - username: login.user, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.nested.property('role._id'); - expect(res.body).to.have.nested.property('role.name', apiRoleNameSubscriptions); - expect(res.body).to.have.nested.property('role.scope', apiRoleScopeSubscriptions); - }) - .end(done); - }); - }); - - describe('GET [/roles.getUsersInRole]', () => { - let userCredentials; - before((done) => { - createUser().then((createdUser) => { - doLogin(createdUser.username, password).then((createdUserCredentials) => { - userCredentials = createdUserCredentials; - updatePermission('access-permissions', ['admin', 'user']).then(done); - }); - }); - }); - it('should return an error when "role" query param is not provided', (done) => { - request.get(api('roles.getUsersInRole')) - .set(userCredentials) - .query({ - }) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body.errorType).to.be.equal('error-param-not-provided'); - }) - .end(done); - }); - it('should return an error when the user does not the necessary permission', (done) => { - updatePermission('access-permissions', ['admin']).then(() => { - request.get(api('roles.getUsersInRole')) - .set(userCredentials) - .query({ - role: 'admin', - }) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body.errorType).to.be.equal('error-not-allowed'); - }) - .end(done); - }); - }); - it('should return an error when the user try access rooms permissions and does not have the necessary permission', (done) => { - updatePermission('access-permissions', ['admin', 'user']).then(() => { - updatePermission('view-other-user-channels', []).then(() => { - request.get(api('roles.getUsersInRole')) - .set(userCredentials) - .query({ - role: 'admin', - roomId: 'GENERAL', - }) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body.errorType).to.be.equal('error-not-allowed'); - }) - .end(done); - }); - }); - }); - it('should return the list of users', (done) => { - updatePermission('access-permissions', ['admin', 'user']).then(() => { - updatePermission('view-other-user-channels', ['admin', 'user']).then(() => { - request.get(api('roles.getUsersInRole')) - .set(userCredentials) - .query({ - role: 'admin', - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body.users).to.be.an('array'); - }) - .end(done); - }); - }); - }); - it('should return the list of users when find by room Id', (done) => { - request.get(api('roles.getUsersInRole')) - .set(userCredentials) - .query({ - role: 'admin', - roomId: 'GENERAL', - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body.users).to.be.an('array'); - }) - .end(done); - }); - }); - - describe('POST [/roles.update]', () => { - const roleName = `role-${ Date.now() }`; - let newRole; - before(async () => { - newRole = await createRole(roleName, 'Users', 'Role description test'); - }); - - it('should update an existing role', (done) => { - const newRoleName = `${ roleName }Updated`; - const newRoleDescription = 'New role description'; - - request.post(api('roles.update')) - .set(credentials) - .send({ - roleId: newRole._id, - name: newRoleName, - description: newRoleDescription, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.nested.property('role._id', newRole._id); - expect(res.body).to.have.nested.property('role.name', newRoleName); - expect(res.body).to.have.nested.property('role.scope', newRole.scope); - expect(res.body).to.have.nested.property('role.description', newRoleDescription); - }) - .end(done); - }); - - it('should NOT update a role with an existing role name', (done) => { - request.post(api('roles.update')) - .set(credentials) - .send({ - roleId: newRole._id, - name: apiRoleNameUsers, - }) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.nested.property('error', 'Role name already exists [error-duplicate-role-names-not-allowed]'); - }) - .end(done); - }); - }); - - describe('POST [/roles.delete]', () => { - let roleWithUser; - let roleWithoutUser; - before(async () => { - roleWithUser = await createRole(`roleWithUser-${ Date.now() }`, 'Users'); - roleWithoutUser = await createRole(`roleWithoutUser-${ Date.now() }`, 'Users'); - - await addUserToRole(roleWithUser.name, login.user); - }); - - it('should delete a role that it is not being used', (done) => { - request.post(api('roles.delete')) - .set(credentials) - .send({ - roleId: roleWithoutUser._id, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - }) - .end(done); - }); - - it('should NOT delete a role that it is protected', (done) => { - request.post(api('roles.delete')) - .set(credentials) - .send({ - roleId: 'admin', - }) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.nested.property('error', 'Cannot delete a protected role [error-role-protected]'); - }) - .end(done); - }); - - it('should NOT delete a role that it is being used', (done) => { - request.post(api('roles.delete')) - .set(credentials) - .send({ - roleId: roleWithUser._id, - }) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.nested.property('error', 'Cannot delete role because it\'s in use [error-role-in-use]'); - }) - .end(done); - }); - }); - - describe('POST [/roles.removeUserFromRole]', () => { - let usersScopedRole; - let subscriptionsScopedRole; - - before(async () => { - usersScopedRole = await createRole(`usersScopedRole-${ Date.now() }`, 'Users'); - subscriptionsScopedRole = await createRole(`subscriptionsScopedRole-${ Date.now() }`, 'Subscriptions'); - - await addUserToRole(usersScopedRole.name, login.user); - await addUserToRole(subscriptionsScopedRole.name, login.user, 'GENERAL'); - }); - - it('should unassign a role with User scope from an user', (done) => { - request.post(api('roles.removeUserFromRole')) - .set(credentials) - .send({ - roleName: usersScopedRole.name, - username: login.user, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.nested.property('role._id', usersScopedRole._id); - expect(res.body).to.have.nested.property('role.name', usersScopedRole.name); - expect(res.body).to.have.nested.property('role.scope', usersScopedRole.scope); - expect(res.body).to.have.nested.property('role.description', usersScopedRole.description); - }) - .end(done); - }); - - it('should unassign a role with Subscriptions scope from an user', (done) => { - request.post(api('roles.removeUserFromRole')) - .set(credentials) - .send({ - roleName: subscriptionsScopedRole.name, - username: login.user, - scope: 'GENERAL', - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.nested.property('role._id', subscriptionsScopedRole._id); - expect(res.body).to.have.nested.property('role.name', subscriptionsScopedRole.name); - expect(res.body).to.have.nested.property('role.scope', subscriptionsScopedRole.scope); - expect(res.body).to.have.nested.property('role.description', subscriptionsScopedRole.description); - }) - .end(done); - }); - }); -}); diff --git a/tsconfig.json b/tsconfig.json index dff6cb6c6dddf..1094df397a417 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -42,9 +42,10 @@ "exclude": [ "./.meteor/**", "./packages/**", - "./imports/client" + "./imports/client**", + "**/dist/**", // "./ee/server/services/**" - // "node_modules" + "node_modules" ], "ts-node": { "files": true From 8931a6a9b7a3ac47afb0569ee2ef8b2527ea4797 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Tue, 16 Nov 2021 11:44:52 -0300 Subject: [PATCH 072/137] Chore: Mocha testing configuration (#23706) Co-authored-by: Tasso Evangelista Co-authored-by: Guilherme Gazzo --- .babelrc | 3 +- .eslintignore | 1 + .eslintrc | 10 + .github/workflows/build_and_test.yml | 6 + .husky/pre-push | 3 +- mocha_end_to_end.opts.js => .mocharc.api.js | 12 +- .mocharc.base.json | 16 + .mocharc.client.js | 32 + .mocharc.definition.js | 29 + .mocharc.js | 28 +- app/apps/server/tests/messages.tests.js | 8 +- .../server/transform_helpers.tests.js | 2 - app/highlight-words/tests/helper.tests.js | 13 +- app/lib/tests/server.tests.js | 14 +- app/mailer/tests/api.spec.ts | 21 +- app/markdown/tests/client.tests.js | 10 +- app/mentions/tests/client.tests.js | 110 +- app/mentions/tests/server.tests.js | 44 +- .../tests/server.tests.ts | 7 +- app/models/server/raw/Sessions.tests.js | 115 +- .../functions/getSettingDefaults.tests.ts | 2 - .../functions/overrideGenerator.tests.ts | 2 - .../server/functions/settings.tests.ts | 38 +- .../functions/validateSettings.tests.ts | 1 - app/settings/server/raw.tests.js | 10 +- .../server/lib/UAParserCustom.tests.js | 2 - .../server/lib/getMostImportantRole.tests.js | 2 - app/ui-utils/tests/server.tests.js | 6 +- .../client/views/app/tests/helpers.tests.js | 26 +- app/utils/lib/getURL.tests.js | 29 +- client/.eslintrc.js | 15 +- client/lib/download.spec.ts | 58 +- client/lib/minimongo/bson.spec.ts | 40 +- client/lib/minimongo/comparisons.spec.ts | 46 +- client/lib/minimongo/lookups.spec.ts | 16 +- client/views/notFound/NotFoundPage.js | 9 +- client/views/notFound/NotFoundPage.spec.tsx | 79 + .../v1/teams/TeamsAddMembersProps.spec.ts | 70 + .../teams/TeamsConvertToChannelProps.spec.ts | 38 + .../rest/v1/teams/TeamsDeleteProps.spec.ts | 63 + .../rest/v1/teams/TeamsLeaveProps.spec.ts | 63 + .../v1/teams/TeamsRemoveMemberProps.spec.ts | 60 + .../v1/teams/TeamsRemoveRoomProps.spec.ts | 26 + .../v1/teams/TeamsUpdateMemberProps.spec.ts | 58 + .../rest/v1/teams/TeamsUpdateProps.spec.ts | 160 ++ package-lock.json | 1833 ++++++----------- package.json | 21 +- server/lib/fileUtils.tests.ts | 1 - tests/cypress/integration/12-settings.js | 4 +- .../integration/14-setting-permissions.js | 1 - tests/cypress/integration/16-discussion.js | 1 - tests/end-to-end/api/00-miscellaneous.js | 4 +- tests/mocks/client/RouterContextMock.tsx | 44 + tests/mocks/client/blobUrls.ts | 23 + tests/mocks/client/jsdom.ts | 10 + tests/setup/chaiPlugins.ts | 8 + tests/setup/cleanupTestingLibrary.ts | 17 + tests/setup/registerWebApiMocks.ts | 5 + 58 files changed, 1784 insertions(+), 1591 deletions(-) rename mocha_end_to_end.opts.js => .mocharc.api.js (54%) create mode 100644 .mocharc.base.json create mode 100644 .mocharc.client.js create mode 100644 .mocharc.definition.js create mode 100644 client/views/notFound/NotFoundPage.spec.tsx create mode 100644 definition/rest/v1/teams/TeamsAddMembersProps.spec.ts create mode 100644 definition/rest/v1/teams/TeamsConvertToChannelProps.spec.ts create mode 100644 definition/rest/v1/teams/TeamsDeleteProps.spec.ts create mode 100644 definition/rest/v1/teams/TeamsLeaveProps.spec.ts create mode 100644 definition/rest/v1/teams/TeamsRemoveMemberProps.spec.ts create mode 100644 definition/rest/v1/teams/TeamsRemoveRoomProps.spec.ts create mode 100644 definition/rest/v1/teams/TeamsUpdateMemberProps.spec.ts create mode 100644 definition/rest/v1/teams/TeamsUpdateProps.spec.ts create mode 100644 tests/mocks/client/RouterContextMock.tsx create mode 100644 tests/mocks/client/blobUrls.ts create mode 100644 tests/mocks/client/jsdom.ts create mode 100644 tests/setup/chaiPlugins.ts create mode 100644 tests/setup/cleanupTestingLibrary.ts create mode 100644 tests/setup/registerWebApiMocks.ts diff --git a/.babelrc b/.babelrc index 867a790a279a5..b9359fe771b40 100644 --- a/.babelrc +++ b/.babelrc @@ -1,5 +1,6 @@ { "presets": [ - "@babel/preset-env" + "@babel/preset-env", + "@babel/preset-react" ] } diff --git a/.eslintignore b/.eslintignore index 24f6298dbc9df..f37309f64ef25 100644 --- a/.eslintignore +++ b/.eslintignore @@ -18,5 +18,6 @@ imports/client/**/* !/.storybook/ ee/server/services/dist/** !/.mocharc.js +!/.mocharc.*.js !/client/.eslintrc.js !/ee/client/.eslintrc.js diff --git a/.eslintrc b/.eslintrc index 8833cddb4eecd..212170f0e1c2c 100644 --- a/.eslintrc +++ b/.eslintrc @@ -144,6 +144,16 @@ "version": "detect" } } + }, + { + "files": [ + "**/*.tests.js", + "**/*.tests.ts", + "**/*.spec.ts" + ], + "env": { + "mocha": true + } } ] } diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 9f7b2443454c8..6631a6cb53c7e 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -242,9 +242,15 @@ jobs: run: | npm install + - name: Unit Test (definitions) + run: npm run testunit-definition + - name: Unit Test run: npm run testunit + - name: Unit Test (client) + run: npm run testunit-client + - name: E2E Test env: TEST_MODE: "true" diff --git a/.husky/pre-push b/.husky/pre-push index 8f8e7a09a9aac..3c9fedc8460ae 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,2 +1,3 @@ meteor npm run lint && \ -meteor npm run testunit +meteor npm run testunit && \ +meteor npm run testunit-client diff --git a/mocha_end_to_end.opts.js b/.mocharc.api.js similarity index 54% rename from mocha_end_to_end.opts.js rename to .mocharc.api.js index 36850cb812095..b8a96c749c9fc 100644 --- a/mocha_end_to_end.opts.js +++ b/.mocharc.api.js @@ -1,13 +1,11 @@ 'use strict'; +/** + * Mocha configuration for REST API integration tests. + */ + module.exports = { - require: [ - 'babel-mocha-es6-compiler', - 'babel-polyfill', - ], - reporter: 'spec', - ui: 'bdd', - extension: 'js,ts', + ...require('./.mocharc.base.json'), // see https://github.com/mochajs/mocha/issues/3916 timeout: 10000, bail: true, file: 'tests/end-to-end/teardown.js', diff --git a/.mocharc.base.json b/.mocharc.base.json new file mode 100644 index 0000000000000..ac8a2bcce8b7f --- /dev/null +++ b/.mocharc.base.json @@ -0,0 +1,16 @@ +{ + "ui": "bdd", + "reporter": "spec", + "extension": ["js", "ts", "tsx"], + "require": [ + "@babel/register", + "regenerator-runtime/runtime", + "ts-node/register", + "./tests/setup/chaiPlugins.ts" + ], + "watch-files": [ + "./**/*.js", + "./**/*.ts", + "./**/*.tsx" + ] +} diff --git a/.mocharc.client.js b/.mocharc.client.js new file mode 100644 index 0000000000000..e4279a9a63565 --- /dev/null +++ b/.mocharc.client.js @@ -0,0 +1,32 @@ +'use strict'; + +/** + * Mocha configuration for client-side unit and integration tests. + */ + +const base = require('./.mocharc.base.json'); + +/** + * Mocha will run `ts-node` without doing type checking to speed-up the tests. It should be fine as `npm run typecheck` + * covers test files too. + */ + +Object.assign(process.env, { + TS_NODE_FILES: true, + TS_NODE_TRANSPILE_ONLY: true, +}, process.env); + +module.exports = { + ...base, // see https://github.com/mochajs/mocha/issues/3916 + require: [ + ...base.require, + './tests/setup/registerWebApiMocks.ts', + './tests/setup/cleanupTestingLibrary.ts', + ], + exit: false, + slow: 200, + spec: [ + 'client/**/*.spec.ts', + 'client/**/*.spec.tsx', + ], +}; diff --git a/.mocharc.definition.js b/.mocharc.definition.js new file mode 100644 index 0000000000000..efffe16964d5c --- /dev/null +++ b/.mocharc.definition.js @@ -0,0 +1,29 @@ +'use strict'; + +/** + * Mocha configuration for unit tests for type guards. + */ + +const base = require('./.mocharc.base.json'); + +/** + * Mocha will run `ts-node` without doing type checking to speed-up the tests. It should be fine as `npm run typecheck` + * covers test files too. + */ + +Object.assign(process.env, { + TS_NODE_FILES: true, + TS_NODE_TRANSPILE_ONLY: true, +}, process.env); + +module.exports = { + ...base, // see https://github.com/mochajs/mocha/issues/3916 + require: [ + ...base.require, + ], + exit: false, + slow: 200, + spec: [ + 'definition/**/*.spec.ts', + ], +}; diff --git a/.mocharc.js b/.mocharc.js index c939a10c0cc5d..a71a3020cf4ba 100644 --- a/.mocharc.js +++ b/.mocharc.js @@ -1,18 +1,28 @@ 'use strict'; +/** + * Mocha configuration for general unit tests. + */ + +const base = require('./.mocharc.base.json'); + +/** + * Mocha will run `ts-node` without doing type checking to speed-up the tests. It should be fine as `npm run typecheck` + * covers test files too. + */ + +Object.assign(process.env, { + TS_NODE_FILES: true, + TS_NODE_TRANSPILE_ONLY: true, +}, process.env); + module.exports = { - require: [ - 'ts-node/register', - '@babel/register', - ], - reporter: 'spec', - ui: 'bdd', - extension: ['js', 'ts'], + ...base, // see https://github.com/mochajs/mocha/issues/3916 + exit: true, spec: [ + 'app/**/*.spec.ts', 'app/**/*.tests.js', 'app/**/*.tests.ts', - 'app/**/*.spec.ts', 'server/**/*.tests.ts', - 'client/**/*.spec.ts', ], }; diff --git a/app/apps/server/tests/messages.tests.js b/app/apps/server/tests/messages.tests.js index 9dee0c68bcdae..9e4919731e8c1 100644 --- a/app/apps/server/tests/messages.tests.js +++ b/app/apps/server/tests/messages.tests.js @@ -1,7 +1,5 @@ -/* eslint-env mocha */ -import 'babel-polyfill'; import mock from 'mock-require'; -import chai from 'chai'; +import { expect } from 'chai'; import { AppServerOrchestratorMock } from './mocks/orchestrator.mock'; import { appMessageMock, appMessageInvalidRoomMock } from './mocks/data/messages.data'; @@ -9,10 +7,6 @@ import { MessagesMock } from './mocks/models/Messages.mock'; import { RoomsMock } from './mocks/models/Rooms.mock'; import { UsersMock } from './mocks/models/Users.mock'; -chai.use(require('chai-datetime')); - -const { expect } = chai; - mock('../../../models', './mocks/models'); mock('meteor/random', { id: () => 1, diff --git a/app/custom-oauth/server/transform_helpers.tests.js b/app/custom-oauth/server/transform_helpers.tests.js index ec1475780e682..5139edb2410e2 100644 --- a/app/custom-oauth/server/transform_helpers.tests.js +++ b/app/custom-oauth/server/transform_helpers.tests.js @@ -1,5 +1,3 @@ -/* eslint-env mocha */ - import { expect } from 'chai'; import { diff --git a/app/highlight-words/tests/helper.tests.js b/app/highlight-words/tests/helper.tests.js index 28c5fd075164e..2b4e895d0e652 100644 --- a/app/highlight-words/tests/helper.tests.js +++ b/app/highlight-words/tests/helper.tests.js @@ -1,7 +1,4 @@ -/* eslint-env mocha */ - -import 'babel-polyfill'; -import assert from 'assert'; +import { expect } from 'chai'; import { highlightWords, getRegexHighlight, getRegexHighlightUrl } from '../client/helper'; @@ -14,7 +11,7 @@ describe('helper', () => { urlRegex: getRegexHighlightUrl(highlight), }))); - assert.equal(res, 'here is some word'); + expect(res).to.be.equal('here is some word'); }); describe('handles links', () => { @@ -25,7 +22,7 @@ describe('helper', () => { urlRegex: getRegexHighlightUrl(highlight), }))); - assert.equal(res, 'here we go https://somedomain.com/here-some.word/pulls more words after'); + expect(res).to.be.equal('here we go https://somedomain.com/here-some.word/pulls more words after'); }); it('not highlighting two links', () => { @@ -36,7 +33,7 @@ describe('helper', () => { urlRegex: getRegexHighlightUrl(highlight), }))); - assert.equal(res, msg); + expect(res).to.be.equal(msg); }); it('not highlighting link but keep words on message highlighted', () => { @@ -46,7 +43,7 @@ describe('helper', () => { urlRegex: getRegexHighlightUrl(highlight), }))); - assert.equal(res, 'here we go https://somedomain.com/here-some.foo/pulls more foo after'); + expect(res).to.be.equal('here we go https://somedomain.com/here-some.foo/pulls more foo after'); }); }); }); diff --git a/app/lib/tests/server.tests.js b/app/lib/tests/server.tests.js index cc6a4de04b1af..a606cff94901a 100644 --- a/app/lib/tests/server.tests.js +++ b/app/lib/tests/server.tests.js @@ -1,7 +1,3 @@ -/* eslint-env mocha */ -import 'babel-polyfill'; -import assert from 'assert'; - import { expect } from 'chai'; import './server.mocks.js'; @@ -41,11 +37,11 @@ describe('PasswordPolicyClass', () => { describe('Password tests with default options', () => { it('should allow all passwords', () => { const passwordPolice = new PasswordPolicyClass({ throwError: false }); - assert.equal(passwordPolice.validate(), false); - assert.equal(passwordPolice.validate(''), false); - assert.equal(passwordPolice.validate(' '), false); - assert.equal(passwordPolice.validate('a'), true); - assert.equal(passwordPolice.validate('aaaaaaaaa'), true); + expect(passwordPolice.validate()).to.be.equal(false); + expect(passwordPolice.validate('')).to.be.equal(false); + expect(passwordPolice.validate(' ')).to.be.equal(false); + expect(passwordPolice.validate('a')).to.be.equal(true); + expect(passwordPolice.validate('aaaaaaaaa')).to.be.equal(true); }); }); }); diff --git a/app/mailer/tests/api.spec.ts b/app/mailer/tests/api.spec.ts index 4c858c1b23e58..5bb1397c62e7f 100644 --- a/app/mailer/tests/api.spec.ts +++ b/app/mailer/tests/api.spec.ts @@ -1,5 +1,4 @@ -/* eslint-env mocha */ -import assert from 'assert'; +import { expect } from 'chai'; import { replaceVariables } from '../server/replaceVariables'; @@ -13,55 +12,55 @@ describe('Mailer-API', function() { describe('single key', function functionName() { it(`should be equal to test ${ i18n.key }`, () => { - assert.strictEqual(`test ${ i18n.key }`, replaceVariables('test {key}', (_match, key) => i18n[key])); + expect(`test ${ i18n.key }`).to.be.equal(replaceVariables('test {key}', (_match, key) => i18n[key])); }); }); describe('multiple keys', function functionName() { it(`should be equal to test ${ i18n.key } and ${ i18n.key }`, () => { - assert.strictEqual(`test ${ i18n.key } and ${ i18n.key }`, replaceVariables('test {key} and {key}', (_match, key) => i18n[key])); + expect(`test ${ i18n.key } and ${ i18n.key }`).to.be.equal(replaceVariables('test {key} and {key}', (_match, key) => i18n[key])); }); }); describe('key with a trailing space', function functionName() { it(`should be equal to test ${ i18n.key }`, () => { - assert.strictEqual(`test ${ i18n.key }`, replaceVariables('test {key }', (_match, key) => i18n[key])); + expect(`test ${ i18n.key }`).to.be.equal(replaceVariables('test {key }', (_match, key) => i18n[key])); }); }); describe('key with a leading space', function functionName() { it(`should be equal to test ${ i18n.key }`, () => { - assert.strictEqual(`test ${ i18n.key }`, replaceVariables('test { key}', (_match, key) => i18n[key])); + expect(`test ${ i18n.key }`).to.be.equal(replaceVariables('test { key}', (_match, key) => i18n[key])); }); }); describe('key with leading and trailing spaces', function functionName() { it(`should be equal to test ${ i18n.key }`, () => { - assert.strictEqual(`test ${ i18n.key }`, replaceVariables('test { key }', (_match, key) => i18n[key])); + expect(`test ${ i18n.key }`).to.be.equal(replaceVariables('test { key }', (_match, key) => i18n[key])); }); }); describe('key with multiple words', function functionName() { it(`should be equal to test ${ i18n.key }`, () => { - assert.strictEqual(`test ${ i18n.key }`, replaceVariables('test {key ignore}', (_match, key) => i18n[key])); + expect(`test ${ i18n.key }`).to.be.equal(replaceVariables('test {key ignore}', (_match, key) => i18n[key])); }); }); describe('key with multiple opening brackets', function functionName() { it(`should be equal to test {${ i18n.key }`, () => { - assert.strictEqual(`test {${ i18n.key }`, replaceVariables('test {{key}', (_match, key) => i18n[key])); + expect(`test {${ i18n.key }`).to.be.equal(replaceVariables('test {{key}', (_match, key) => i18n[key])); }); }); describe('key with multiple closing brackets', function functionName() { it(`should be equal to test ${ i18n.key }}`, () => { - assert.strictEqual(`test ${ i18n.key }}`, replaceVariables('test {key}}', (_match, key) => i18n[key])); + expect(`test ${ i18n.key }}`).to.be.equal(replaceVariables('test {key}}', (_match, key) => i18n[key])); }); }); describe('key with multiple opening and closing brackets', function functionName() { it(`should be equal to test {${ i18n.key }}`, () => { - assert.strictEqual(`test {${ i18n.key }}`, replaceVariables('test {{key}}', (_match, key) => i18n[key])); + expect(`test {${ i18n.key }}`).to.be.equal(replaceVariables('test {{key}}', (_match, key) => i18n[key])); }); }); }); diff --git a/app/markdown/tests/client.tests.js b/app/markdown/tests/client.tests.js index 83567dff63231..8d741f696ddb9 100644 --- a/app/markdown/tests/client.tests.js +++ b/app/markdown/tests/client.tests.js @@ -1,8 +1,6 @@ -/* eslint-env mocha */ -import 'babel-polyfill'; -import assert from 'assert'; - import './client.mocks.js'; + +import { expect } from 'chai'; import { escapeHTML } from '@rocket.chat/string-helpers'; import { original } from '../lib/parser/original/original'; @@ -375,7 +373,7 @@ const blockcodeFiltered = { 'Here```code```lies': 'Herecodelies', }; -const defaultObjectTest = (result, object, objectKey) => assert.equal(result.html, object[objectKey]); +const defaultObjectTest = (result, object, objectKey) => expect(result.html).to.be.equal(object[objectKey]); const testObject = (object, parser = original, test = defaultObjectTest) => { Object.keys(object).forEach((objectKey) => { @@ -435,7 +433,7 @@ describe('Filtered', function() { describe('blockcodeFilter', () => testObject(blockcodeFiltered, filtered)); }); -// describe.only('Marked', function() { +// describe('Marked', function() { // describe('Bold', () => testObject(bold, marked)); // describe('Italic', () => testObject(italic, marked)); diff --git a/app/mentions/tests/client.tests.js b/app/mentions/tests/client.tests.js index 5854ec14ba6ab..e90249dcf23c2 100644 --- a/app/mentions/tests/client.tests.js +++ b/app/mentions/tests/client.tests.js @@ -1,11 +1,9 @@ -/* eslint-env mocha */ -import 'babel-polyfill'; -import assert from 'assert'; +import { expect } from 'chai'; import { MentionsParser } from '../lib/MentionsParser'; let mentionsParser; -beforeEach(function functionName() { +beforeEach(() => { mentionsParser = new MentionsParser({ pattern: '[0-9a-zA-Z-_.]+', me: () => 'me', @@ -17,15 +15,15 @@ describe('Mention', function() { const regexp = '[0-9a-zA-Z-_.]+'; beforeEach(() => { mentionsParser.pattern = () => regexp; }); - describe('by function', function functionName() { + describe('by function', () => { it(`should be equal to ${ regexp }`, () => { - assert.equal(regexp, mentionsParser.pattern); + expect(regexp).to.be.equal(mentionsParser.pattern); }); }); - describe('by const', function functionName() { + describe('by const', () => { it(`should be equal to ${ regexp }`, () => { - assert.equal(regexp, mentionsParser.pattern); + expect(regexp).to.be.equal(mentionsParser.pattern); }); }); }); @@ -33,15 +31,15 @@ describe('Mention', function() { describe('get useRealName', () => { beforeEach(() => { mentionsParser.useRealName = () => true; }); - describe('by function', function functionName() { + describe('by function', () => { it('should be true', () => { - assert.equal(true, mentionsParser.useRealName); + expect(true).to.be.equal(mentionsParser.useRealName); }); }); - describe('by const', function functionName() { + describe('by const', () => { it('should be true', () => { - assert.equal(true, mentionsParser.useRealName); + expect(true).to.be.equal(mentionsParser.useRealName); }); }); }); @@ -49,24 +47,24 @@ describe('Mention', function() { describe('get me', () => { const me = 'me'; - describe('by function', function functionName() { + describe('by function', () => { beforeEach(() => { mentionsParser.me = () => me; }); it(`should be equal to ${ me }`, () => { - assert.equal(me, mentionsParser.me); + expect(me).to.be.equal(mentionsParser.me); }); }); - describe('by const', function functionName() { + describe('by const', () => { beforeEach(() => { mentionsParser.me = me; }); it(`should be equal to ${ me }`, () => { - assert.equal(me, mentionsParser.me); + expect(me).to.be.equal(mentionsParser.me); }); }); }); - describe('getUserMentions', function functionName() { + describe('getUserMentions', () => { describe('for simple text, no mentions', () => { const result = []; [ @@ -75,7 +73,7 @@ describe('Mention', function() { ] .forEach((text) => { it(`should return "${ JSON.stringify(result) }" from "${ text }"`, () => { - assert.deepEqual(result, mentionsParser.getUserMentions(text)); + expect(result).to.be.deep.equal(mentionsParser.getUserMentions(text)); }); }); }); @@ -93,20 +91,20 @@ describe('Mention', function() { ] .forEach((text) => { it(`should return "${ JSON.stringify(result) }" from "${ text }"`, () => { - assert.deepEqual(result, mentionsParser.getUserMentions(text)); + expect(result).to.be.deep.equal(mentionsParser.getUserMentions(text)); }); }); it.skip('should return without the "." from "@rocket.cat."', () => { - assert.deepEqual(result, mentionsParser.getUserMentions('@rocket.cat.')); + expect(result).to.be.deep.equal(mentionsParser.getUserMentions('@rocket.cat.')); }); it.skip('should return without the "_" from "@rocket.cat_"', () => { - assert.deepEqual(result, mentionsParser.getUserMentions('@rocket.cat_')); + expect(result).to.be.deep.equal(mentionsParser.getUserMentions('@rocket.cat_')); }); it.skip('should return without the "-" from "@rocket.cat-"', () => { - assert.deepEqual(result, mentionsParser.getUserMentions('@rocket.cat-')); + expect(result).to.be.deep.equal(mentionsParser.getUserMentions('@rocket.cat-')); }); }); @@ -121,13 +119,13 @@ describe('Mention', function() { ] .forEach((text) => { it(`should return "${ JSON.stringify(result) }" from "${ text }"`, () => { - assert.deepEqual(result, mentionsParser.getUserMentions(text)); + expect(result).to.be.deep.equal(mentionsParser.getUserMentions(text)); }); }); }); }); - describe('getChannelMentions', function functionName() { + describe('getChannelMentions', () => { describe('for simple text, no mentions', () => { const result = []; [ @@ -136,7 +134,7 @@ describe('Mention', function() { ] .forEach((text) => { it(`should return "${ JSON.stringify(result) }" from "${ text }"`, () => { - assert.deepEqual(result, mentionsParser.getChannelMentions(text)); + expect(result).to.be.deep.equal(mentionsParser.getChannelMentions(text)); }); }); }); @@ -151,20 +149,20 @@ describe('Mention', function() { 'hello #general, how are you?', ].forEach((text) => { it(`should return "${ JSON.stringify(result) }" from "${ text }"`, () => { - assert.deepEqual(result, mentionsParser.getChannelMentions(text)); + expect(result).to.be.deep.equal(mentionsParser.getChannelMentions(text)); }); }); it.skip('should return without the "." from "#general."', () => { - assert.deepEqual(result, mentionsParser.getUserMentions('#general.')); + expect(result).to.be.deep.equal(mentionsParser.getUserMentions('#general.')); }); it.skip('should return without the "_" from "#general_"', () => { - assert.deepEqual(result, mentionsParser.getUserMentions('#general_')); + expect(result).to.be.deep.equal(mentionsParser.getUserMentions('#general_')); }); it.skip('should return without the "-" from "#general."', () => { - assert.deepEqual(result, mentionsParser.getUserMentions('#general-')); + expect(result).to.be.deep.equal(mentionsParser.getUserMentions('#general-')); }); }); @@ -178,7 +176,7 @@ describe('Mention', function() { 'hello #general #other, how are you?', ].forEach((text) => { it(`should return "${ JSON.stringify(result) }" from "${ text }"`, () => { - assert.deepEqual(result, mentionsParser.getChannelMentions(text)); + expect(result).to.be.deep.equal(mentionsParser.getChannelMentions(text)); }); }); }); @@ -189,7 +187,7 @@ describe('Mention', function() { 'http://localhost/#general', ].forEach((text) => { it(`should return nothing from "${ text }"`, () => { - assert.deepEqual(result, mentionsParser.getChannelMentions(text)); + expect(result).to.be.deep.equal(mentionsParser.getChannelMentions(text)); }); }); }); @@ -200,7 +198,7 @@ describe('Mention', function() { 'http://localhost/#general #general', ].forEach((text) => { it(`should return "${ JSON.stringify(result) }" from "${ text }"`, () => { - assert.deepEqual(result, mentionsParser.getChannelMentions(text)); + expect(result).to.be.deep.equal(mentionsParser.getChannelMentions(text)); }); }); }); @@ -216,29 +214,29 @@ describe('replace methods', function() { describe('replaceUsers', () => { it('should render for @all', () => { const result = mentionsParser.replaceUsers('@all', message, 'me'); - assert.equal(result, 'all'); + expect(result).to.be.equal('all'); }); const str2 = 'rocket.cat'; it(`should render for "@${ str2 }"`, () => { const result = mentionsParser.replaceUsers(`@${ str2 }`, message, 'me'); - assert.equal(result, `${ str2 }`); + expect(result).to.be.equal(`${ str2 }`); }); it(`should render for "hello ${ str2 }"`, () => { const result = mentionsParser.replaceUsers(`hello @${ str2 }`, message, 'me'); - assert.equal(result, `hello ${ str2 }`); + expect(result).to.be.equal(`hello ${ str2 }`); }); it('should render for unknow/private user "hello @unknow"', () => { const result = mentionsParser.replaceUsers('hello @unknow', message, 'me'); - assert.equal(result, 'hello @unknow'); + expect(result).to.be.equal('hello @unknow'); }); it('should render for me', () => { const result = mentionsParser.replaceUsers('hello @me', message, 'me'); - assert.equal(result, 'hello me'); + expect(result).to.be.equal('hello me'); }); }); @@ -249,7 +247,7 @@ describe('replace methods', function() { it('should render for @all', () => { const result = mentionsParser.replaceUsers('@all', message, 'me'); - assert.equal(result, 'all'); + expect(result).to.be.equal('all'); }); const str2 = 'rocket.cat'; @@ -257,12 +255,12 @@ describe('replace methods', function() { it(`should render for "@${ str2 }"`, () => { const result = mentionsParser.replaceUsers(`@${ str2 }`, message, 'me'); - assert.equal(result, `${ str2Name }`); + expect(result).to.be.equal(`${ str2Name }`); }); it(`should render for "hello @${ str2 }"`, () => { const result = mentionsParser.replaceUsers(`hello @${ str2 }`, message, 'me'); - assert.equal(result, `hello ${ str2Name }`); + expect(result).to.be.equal(`hello ${ str2Name }`); }); const specialchars = 'specialchars'; @@ -270,46 +268,46 @@ describe('replace methods', function() { it(`should escape special characters in "hello @${ specialchars }"`, () => { const result = mentionsParser.replaceUsers(`hello @${ specialchars }`, message, 'me'); - assert.equal(result, `hello ${ specialcharsName }`); + expect(result).to.be.equal(`hello ${ specialcharsName }`); }); it(`should render for "hello
    @${ str2 }
    "`, () => { const result = mentionsParser.replaceUsers(`hello
    @${ str2 }
    `, message, 'me'); - assert.equal(result, `hello
    ${ str2Name }
    `); + expect(result).to.be.equal(`hello
    ${ str2Name }
    `); }); it('should render for unknow/private user "hello @unknow"', () => { const result = mentionsParser.replaceUsers('hello @unknow', message, 'me'); - assert.equal(result, 'hello @unknow'); + expect(result).to.be.equal('hello @unknow'); }); it('should render for me', () => { const result = mentionsParser.replaceUsers('hello @me', message, 'me'); - assert.equal(result, 'hello Me'); + expect(result).to.be.equal('hello Me'); }); }); describe('replaceChannels', () => { it('should render for #general', () => { const result = mentionsParser.replaceChannels('#general', message); - assert.equal('#general', result); + expect('<).to.be.equal(class="mention-link mention-link--room" data-channel="42">#general', result); }); const str2 = '#rocket.cat'; it(`should render for ${ str2 }`, () => { const result = mentionsParser.replaceChannels(str2, message); - assert.equal(result, `${ str2 }`); + expect(result).to.be.equal(`${ str2 }`); }); it(`should render for "hello ${ str2 }"`, () => { const result = mentionsParser.replaceChannels(`hello ${ str2 }`, message); - assert.equal(result, `hello ${ str2 }`); + expect(result).to.be.equal(`hello ${ str2 }`); }); it('should render for unknow/private channel "hello #unknow"', () => { const result = mentionsParser.replaceChannels('hello #unknow', message); - assert.equal(result, 'hello #unknow'); + expect(result).to.be.equal('hello #unknow'); }); }); @@ -317,25 +315,25 @@ describe('replace methods', function() { it('should render for #general', () => { message.html = '#general'; const result = mentionsParser.parse(message, 'me'); - assert.equal(result.html, '#general'); + expect(result.html).to.be.equal('#general'); }); it('should render for "#general and @rocket.cat', () => { message.html = '#general and @rocket.cat'; const result = mentionsParser.parse(message, 'me'); - assert.equal(result.html, '#general and rocket.cat'); + expect(result.html).to.be.equal('#general and rocket.cat'); }); it('should render for "', () => { message.html = ''; const result = mentionsParser.parse(message, 'me'); - assert.equal(result.html, ''); + expect(result.html).to.be.equal(''); }); it('should render for "simple text', () => { message.html = 'simple text'; const result = mentionsParser.parse(message, 'me'); - assert.equal(result.html, 'simple text'); + expect(result.html).to.be.equal('simple text'); }); }); @@ -347,25 +345,25 @@ describe('replace methods', function() { it('should render for #general', () => { message.html = '#general'; const result = mentionsParser.parse(message, 'me'); - assert.equal(result.html, '#general'); + expect(result.html).to.be.equal('#general'); }); it('should render for "#general and @rocket.cat', () => { message.html = '#general and @rocket.cat'; const result = mentionsParser.parse(message, 'me'); - assert.equal(result.html, '#general and Rocket.Cat'); + expect(result.html).to.be.equal('#general and Rocket.Cat'); }); it('should render for "', () => { message.html = ''; const result = mentionsParser.parse(message, 'me'); - assert.equal(result.html, ''); + expect(result.html).to.be.equal(''); }); it('should render for "simple text', () => { message.html = 'simple text'; const result = mentionsParser.parse(message, 'me'); - assert.equal(result.html, 'simple text'); + expect(result.html).to.be.equal('simple text'); }); }); }); diff --git a/app/mentions/tests/server.tests.js b/app/mentions/tests/server.tests.js index 30bc075649840..a1a77bac30584 100644 --- a/app/mentions/tests/server.tests.js +++ b/app/mentions/tests/server.tests.js @@ -1,6 +1,4 @@ -/* eslint-env mocha */ -import 'babel-polyfill'; -import assert from 'assert'; +import { expect } from 'chai'; import MentionsServer from '../server/Mentions'; @@ -43,7 +41,7 @@ describe('Mention Server', () => { }; const expected = []; const result = mention.getUsersByMentions(message); - assert.deepEqual(expected, result); + expect(expected).to.be.deep.equal(result); }); }); describe('for one user', () => { @@ -69,7 +67,7 @@ describe('Mention Server', () => { username: 'all', }]; const result = mention.getUsersByMentions(message); - assert.deepEqual(expected, result); + expect(expected).to.be.deep.equal(result); }); it('should return "here"', () => { const message = { @@ -80,7 +78,7 @@ describe('Mention Server', () => { username: 'here', }]; const result = mention.getUsersByMentions(message); - assert.deepEqual(expected, result); + expect(expected).to.be.deep.equal(result); }); it('should return "rocket.cat"', () => { const message = { @@ -91,7 +89,7 @@ describe('Mention Server', () => { username: 'rocket.cat', }]; const result = mention.getUsersByMentions(message); - assert.deepEqual(expected, result); + expect(expected).to.be.deep.equal(result); }); }); describe('for two user', () => { @@ -107,7 +105,7 @@ describe('Mention Server', () => { username: 'here', }]; const result = mention.getUsersByMentions(message); - assert.deepEqual(expected, result); + expect(expected).to.be.deep.equal(result); }); it('should return "here and rocket.cat"', () => { const message = { @@ -121,7 +119,7 @@ describe('Mention Server', () => { username: 'rocket.cat', }]; const result = mention.getUsersByMentions(message); - assert.deepEqual(expected, result); + expect(expected).to.be.deep.equal(result); }); it('should return "here, rocket.cat, jon"', () => { @@ -139,7 +137,7 @@ describe('Mention Server', () => { username: 'jon', }]; const result = mention.getUsersByMentions(message); - assert.deepEqual(expected, result); + expect(expected).to.be.deep.equal(result); }); }); @@ -150,7 +148,7 @@ describe('Mention Server', () => { }; const expected = []; const result = mention.getUsersByMentions(message); - assert.deepEqual(expected, result); + expect(expected).to.be.deep.equal(result); }); }); }); @@ -164,7 +162,7 @@ describe('Mention Server', () => { name: 'general', }]; const result = mention.getChannelbyMentions(message); - assert.deepEqual(result, expected); + expect(result).to.be.deep.equal(expected); }); it('should return nothing"', () => { const message = { @@ -172,7 +170,7 @@ describe('Mention Server', () => { }; const expected = []; const result = mention.getChannelbyMentions(message); - assert.deepEqual(result, expected); + expect(result).to.be.deep.equal(expected); }); }); describe('execute', () => { @@ -185,7 +183,7 @@ describe('Mention Server', () => { name: 'general', }]; const result = mention.getChannelbyMentions(message); - assert.deepEqual(result, expected); + expect(result).to.be.deep.equal(expected); }); it('should return nothing"', () => { const message = { @@ -197,7 +195,7 @@ describe('Mention Server', () => { channels: [], }; const result = mention.execute(message); - assert.deepEqual(result, expected); + expect(result).to.be.deep.equal(expected); }); }); @@ -207,13 +205,13 @@ describe('Mention Server', () => { describe('constant', () => { it('should return the informed value', () => { mention.messageMaxAll = 4; - assert.deepEqual(mention.messageMaxAll, 4); + expect(mention.messageMaxAll).to.be.deep.equal(4); }); }); describe('function', () => { it('should return the informed value', () => { mention.messageMaxAll = () => 4; - assert.deepEqual(mention.messageMaxAll, 4); + expect(mention.messageMaxAll).to.be.deep.equal(4); }); }); }); @@ -222,13 +220,13 @@ describe('Mention Server', () => { describe('constant', () => { it('should return the informed value', () => { mention.getUsers = 4; - assert.deepEqual(mention.getUsers(), 4); + expect(mention.getUsers()).to.be.deep.equal(4); }); }); describe('function', () => { it('should return the informed value', () => { mention.getUsers = () => 4; - assert.deepEqual(mention.getUsers(), 4); + expect(mention.getUsers()).to.be.deep.equal(4); }); }); }); @@ -237,13 +235,13 @@ describe('Mention Server', () => { describe('constant', () => { it('should return the informed value', () => { mention.getChannels = 4; - assert.deepEqual(mention.getChannels(), 4); + expect(mention.getChannels()).to.be.deep.equal(4); }); }); describe('function', () => { it('should return the informed value', () => { mention.getChannels = () => 4; - assert.deepEqual(mention.getChannels(), 4); + expect(mention.getChannels()).to.be.deep.equal(4); }); }); }); @@ -252,13 +250,13 @@ describe('Mention Server', () => { describe('constant', () => { it('should return the informed value', () => { mention.getChannel = true; - assert.deepEqual(mention.getChannel(), true); + expect(mention.getChannel()).to.be.deep.equal(true); }); }); describe('function', () => { it('should return the informed value', () => { mention.getChannel = () => true; - assert.deepEqual(mention.getChannel(), true); + expect(mention.getChannel()).to.be.deep.equal(true); }); }); }); diff --git a/app/meteor-accounts-saml/tests/server.tests.ts b/app/meteor-accounts-saml/tests/server.tests.ts index 25a512fffe835..2b64134e22a94 100644 --- a/app/meteor-accounts-saml/tests/server.tests.ts +++ b/app/meteor-accounts-saml/tests/server.tests.ts @@ -1,7 +1,4 @@ -/* eslint-env mocha */ -import 'babel-polyfill'; - -import chai from 'chai'; +import { expect } from 'chai'; import '../../lib/tests/server.mocks.js'; import { AuthorizeRequest } from '../server/lib/generators/AuthorizeRequest'; @@ -39,8 +36,6 @@ import { privateKey, } from './data'; -const { expect } = chai; - describe('SAML', () => { describe('[AuthorizeRequest]', () => { describe('AuthorizeRequest.generate', () => { diff --git a/app/models/server/raw/Sessions.tests.js b/app/models/server/raw/Sessions.tests.js index 7dd157e9e6819..8b284009c5a4b 100644 --- a/app/models/server/raw/Sessions.tests.js +++ b/app/models/server/raw/Sessions.tests.js @@ -1,7 +1,4 @@ -/* eslint-env mocha */ - -import assert from 'assert'; - +import { expect } from 'chai'; import { MongoMemoryServer } from 'mongodb-memory-server'; const { MongoClient } = require('mongodb'); @@ -280,14 +277,14 @@ describe('Sessions Aggregates', () => { it('should have sessions_dates data saved', () => { const collection = db.collection('sessions_dates'); return collection.find().toArray() - .then((docs) => assert.strictEqual(docs.length, DATA.sessions_dates.length)); + .then((docs) => expect(docs.length).to.be.equal(DATA.sessions_dates.length)); }); it('should match sessions between 2018-12-11 and 2019-1-10', () => { const collection = db.collection('sessions_dates'); const $match = aggregates.getMatchOfLastMonthOrWeek({ year: 2019, month: 1, day: 10 }); - assert.deepStrictEqual($match, { + expect($match).to.be.deep.equal({ $and: [{ $or: [ { year: { $gt: 2018 } }, @@ -307,8 +304,8 @@ describe('Sessions Aggregates', () => { $match, }]).toArray() .then((docs) => { - assert.strictEqual(docs.length, 31); - assert.deepStrictEqual(docs, [ + expect(docs.length).to.be.equal(31); + expect(docs).to.be.deep.equal([ { _id: '2018-12-11', year: 2018, month: 12, day: 11 }, { _id: '2018-12-12', year: 2018, month: 12, day: 12 }, { _id: '2018-12-13', year: 2018, month: 12, day: 13 }, @@ -348,7 +345,7 @@ describe('Sessions Aggregates', () => { const collection = db.collection('sessions_dates'); const $match = aggregates.getMatchOfLastMonthOrWeek({ year: 2019, month: 2, day: 10 }); - assert.deepStrictEqual($match, { + expect($match).to.be.deep.equal({ year: 2019, $and: [{ $or: [ @@ -367,8 +364,8 @@ describe('Sessions Aggregates', () => { $match, }]).toArray() .then((docs) => { - assert.strictEqual(docs.length, 31); - assert.deepStrictEqual(docs, [ + expect(docs.length).to.be.deep.equal(31); + expect(docs).to.be.deep.equal([ { _id: '2019-1-11', year: 2019, month: 1, day: 11 }, { _id: '2019-1-12', year: 2019, month: 1, day: 12 }, { _id: '2019-1-13', year: 2019, month: 1, day: 13 }, @@ -408,7 +405,7 @@ describe('Sessions Aggregates', () => { const collection = db.collection('sessions_dates'); const $match = aggregates.getMatchOfLastMonthOrWeek({ year: 2019, month: 5, day: 31 }); - assert.deepStrictEqual($match, { + expect($match).to.be.deep.equal({ year: 2019, month: 5, day: { $gte: 1, $lte: 31 }, @@ -418,8 +415,8 @@ describe('Sessions Aggregates', () => { $match, }]).toArray() .then((docs) => { - assert.strictEqual(docs.length, 31); - assert.deepStrictEqual(docs, [ + expect(docs.length).to.be.equal(31); + expect(docs).to.be.deep.equal([ { _id: '2019-5-1', year: 2019, month: 5, day: 1 }, { _id: '2019-5-2', year: 2019, month: 5, day: 2 }, { _id: '2019-5-3', year: 2019, month: 5, day: 3 }, @@ -459,7 +456,7 @@ describe('Sessions Aggregates', () => { const collection = db.collection('sessions_dates'); const $match = aggregates.getMatchOfLastMonthOrWeek({ year: 2019, month: 4, day: 30 }); - assert.deepStrictEqual($match, { + expect($match).to.be.deep.equal({ year: 2019, month: 4, day: { $gte: 1, $lte: 30 }, @@ -469,8 +466,8 @@ describe('Sessions Aggregates', () => { $match, }]).toArray() .then((docs) => { - assert.strictEqual(docs.length, 30); - assert.deepStrictEqual(docs, [ + expect(docs.length).to.be.equal(30); + expect(docs).to.be.deep.equal([ { _id: '2019-4-1', year: 2019, month: 4, day: 1 }, { _id: '2019-4-2', year: 2019, month: 4, day: 2 }, { _id: '2019-4-3', year: 2019, month: 4, day: 3 }, @@ -509,7 +506,7 @@ describe('Sessions Aggregates', () => { const collection = db.collection('sessions_dates'); const $match = aggregates.getMatchOfLastMonthOrWeek({ year: 2019, month: 2, day: 28 }); - assert.deepStrictEqual($match, { + expect($match).to.be.deep.equal({ year: 2019, month: 2, day: { $gte: 1, $lte: 28 }, @@ -519,8 +516,8 @@ describe('Sessions Aggregates', () => { $match, }]).toArray() .then((docs) => { - assert.strictEqual(docs.length, 28); - assert.deepStrictEqual(docs, [ + expect(docs.length).to.be.equal(28); + expect(docs).to.be.deep.equal([ { _id: '2019-2-1', year: 2019, month: 2, day: 1 }, { _id: '2019-2-2', year: 2019, month: 2, day: 2 }, { _id: '2019-2-3', year: 2019, month: 2, day: 3 }, @@ -557,7 +554,7 @@ describe('Sessions Aggregates', () => { const collection = db.collection('sessions_dates'); const $match = aggregates.getMatchOfLastMonthOrWeek({ year: 2019, month: 2, day: 27 }); - assert.deepStrictEqual($match, { + expect($match).to.be.deep.equal({ year: 2019, $and: [{ $or: [ @@ -576,8 +573,8 @@ describe('Sessions Aggregates', () => { $match, }]).toArray() .then((docs) => { - assert.strictEqual(docs.length, 31); - assert.deepStrictEqual(docs, [ + expect(docs.length).to.be.equal(31); + expect(docs).to.be.deep.equal([ { _id: '2019-1-28', year: 2019, month: 1, day: 28 }, { _id: '2019-1-29', year: 2019, month: 1, day: 29 }, { _id: '2019-1-30', year: 2019, month: 1, day: 30 }, @@ -616,7 +613,7 @@ describe('Sessions Aggregates', () => { it('should have sessions data saved', () => { const collection = db.collection('sessions'); return collection.find().toArray() - .then((docs) => assert.strictEqual(docs.length, DATA.sessions.length)); + .then((docs) => expect(docs.length).to.be.equal(DATA.sessions.length)); }); it('should generate daily sessions', () => { @@ -629,8 +626,8 @@ describe('Sessions Aggregates', () => { await collection.insertMany(docs); - assert.strictEqual(docs.length, 3); - assert.deepStrictEqual(docs, [{ + expect(docs.length).to.be.equal(3); + expect(docs).to.be.deep.equal([{ _id: 'xPZXw9xqM3kKshsse-2019-5-2', time: 5814, sessions: 3, @@ -726,8 +723,8 @@ describe('Sessions Aggregates', () => { const collection = db.collection('sessions'); return aggregates.getUniqueUsersOfLastMonthOrWeek(collection, { year: 2019, month: 5, day: 31 }) .then((docs) => { - assert.strictEqual(docs.length, 1); - assert.deepStrictEqual(docs, [{ + expect(docs.length).to.be.equal(1); + expect(docs).to.be.deep.equal([{ count: 2, roles: [{ count: 1, @@ -750,8 +747,8 @@ describe('Sessions Aggregates', () => { const collection = db.collection('sessions'); return aggregates.getUniqueUsersOfYesterday(collection, { year: 2019, month: 5, day: 1 }) .then((docs) => { - assert.strictEqual(docs.length, 1); - assert.deepStrictEqual(docs, [{ + expect(docs.length).to.be.equal(1); + expect(docs).to.be.deep.equal([{ count: 1, roles: [{ count: 1, @@ -769,8 +766,8 @@ describe('Sessions Aggregates', () => { const collection = db.collection('sessions'); return aggregates.getUniqueUsersOfYesterday(collection, { year: 2019, month: 5, day: 2 }) .then((docs) => { - assert.strictEqual(docs.length, 1); - assert.deepStrictEqual(docs, [{ + expect(docs.length).to.be.equal(1); + expect(docs).to.be.deep.equal([{ count: 1, roles: [{ count: 1, @@ -788,8 +785,8 @@ describe('Sessions Aggregates', () => { const collection = db.collection('sessions'); return aggregates.getUniqueDevicesOfLastMonthOrWeek(collection, { year: 2019, month: 5, day: 31 }) .then((docs) => { - assert.strictEqual(docs.length, 2); - assert.deepStrictEqual(docs, [{ + expect(docs.length).to.be.equal(2); + expect(docs).to.be.deep.equal([{ count: 3, time: 9695, type: 'browser', @@ -809,8 +806,8 @@ describe('Sessions Aggregates', () => { const collection = db.collection('sessions'); return aggregates.getUniqueDevicesOfYesterday(collection, { year: 2019, month: 5, day: 2 }) .then((docs) => { - assert.strictEqual(docs.length, 2); - assert.deepStrictEqual(docs, [{ + expect(docs.length).to.be.equal(2); + expect(docs).to.be.deep.equal([{ count: 2, time: 5528, type: 'browser', @@ -830,8 +827,8 @@ describe('Sessions Aggregates', () => { const collection = db.collection('sessions'); return aggregates.getUniqueOSOfLastMonthOrWeek(collection, { year: 2019, month: 5, day: 31 }) .then((docs) => { - assert.strictEqual(docs.length, 2); - assert.deepStrictEqual(docs, [{ + expect(docs.length).to.be.equal(2); + expect(docs).to.be.deep.equal([{ count: 3, time: 9695, name: 'Mac OS', @@ -849,8 +846,8 @@ describe('Sessions Aggregates', () => { const collection = db.collection('sessions'); return aggregates.getUniqueOSOfYesterday(collection, { year: 2019, month: 5, day: 2 }) .then((docs) => { - assert.strictEqual(docs.length, 2); - assert.deepStrictEqual(docs, [{ + expect(docs.length).to.be.equal(2); + expect(docs).to.be.deep.equal([{ count: 2, time: 5528, name: 'Mac OS', @@ -868,7 +865,7 @@ describe('Sessions Aggregates', () => { const collection = db.collection('sessions_dates'); const $match = aggregates.getMatchOfLastMonthOrWeek({ year: 2019, month: 1, day: 4, type: 'week' }); - assert.deepStrictEqual($match, { + expect($match).to.be.deep.equal({ $and: [{ $or: [ { year: { $gt: 2018 } }, @@ -888,8 +885,8 @@ describe('Sessions Aggregates', () => { $match, }]).toArray() .then((docs) => { - assert.strictEqual(docs.length, 7); - assert.deepStrictEqual(docs, [ + expect(docs.length).to.be.equal(7); + expect(docs).to.be.deep.equal([ { _id: '2018-12-29', year: 2018, month: 12, day: 29 }, { _id: '2018-12-30', year: 2018, month: 12, day: 30 }, { _id: '2018-12-31', year: 2018, month: 12, day: 31 }, @@ -905,7 +902,7 @@ describe('Sessions Aggregates', () => { const collection = db.collection('sessions_dates'); const $match = aggregates.getMatchOfLastMonthOrWeek({ year: 2019, month: 2, day: 4, type: 'week' }); - assert.deepStrictEqual($match, { + expect($match).to.be.deep.equal({ year: 2019, $and: [{ $or: [ @@ -924,8 +921,8 @@ describe('Sessions Aggregates', () => { $match, }]).toArray() .then((docs) => { - assert.strictEqual(docs.length, 7); - assert.deepStrictEqual(docs, [ + expect(docs.length).to.be.equal(7); + expect(docs).to.be.deep.equal([ { _id: '2019-1-29', year: 2019, month: 1, day: 29 }, { _id: '2019-1-30', year: 2019, month: 1, day: 30 }, { _id: '2019-1-31', year: 2019, month: 1, day: 31 }, @@ -941,7 +938,7 @@ describe('Sessions Aggregates', () => { const collection = db.collection('sessions_dates'); const $match = aggregates.getMatchOfLastMonthOrWeek({ year: 2019, month: 5, day: 7, type: 'week' }); - assert.deepStrictEqual($match, { + expect($match).to.be.deep.equal({ year: 2019, month: 5, day: { $gte: 1, $lte: 7 }, @@ -951,8 +948,8 @@ describe('Sessions Aggregates', () => { $match, }]).toArray() .then((docs) => { - assert.strictEqual(docs.length, 7); - assert.deepStrictEqual(docs, [ + expect(docs.length).to.be.equal(7); + expect(docs).to.be.deep.equal([ { _id: '2019-5-1', year: 2019, month: 5, day: 1 }, { _id: '2019-5-2', year: 2019, month: 5, day: 2 }, { _id: '2019-5-3', year: 2019, month: 5, day: 3 }, @@ -968,7 +965,7 @@ describe('Sessions Aggregates', () => { const collection = db.collection('sessions_dates'); const $match = aggregates.getMatchOfLastMonthOrWeek({ year: 2019, month: 5, day: 14, type: 'week' }); - assert.deepStrictEqual($match, { + expect($match).to.be.deep.equal({ year: 2019, month: 5, day: { $gte: 8, $lte: 14 }, @@ -978,8 +975,8 @@ describe('Sessions Aggregates', () => { $match, }]).toArray() .then((docs) => { - assert.strictEqual(docs.length, 7); - assert.deepStrictEqual(docs, [ + expect(docs.length).to.be.equal(7); + expect(docs).to.be.deep.equal([ { _id: '2019-5-8', year: 2019, month: 5, day: 8 }, { _id: '2019-5-9', year: 2019, month: 5, day: 9 }, { _id: '2019-5-10', year: 2019, month: 5, day: 10 }, @@ -995,7 +992,7 @@ describe('Sessions Aggregates', () => { const collection = db.collection('sessions'); return aggregates.getUniqueUsersOfLastMonthOrWeek(collection, { year: 2019, month: 5, day: 31, type: 'week' }) .then((docs) => { - assert.strictEqual(docs.length, 0); + expect(docs.length).to.be.equal(0); }); }); @@ -1003,8 +1000,8 @@ describe('Sessions Aggregates', () => { const collection = db.collection('sessions'); return aggregates.getUniqueUsersOfLastMonthOrWeek(collection, { year: 2019, month: 5, day: 7, type: 'week' }) .then((docs) => { - assert.strictEqual(docs.length, 1); - assert.deepStrictEqual(docs, [{ + expect(docs.length).to.be.equal(1); + expect(docs).to.be.deep.equal([{ count: 2, roles: [{ count: 1, @@ -1027,8 +1024,8 @@ describe('Sessions Aggregates', () => { const collection = db.collection('sessions'); return aggregates.getUniqueDevicesOfLastMonthOrWeek(collection, { year: 2019, month: 5, day: 7, type: 'week' }) .then((docs) => { - assert.strictEqual(docs.length, 2); - assert.deepStrictEqual(docs, [{ + expect(docs.length).to.be.equal(2); + expect(docs).to.be.deep.equal([{ count: 3, time: 9695, type: 'browser', @@ -1048,8 +1045,8 @@ describe('Sessions Aggregates', () => { const collection = db.collection('sessions'); return aggregates.getUniqueOSOfLastMonthOrWeek(collection, { year: 2019, month: 5, day: 7 }) .then((docs) => { - assert.strictEqual(docs.length, 2); - assert.deepStrictEqual(docs, [{ + expect(docs.length).to.be.equal(2); + expect(docs).to.be.deep.equal([{ count: 3, time: 9695, name: 'Mac OS', diff --git a/app/settings/server/functions/getSettingDefaults.tests.ts b/app/settings/server/functions/getSettingDefaults.tests.ts index 35d5f3f5c0291..63581c43ce69d 100644 --- a/app/settings/server/functions/getSettingDefaults.tests.ts +++ b/app/settings/server/functions/getSettingDefaults.tests.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/camelcase */ -/* eslint-env mocha */ import { expect } from 'chai'; import { getSettingDefaults } from './getSettingDefaults'; diff --git a/app/settings/server/functions/overrideGenerator.tests.ts b/app/settings/server/functions/overrideGenerator.tests.ts index a1881527ed84d..776cf8ce2cf95 100644 --- a/app/settings/server/functions/overrideGenerator.tests.ts +++ b/app/settings/server/functions/overrideGenerator.tests.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/camelcase */ -/* eslint-env mocha */ import { expect } from 'chai'; import { getSettingDefaults } from './getSettingDefaults'; diff --git a/app/settings/server/functions/settings.tests.ts b/app/settings/server/functions/settings.tests.ts index 4028486beb2a6..55692c7dc6d55 100644 --- a/app/settings/server/functions/settings.tests.ts +++ b/app/settings/server/functions/settings.tests.ts @@ -1,14 +1,10 @@ /* eslint-disable @typescript-eslint/camelcase */ -/* eslint-env mocha */ -import chai, { expect } from 'chai'; -import spies from 'chai-spies'; +import { expect, spy } from 'chai'; import { Settings } from './settings.mocks'; import { SettingsRegistry } from '../SettingsRegistry'; import { CachedSettings } from '../CachedSettings'; -chai.use(spies); - describe('Settings', () => { beforeEach(() => { Settings.insertCalls = 0; @@ -306,8 +302,8 @@ describe('Settings', () => { settings.initilized(); const settingsRegistry = new SettingsRegistry({ store: settings, model: Settings as any }); - const spy = chai.spy(); - const spy2 = chai.spy(); + const spiedCallback1 = spy(); + const spiedCallback2 = spy(); settingsRegistry.addGroup('group', function() { this.section('section', function() { @@ -317,27 +313,27 @@ describe('Settings', () => { }); }); - settings.watch('setting_callback', spy, { debounce: 10 }); - settings.watchByRegex(/setting_callback/, spy2, { debounce: 10 }); + settings.watch('setting_callback', spiedCallback1, { debounce: 10 }); + settings.watchByRegex(/setting_callback/, spiedCallback2, { debounce: 10 }); setTimeout(() => { - expect(spy).to.have.been.called.exactly(1); - expect(spy2).to.have.been.called.exactly(1); - expect(spy).to.have.been.called.always.with('value1'); - expect(spy2).to.have.been.called.always.with('setting_callback', 'value1'); + expect(spiedCallback1).to.have.been.called.exactly(1); + expect(spiedCallback2).to.have.been.called.exactly(1); + expect(spiedCallback1).to.have.been.called.always.with('value1'); + expect(spiedCallback2).to.have.been.called.always.with('setting_callback', 'value1'); done(); }, settings.getConfig({ debounce: 10 }).debounce); }); it('should call `settings.watch` callback on setting changed registering before initialized', (done) => { - const spy = chai.spy(); - const spy2 = chai.spy(); + const spiedCallback1 = spy(); + const spiedCallback2 = spy(); const settings = new CachedSettings(); Settings.settings = settings; const settingsRegistry = new SettingsRegistry({ store: settings, model: Settings as any }); - settings.watch('setting_callback', spy, { debounce: 1 }); - settings.watchByRegex(/setting_callback/ig, spy2, { debounce: 1 }); + settings.watch('setting_callback', spiedCallback1, { debounce: 1 }); + settings.watchByRegex(/setting_callback/ig, spiedCallback2, { debounce: 1 }); settings.initilized(); settingsRegistry.addGroup('group', function() { @@ -350,10 +346,10 @@ describe('Settings', () => { setTimeout(() => { Settings.updateValueById('setting_callback', 'value3'); setTimeout(() => { - expect(spy).to.have.been.called.exactly(2); - expect(spy2).to.have.been.called.exactly(2); - expect(spy).to.have.been.called.with('value2'); - expect(spy).to.have.been.called.with('value3'); + expect(spiedCallback1).to.have.been.called.exactly(2); + expect(spiedCallback2).to.have.been.called.exactly(2); + expect(spiedCallback1).to.have.been.called.with('value2'); + expect(spiedCallback1).to.have.been.called.with('value3'); done(); }, settings.getConfig({ debounce: 10 }).debounce); }, settings.getConfig({ debounce: 10 }).debounce); diff --git a/app/settings/server/functions/validateSettings.tests.ts b/app/settings/server/functions/validateSettings.tests.ts index 8891afcf6947b..ba7d28f793f89 100644 --- a/app/settings/server/functions/validateSettings.tests.ts +++ b/app/settings/server/functions/validateSettings.tests.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/camelcase */ -/* eslint-env mocha */ import { expect } from 'chai'; import { validateSetting } from './validateSetting'; diff --git a/app/settings/server/raw.tests.js b/app/settings/server/raw.tests.js index 7a9d6bccd6cf9..ecd1aeada491f 100644 --- a/app/settings/server/raw.tests.js +++ b/app/settings/server/raw.tests.js @@ -1,22 +1,18 @@ -/* eslint-env mocha */ -import chai, { expect } from 'chai'; -import spies from 'chai-spies'; +import { expect, spy } from 'chai'; import rewire from 'rewire'; -chai.use(spies); - describe('Raw Settings', () => { let rawModule; const cache = new Map(); before('rewire deps', () => { - const spy = chai.spy(async (id) => { + const spied = spy(async (id) => { if (id === '1') { return 'some-setting-value'; } return null; }); rawModule = rewire('./raw'); - rawModule.__set__('setFromDB', spy); + rawModule.__set__('setFromDB', spied); rawModule.__set__('cache', cache); }); diff --git a/app/statistics/server/lib/UAParserCustom.tests.js b/app/statistics/server/lib/UAParserCustom.tests.js index 0d7b2da16f3d9..18338bc051046 100644 --- a/app/statistics/server/lib/UAParserCustom.tests.js +++ b/app/statistics/server/lib/UAParserCustom.tests.js @@ -1,5 +1,3 @@ -/* eslint-env mocha */ - import { expect } from 'chai'; import { UAParserMobile, UAParserDesktop } from './UAParserCustom'; diff --git a/app/statistics/server/lib/getMostImportantRole.tests.js b/app/statistics/server/lib/getMostImportantRole.tests.js index 464f14b0e5744..5d3c7a6efa843 100644 --- a/app/statistics/server/lib/getMostImportantRole.tests.js +++ b/app/statistics/server/lib/getMostImportantRole.tests.js @@ -1,5 +1,3 @@ -/* eslint-env mocha */ - import { expect } from 'chai'; import { getMostImportantRole } from './getMostImportantRole'; diff --git a/app/ui-utils/tests/server.tests.js b/app/ui-utils/tests/server.tests.js index a1592b60602a5..5f2dfee9e8911 100644 --- a/app/ui-utils/tests/server.tests.js +++ b/app/ui-utils/tests/server.tests.js @@ -1,6 +1,4 @@ -/* eslint-env mocha */ -import 'babel-polyfill'; -import assert from 'assert'; +import { expect } from 'chai'; import './server.mocks.js'; import { messageProperties } from '../lib/MessageProperties'; @@ -16,7 +14,7 @@ describe('Message Properties', () => { describe('Check Message Length', () => { Object.keys(messages).forEach((objectKey) => { it('should treat emojis as single characters', () => { - assert.equal(messageProperties.length(objectKey), messages[objectKey]); + expect(messageProperties.length(objectKey)).to.be.equal(messages[objectKey]); }); }); }); diff --git a/app/ui/client/views/app/tests/helpers.tests.js b/app/ui/client/views/app/tests/helpers.tests.js index 110a050454ebe..77487ec1ea4cc 100644 --- a/app/ui/client/views/app/tests/helpers.tests.js +++ b/app/ui/client/views/app/tests/helpers.tests.js @@ -1,6 +1,4 @@ -/* eslint-env mocha */ -import 'babel-polyfill'; -import assert from 'assert'; +import { expect } from 'chai'; import { timeAgo } from '../helpers'; @@ -16,9 +14,9 @@ describe('Helpers', () => { const func = (a) => a; - assert.equal(timeAgo(t1, func, now), '1:00 AM'); - assert.equal(timeAgo(t2, func, now), '10:00 AM'); - assert.equal(timeAgo(t3, func, now), '2:30 PM'); + expect(timeAgo(t1, func, now)).to.be.equal('1:00 AM'); + expect(timeAgo(t2, func, now)).to.be.equal('10:00 AM'); + expect(timeAgo(t3, func, now)).to.be.equal('2:30 PM'); }); it('returns "yesterday" when the passed value is on the day before', () => { @@ -30,9 +28,9 @@ describe('Helpers', () => { const func = (a) => a; - assert.equal(timeAgo(t1, func, now), 'yesterday'); - assert.equal(timeAgo(t2, func, now), 'yesterday'); - assert.equal(timeAgo(t3, func, now), 'yesterday'); + expect(timeAgo(t1, func, now)).to.be.equal('yesterday'); + expect(timeAgo(t2, func, now)).to.be.equal('yesterday'); + expect(timeAgo(t3, func, now)).to.be.equal('yesterday'); }); it('returns formated date when the passed value two or more days before', () => { @@ -46,11 +44,11 @@ describe('Helpers', () => { const func = () => 'should not be called'; - assert.equal(timeAgo(t1, func, now), 'Jun 18, 2018'); - assert.equal(timeAgo(t2, func, now), 'Jun 10, 2018'); - assert.equal(timeAgo(t3, func, now), 'May 10, 2018'); - assert.equal(timeAgo(t4, func, now), 'May 20, 2018'); - assert.equal(timeAgo(t5, func, now), 'Nov 10, 2017'); + expect(timeAgo(t1, func, now)).to.be.equal('Jun 18, 2018'); + expect(timeAgo(t2, func, now)).to.be.equal('Jun 10, 2018'); + expect(timeAgo(t3, func, now)).to.be.equal('May 10, 2018'); + expect(timeAgo(t4, func, now)).to.be.equal('May 20, 2018'); + expect(timeAgo(t5, func, now)).to.be.equal('Nov 10, 2017'); }); }); }); diff --git a/app/utils/lib/getURL.tests.js b/app/utils/lib/getURL.tests.js index 5ef7af6a4888f..1cccb926d941a 100644 --- a/app/utils/lib/getURL.tests.js +++ b/app/utils/lib/getURL.tests.js @@ -1,7 +1,4 @@ -/* eslint-env mocha */ -import 'babel-polyfill'; -import assert from 'assert'; - +import { expect } from 'chai'; import s from 'underscore.string'; import { _getURL } from './getURL'; @@ -12,17 +9,17 @@ const testPaths = (o, _processPath) => { processPath = (path) => _processPath(o._root_url_path_prefix + path); } - assert.equal(_getURL('', o), processPath('')); - assert.equal(_getURL('/', o), processPath('')); - assert.equal(_getURL('//', o), processPath('')); - assert.equal(_getURL('///', o), processPath('')); - assert.equal(_getURL('/channel', o), processPath('/channel')); - assert.equal(_getURL('/channel/', o), processPath('/channel')); - assert.equal(_getURL('/channel//', o), processPath('/channel')); - assert.equal(_getURL('/channel/123', o), processPath('/channel/123')); - assert.equal(_getURL('/channel/123/', o), processPath('/channel/123')); - assert.equal(_getURL('/channel/123?id=456&name=test', o), processPath('/channel/123?id=456&name=test')); - assert.equal(_getURL('/channel/123/?id=456&name=test', o), processPath('/channel/123?id=456&name=test')); + expect(_getURL('', o)).to.be.equal(processPath('')); + expect(_getURL('/', o)).to.be.equal(processPath('')); + expect(_getURL('//', o)).to.be.equal(processPath('')); + expect(_getURL('///', o)).to.be.equal(processPath('')); + expect(_getURL('/channel', o)).to.be.equal(processPath('/channel')); + expect(_getURL('/channel/', o)).to.be.equal(processPath('/channel')); + expect(_getURL('/channel//', o)).to.be.equal(processPath('/channel')); + expect(_getURL('/channel/123', o)).to.be.equal(processPath('/channel/123')); + expect(_getURL('/channel/123/', o)).to.be.equal(processPath('/channel/123')); + expect(_getURL('/channel/123?id=456&name=test', o)).to.be.equal(processPath('/channel/123?id=456&name=test')); + expect(_getURL('/channel/123/?id=456&name=test', o)).to.be.equal(processPath('/channel/123?id=456&name=test')); }; const getCloudUrl = (_site_url, path) => { @@ -119,7 +116,7 @@ const testCasesForOptions = (description, options) => { }); }; -describe.only('getURL', () => { +describe('getURL', () => { testCasesForOptions('getURL with no CDN, no PREFIX for http://localhost:3000/', { _cdn_prefix: '', _root_url_path_prefix: '', diff --git a/client/.eslintrc.js b/client/.eslintrc.js index 4d6bf74449b7b..baae88c35553a 100644 --- a/client/.eslintrc.js +++ b/client/.eslintrc.js @@ -2,7 +2,7 @@ module.exports = { root: true, extends: ['@rocket.chat/eslint-config', 'prettier'], parser: 'babel-eslint', - plugins: ['react', 'react-hooks', 'prettier'], + plugins: ['react', 'react-hooks', 'prettier', 'testing-library'], rules: { 'import/named': 'error', 'import/order': [ @@ -133,5 +133,18 @@ module.exports = { 'react/no-multi-comp': 'off', }, }, + { + files: ['**/*.tests.js', '**/*.tests.ts', '**/*.spec.ts', '**/*.spec.tsx'], + extends: ['plugin:testing-library/react'], + rules: { + 'testing-library/no-await-sync-events': 'warn', + 'testing-library/no-manual-cleanup': 'warn', + 'testing-library/prefer-explicit-assert': 'warn', + 'testing-library/prefer-user-event': 'warn', + }, + env: { + mocha: true, + }, + }, ], }; diff --git a/client/lib/download.spec.ts b/client/lib/download.spec.ts index 12a9809afdca3..042495a4ed7e0 100644 --- a/client/lib/download.spec.ts +++ b/client/lib/download.spec.ts @@ -1,85 +1,47 @@ -import 'jsdom-global/register'; -import chai from 'chai'; -import chaiSpies from 'chai-spies'; -import { after, before, describe, it } from 'mocha'; +import { expect, spy } from 'chai'; +import { describe, it } from 'mocha'; import { download, downloadAs, downloadCsvAs, downloadJsonAs } from './download'; -chai.use(chaiSpies); - -const withURL = (): void => { - let createObjectURL: typeof URL.createObjectURL; - let revokeObjectURL: typeof URL.revokeObjectURL; - - before(() => { - const blobs = new Map(); - - createObjectURL = window.URL.createObjectURL; - revokeObjectURL = window.URL.revokeObjectURL; - - window.URL.createObjectURL = (blob: Blob): string => { - const uuid = Math.random().toString(36).slice(2); - const url = `blob://${uuid}`; - blobs.set(url, blob); - return url; - }; - - window.URL.revokeObjectURL = (url: string): void => { - blobs.delete(url); - }; - }); - - after(() => { - window.URL.createObjectURL = createObjectURL; - window.URL.revokeObjectURL = revokeObjectURL; - }); -}; - describe('download', () => { it('should work', () => { - const listener = chai.spy(); + const listener = spy(); document.addEventListener('click', listener, false); download('about:blank', 'blank'); document.removeEventListener('click', listener, false); - chai.expect(listener).to.have.been.called(); + expect(listener).to.have.been.called(); }); }); describe('downloadAs', () => { - withURL(); - it('should work', () => { - const listener = chai.spy(); + const listener = spy(); document.addEventListener('click', listener, false); downloadAs({ data: [] }, 'blank'); document.removeEventListener('click', listener, false); - chai.expect(listener).to.have.been.called(); + expect(listener).to.have.been.called(); }); }); describe('downloadJsonAs', () => { - withURL(); - it('should work', () => { - const listener = chai.spy(); + const listener = spy(); document.addEventListener('click', listener, false); downloadJsonAs({}, 'blank'); document.removeEventListener('click', listener, false); - chai.expect(listener).to.have.been.called(); + expect(listener).to.have.been.called(); }); }); describe('downloadCsvAs', () => { - withURL(); - it('should work', () => { - const listener = chai.spy(); + const listener = spy(); document.addEventListener('click', listener, false); downloadCsvAs( @@ -91,6 +53,6 @@ describe('downloadCsvAs', () => { ); document.removeEventListener('click', listener, false); - chai.expect(listener).to.have.been.called(); + expect(listener).to.have.been.called(); }); }); diff --git a/client/lib/minimongo/bson.spec.ts b/client/lib/minimongo/bson.spec.ts index 1f2c5048f4248..f71a38f95141c 100644 --- a/client/lib/minimongo/bson.spec.ts +++ b/client/lib/minimongo/bson.spec.ts @@ -1,4 +1,4 @@ -import chai from 'chai'; +import { expect } from 'chai'; import { describe, it } from 'mocha'; import { getBSONType, compareBSONValues } from './bson'; @@ -6,32 +6,32 @@ import { BSONType } from './types'; describe('getBSONType', () => { it('should work', () => { - chai.expect(getBSONType(1)).to.be.equals(BSONType.Double); - chai.expect(getBSONType('xyz')).to.be.equals(BSONType.String); - chai.expect(getBSONType({})).to.be.equals(BSONType.Object); - chai.expect(getBSONType([])).to.be.equals(BSONType.Array); - chai.expect(getBSONType(new Uint8Array())).to.be.equals(BSONType.BinData); - chai.expect(getBSONType(undefined)).to.be.equals(BSONType.Object); - chai.expect(getBSONType(null)).to.be.equals(BSONType.Null); - chai.expect(getBSONType(false)).to.be.equals(BSONType.Boolean); - chai.expect(getBSONType(/.*/)).to.be.equals(BSONType.Regex); - chai.expect(getBSONType(() => true)).to.be.equals(BSONType.JavaScript); - chai.expect(getBSONType(new Date(0))).to.be.equals(BSONType.Date); + expect(getBSONType(1)).to.be.equals(BSONType.Double); + expect(getBSONType('xyz')).to.be.equals(BSONType.String); + expect(getBSONType({})).to.be.equals(BSONType.Object); + expect(getBSONType([])).to.be.equals(BSONType.Array); + expect(getBSONType(new Uint8Array())).to.be.equals(BSONType.BinData); + expect(getBSONType(undefined)).to.be.equals(BSONType.Object); + expect(getBSONType(null)).to.be.equals(BSONType.Null); + expect(getBSONType(false)).to.be.equals(BSONType.Boolean); + expect(getBSONType(/.*/)).to.be.equals(BSONType.Regex); + expect(getBSONType(() => true)).to.be.equals(BSONType.JavaScript); + expect(getBSONType(new Date(0))).to.be.equals(BSONType.Date); }); }); describe('compareBSONValues', () => { it('should work for the same types', () => { - chai.expect(compareBSONValues(2, 3)).to.be.equals(-1); - chai.expect(compareBSONValues('xyz', 'abc')).to.be.equals(1); - chai.expect(compareBSONValues({}, {})).to.be.equals(0); - chai.expect(compareBSONValues(true, false)).to.be.equals(1); - chai.expect(compareBSONValues(new Date(0), new Date(1))).to.be.equals(-1); + expect(compareBSONValues(2, 3)).to.be.equals(-1); + expect(compareBSONValues('xyz', 'abc')).to.be.equals(1); + expect(compareBSONValues({}, {})).to.be.equals(0); + expect(compareBSONValues(true, false)).to.be.equals(1); + expect(compareBSONValues(new Date(0), new Date(1))).to.be.equals(-1); }); it('should work for different types', () => { - chai.expect(compareBSONValues(2, null)).to.be.equals(1); - chai.expect(compareBSONValues('xyz', {})).to.be.equals(-1); - chai.expect(compareBSONValues(false, 3)).to.be.equals(1); + expect(compareBSONValues(2, null)).to.be.equals(1); + expect(compareBSONValues('xyz', {})).to.be.equals(-1); + expect(compareBSONValues(false, 3)).to.be.equals(1); }); }); diff --git a/client/lib/minimongo/comparisons.spec.ts b/client/lib/minimongo/comparisons.spec.ts index eb32433d9a81b..3048223f51ac6 100644 --- a/client/lib/minimongo/comparisons.spec.ts +++ b/client/lib/minimongo/comparisons.spec.ts @@ -1,4 +1,4 @@ -import chai from 'chai'; +import { expect } from 'chai'; import { describe, it } from 'mocha'; import { equals, isObject, flatSome, some, isEmptyArray } from './comparisons'; @@ -6,57 +6,57 @@ import { equals, isObject, flatSome, some, isEmptyArray } from './comparisons'; describe('Comparisons service', () => { describe('equals', () => { it('should return true if two numbers are equal', () => { - chai.expect(equals(1, 1)).to.be.equal(true); + expect(equals(1, 1)).to.be.equal(true); }); it('should return false if arguments are null or undefined', () => { - chai.expect(equals(undefined, null)).to.be.equal(false); - chai.expect(equals(null, undefined)).to.be.equal(false); + expect(equals(undefined, null)).to.be.equal(false); + expect(equals(null, undefined)).to.be.equal(false); }); it('should return false if arguments arent objects and they are not the same', () => { - chai.expect(equals('not', 'thesame')).to.be.equal(false); + expect(equals('not', 'thesame')).to.be.equal(false); }); it('should return true if date objects provided have the same value', () => { const currentDate = new Date(); - chai.expect(equals(currentDate, currentDate)).to.be.equal(true); + expect(equals(currentDate, currentDate)).to.be.equal(true); }); it('should return true if 2 equal UInt8Array are provided', () => { const arr1 = new Uint8Array([1, 2]); const arr2 = new Uint8Array([1, 2]); - chai.expect(equals(arr1, arr2)).to.be.equal(true); + expect(equals(arr1, arr2)).to.be.equal(true); }); it('should return true if 2 equal arrays are provided', () => { const arr1 = [1, 2, 4]; const arr2 = [1, 2, 4]; - chai.expect(equals(arr1, arr2)).to.be.equal(true); + expect(equals(arr1, arr2)).to.be.equal(true); }); it('should return false if 2 arrays with different length are provided', () => { const arr1 = [1, 4, 5]; const arr2 = [1, 4, 5, 7]; - chai.expect(equals(arr1, arr2)).to.be.equal(false); + expect(equals(arr1, arr2)).to.be.equal(false); }); it('should return true if the objects provided are "equal"', () => { const obj = { a: 1 }; const obj2 = obj; - chai.expect(equals(obj, obj2)).to.be.equal(true); + expect(equals(obj, obj2)).to.be.equal(true); }); it('should return true if both objects have the same keys', () => { const obj = { a: 1 }; const obj2 = { a: 1 }; - chai.expect(equals(obj, obj2)).to.be.equal(true); + expect(equals(obj, obj2)).to.be.equal(true); }); }); @@ -65,14 +65,14 @@ describe('Comparisons service', () => { const obj = {}; const func = (a: any): any => a; - chai.expect(isObject(obj)).to.be.equal(true); - chai.expect(isObject(func)).to.be.equal(true); + expect(isObject(obj)).to.be.equal(true); + expect(isObject(func)).to.be.equal(true); }); it('should return false for other data types', () => { - chai.expect(isObject(1)).to.be.equal(false); - chai.expect(isObject(true)).to.be.equal(false); - chai.expect(isObject('212')).to.be.equal(false); + expect(isObject(1)).to.be.equal(false); + expect(isObject(true)).to.be.equal(false); + expect(isObject('212')).to.be.equal(false); }); }); @@ -81,14 +81,14 @@ describe('Comparisons service', () => { const arr = [1, 2, 4, 6, 9]; const isEven = (v: number): boolean => v % 2 === 0; - chai.expect(flatSome(arr, isEven)).to.be.equal(true); + expect(flatSome(arr, isEven)).to.be.equal(true); }); it('should run the function on the value when its not an array', () => { const val = 1; const isEven = (v: number): boolean => v % 2 === 0; - chai.expect(flatSome(val, isEven)).to.be.equal(false); + expect(flatSome(val, isEven)).to.be.equal(false); }); }); @@ -102,7 +102,7 @@ describe('Comparisons service', () => { return v % 2 === 0; }; - chai.expect(some(arr, isEven)).to.be.equal(true); + expect(some(arr, isEven)).to.be.equal(true); }); it('should run the function on the value when its not an array', () => { @@ -114,21 +114,21 @@ describe('Comparisons service', () => { return v % 2 === 0; }; - chai.expect(some(val, isEven)).to.be.equal(false); + expect(some(val, isEven)).to.be.equal(false); }); }); describe('isEmptyArray', () => { it('should return true if array is empty', () => { - chai.expect(isEmptyArray([])).to.be.equal(true); + expect(isEmptyArray([])).to.be.equal(true); }); it('should return false if value is not an array', () => { - chai.expect(isEmptyArray(1)).to.be.equal(false); + expect(isEmptyArray(1)).to.be.equal(false); }); it('should return false if array is not empty', () => { - chai.expect(isEmptyArray([1, 2])).to.be.equal(false); + expect(isEmptyArray([1, 2])).to.be.equal(false); }); }); }); diff --git a/client/lib/minimongo/lookups.spec.ts b/client/lib/minimongo/lookups.spec.ts index 1056a3d7f5c89..3bae10346b628 100644 --- a/client/lib/minimongo/lookups.spec.ts +++ b/client/lib/minimongo/lookups.spec.ts @@ -1,15 +1,17 @@ -import chai from 'chai'; +import { expect } from 'chai'; import { describe, it } from 'mocha'; import { createLookupFunction } from './lookups'; describe('createLookupFunction', () => { it('should work', () => { - chai.expect(createLookupFunction('a.x')({ a: { x: 1 } })).to.be.deep.equals([1]); - chai.expect(createLookupFunction('a.x')({ a: { x: [1] } })).to.be.deep.equals([[1]]); - chai.expect(createLookupFunction('a.x')({ a: 5 })).to.be.deep.equals([undefined]); - chai - .expect(createLookupFunction('a.x')({ a: [{ x: 1 }, { x: [2] }, { y: 3 }] })) - .to.be.deep.equals([1, [2], undefined]); + expect(createLookupFunction('a.x')({ a: { x: 1 } })).to.be.deep.equals([1]); + expect(createLookupFunction('a.x')({ a: { x: [1] } })).to.be.deep.equals([[1]]); + expect(createLookupFunction('a.x')({ a: 5 })).to.be.deep.equals([undefined]); + expect(createLookupFunction('a.x')({ a: [{ x: 1 }, { x: [2] }, { y: 3 }] })).to.be.deep.equals([ + 1, + [2], + undefined, + ]); }); }); diff --git a/client/views/notFound/NotFoundPage.js b/client/views/notFound/NotFoundPage.js index 9a564a7530531..0aaf3bf64d1a8 100644 --- a/client/views/notFound/NotFoundPage.js +++ b/client/views/notFound/NotFoundPage.js @@ -38,11 +38,16 @@ function NotFoundPage() { 404 - + {t('Oops_page_not_found')} - + {t('Sorry_page_you_requested_does_not_exist_or_was_deleted')} diff --git a/client/views/notFound/NotFoundPage.spec.tsx b/client/views/notFound/NotFoundPage.spec.tsx new file mode 100644 index 0000000000000..3f13896315804 --- /dev/null +++ b/client/views/notFound/NotFoundPage.spec.tsx @@ -0,0 +1,79 @@ +import { render, waitFor, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { expect, spy } from 'chai'; +import React from 'react'; + +import RouterContextMock from '../../../tests/mocks/client/RouterContextMock'; +import NotFoundPage from './NotFoundPage'; + +describe('views/notFound/NotFoundPage', () => { + it('should look good', async () => { + render(); + + expect(screen.getByRole('heading', { level: 1, name: 'Oops_page_not_found' })).to.exist; + expect( + screen.getByRole('status', { + name: 'Sorry_page_you_requested_does_not_exist_or_was_deleted', + }), + ).to.exist; + expect( + screen.getByRole('button', { name: 'Return_to_previous_page' }), + ).to.exist.and.to.not.match(':disabled'); + expect(screen.getByRole('button', { name: 'Return_to_home' })).to.exist.and.to.not.match( + ':disabled', + ); + }); + + it('should have correct tab order', () => { + render(); + + expect(document.body).to.have.focus; + userEvent.tab(); + expect(screen.getByRole('button', { name: 'Return_to_previous_page' })).to.have.focus; + userEvent.tab(); + expect(screen.getByRole('button', { name: 'Return_to_home' })).to.have.focus; + userEvent.tab(); + expect(document.body).to.have.focus; + }); + + context('"Return to previous page" button', () => { + context('when clicked', () => { + const listener = spy(); + + before(() => { + window.history.pushState('404-page', '', 'http://localhost:3000/404'); + window.addEventListener('popstate', listener); + }); + + after(() => { + window.removeEventListener('popstate', listener); + }); + + it('should go back on history', async () => { + render(); + const button = screen.getByRole('button', { name: 'Return_to_previous_page' }); + + userEvent.click(button); + await waitFor(() => expect(listener).to.have.been.called(), { timeout: 2000 }); + expect(window.history.state).to.not.be.eq('404-page'); + }); + }); + }); + + context('"Return to home" button', () => { + context('when clicked', () => { + it('should go back on history', async () => { + const pushRoute = spy(); + render( + + + , + ); + const button = screen.getByRole('button', { name: 'Return_to_home' }); + + userEvent.click(button); + await waitFor(() => expect(pushRoute).to.have.been.called.with('home')); + }); + }); + }); +}); diff --git a/definition/rest/v1/teams/TeamsAddMembersProps.spec.ts b/definition/rest/v1/teams/TeamsAddMembersProps.spec.ts new file mode 100644 index 0000000000000..d06fcc7507d1c --- /dev/null +++ b/definition/rest/v1/teams/TeamsAddMembersProps.spec.ts @@ -0,0 +1,70 @@ +import { assert } from 'chai'; + +import { isTeamsAddMembersProps } from './TeamsAddMembersProps'; + +describe('TeamsAddMemberProps (definition/rest/v1)', () => { + describe('isTeamsAddMembersProps', () => { + it('should be a function', () => { + assert.isFunction(isTeamsAddMembersProps); + }); + it('should return false if the parameter is empty', () => { + assert.isFalse(isTeamsAddMembersProps({})); + }); + + it('should return false if teamId is provided but no member was provided', () => { + assert.isFalse(isTeamsAddMembersProps({ teamId: '123' })); + }); + + it('should return false if teamName is provided but no member was provided', () => { + assert.isFalse(isTeamsAddMembersProps({ teamName: '123' })); + }); + + it('should return false if members is provided but no teamId or teamName were provided', () => { + assert.isFalse(isTeamsAddMembersProps({ members: [{ userId: '123' }] })); + }); + + it('should return false if teamName was provided but members are empty', () => { + assert.isFalse(isTeamsAddMembersProps({ teamName: '123', members: [] })); + }); + + it('should return false if teamId was provided but members are empty', () => { + assert.isFalse(isTeamsAddMembersProps({ teamId: '123', members: [] })); + }); + + it('should return false if members with role is provided but no teamId or teamName were provided', () => { + assert.isFalse(isTeamsAddMembersProps({ members: [{ userId: '123', roles: ['123'] }] })); + }); + + it('should return true if members is provided and teamId is provided', () => { + assert.isTrue(isTeamsAddMembersProps({ members: [{ userId: '123' }], teamId: '123' })); + }); + + it('should return true if members is provided and teamName is provided', () => { + assert.isTrue(isTeamsAddMembersProps({ members: [{ userId: '123' }], teamName: '123' })); + }); + + it('should return true if members with role is provided and teamId is provided', () => { + assert.isTrue(isTeamsAddMembersProps({ members: [{ userId: '123', roles: ['123'] }], teamId: '123' })); + }); + + it('should return true if members with role is provided and teamName is provided', () => { + assert.isTrue(isTeamsAddMembersProps({ members: [{ userId: '123', roles: ['123'] }], teamName: '123' })); + }); + + it('should return false if teamName was provided and members contains an invalid property', () => { + assert.isFalse(isTeamsAddMembersProps({ teamName: '123', members: [{ userId: '123', roles: ['123'], invalid: true }] })); + }); + + it('should return false if teamId was provided and members contains an invalid property', () => { + assert.isFalse(isTeamsAddMembersProps({ teamId: '123', members: [{ userId: '123', roles: ['123'], invalid: true }] })); + }); + + it('should return false if teamName informed but contains an invalid property', () => { + assert.isFalse(isTeamsAddMembersProps({ member: [{ userId: '123', roles: ['123'] }], teamName: '123', invalid: true })); + }); + + it('should return false if teamId informed but contains an invalid property', () => { + assert.isFalse(isTeamsAddMembersProps({ member: [{ userId: '123', roles: ['123'] }], teamId: '123', invalid: true })); + }); + }); +}); diff --git a/definition/rest/v1/teams/TeamsConvertToChannelProps.spec.ts b/definition/rest/v1/teams/TeamsConvertToChannelProps.spec.ts new file mode 100644 index 0000000000000..0222e0acfe1c5 --- /dev/null +++ b/definition/rest/v1/teams/TeamsConvertToChannelProps.spec.ts @@ -0,0 +1,38 @@ +import { assert } from 'chai'; + +import { isTeamsConvertToChannelProps } from './TeamsConvertToChannelProps'; + +describe('TeamsConvertToChannelProps (definition/rest/v1)', () => { + describe('isTeamsConvertToChannelProps', () => { + it('should be a function', () => { + assert.isFunction(isTeamsConvertToChannelProps); + }); + it('should return false if neither teamName or teamId is provided', () => { + assert.isFalse(isTeamsConvertToChannelProps({})); + }); + + it('should return true if teamName is provided', () => { + assert.isTrue(isTeamsConvertToChannelProps({ teamName: 'teamName' })); + }); + + it('should return true if teamId is provided', () => { + assert.isTrue(isTeamsConvertToChannelProps({ teamId: 'teamId' })); + }); + + it('should return false if both teamName and teamId are provided', () => { + assert.isFalse(isTeamsConvertToChannelProps({ teamName: 'teamName', teamId: 'teamId' })); + }); + + it('should return false if teamName is not a string', () => { + assert.isFalse(isTeamsConvertToChannelProps({ teamName: 1 })); + }); + + it('should return false if teamId is not a string', () => { + assert.isFalse(isTeamsConvertToChannelProps({ teamId: 1 })); + }); + + it('should return false if an additionalProperties is provided', () => { + assert.isFalse(isTeamsConvertToChannelProps({ teamName: 'teamName', additionalProperties: 'additionalProperties' })); + }); + }); +}); diff --git a/definition/rest/v1/teams/TeamsDeleteProps.spec.ts b/definition/rest/v1/teams/TeamsDeleteProps.spec.ts new file mode 100644 index 0000000000000..aa5fbfa287c82 --- /dev/null +++ b/definition/rest/v1/teams/TeamsDeleteProps.spec.ts @@ -0,0 +1,63 @@ +import { assert } from 'chai'; + +import { isTeamsDeleteProps } from './TeamsDeleteProps'; + +describe('TeamsDeleteProps (definition/rest/v1)', () => { + describe('isTeamsDeleteProps', () => { + it('should be a function', () => { + assert.isFunction(isTeamsDeleteProps); + }); + + it('should return false if neither teamName or teamId is provided', () => { + assert.isFalse(isTeamsDeleteProps({})); + }); + + it('should return true if teamId is provided', () => { + assert.isTrue(isTeamsDeleteProps({ teamId: 'teamId' })); + }); + + it('should return true if teamName is provided', () => { + assert.isTrue(isTeamsDeleteProps({ teamName: 'teamName' })); + }); + + it('should return false if teamId and roomsToRemove are provided, but roomsToRemove is empty', () => { + assert.isFalse(isTeamsDeleteProps({ teamId: 'teamId', roomsToRemove: [] })); + }); + + it('should return false if teamName and roomsToRemove are provided, but roomsToRemove is empty', () => { + assert.isFalse(isTeamsDeleteProps({ teamName: 'teamName', roomsToRemove: [] })); + }); + + it('should return true if teamId and roomsToRemove are provided', () => { + assert.isTrue(isTeamsDeleteProps({ teamId: 'teamId', roomsToRemove: ['roomId'] })); + }); + + it('should return true if teamName and roomsToRemove are provided', () => { + assert.isTrue(isTeamsDeleteProps({ teamName: 'teamName', roomsToRemove: ['roomId'] })); + }); + + it('should return false if teamId and roomsToRemove are provided, but roomsToRemove is not an array', () => { + assert.isFalse(isTeamsDeleteProps({ teamId: 'teamId', roomsToRemove: {} })); + }); + + it('should return false if teamName and roomsToRemove are provided, but roomsToRemove is not an array', () => { + assert.isFalse(isTeamsDeleteProps({ teamName: 'teamName', roomsToRemove: {} })); + }); + + it('should return false if teamId and roomsToRemove are provided, but roomsToRemove is not an array of strings', () => { + assert.isFalse(isTeamsDeleteProps({ teamId: 'teamId', roomsToRemove: [1] })); + }); + + it('should return false if teamName and roomsToRemove are provided, but roomsToRemove is not an array of strings', () => { + assert.isFalse(isTeamsDeleteProps({ teamName: 'teamName', roomsToRemove: [1] })); + }); + + it('should return false if teamName and rooms are provided but an extra property is provided', () => { + assert.isFalse(isTeamsDeleteProps({ teamName: 'teamName', roomsToRemove: ['roomsToRemove'], extra: 'extra' })); + }); + + it('should return false if teamId and rooms are provided but an extra property is provided', () => { + assert.isFalse(isTeamsDeleteProps({ teamId: 'teamId', roomsToRemove: ['roomsToRemove'], extra: 'extra' })); + }); + }); +}); diff --git a/definition/rest/v1/teams/TeamsLeaveProps.spec.ts b/definition/rest/v1/teams/TeamsLeaveProps.spec.ts new file mode 100644 index 0000000000000..6661ab100bf19 --- /dev/null +++ b/definition/rest/v1/teams/TeamsLeaveProps.spec.ts @@ -0,0 +1,63 @@ +import { assert } from 'chai'; + +import { isTeamsLeaveProps } from './TeamsLeaveProps'; + +describe('TeamsLeaveProps (definition/rest/v1)', () => { + describe('isTeamsLeaveProps', () => { + it('should be a function', () => { + assert.isFunction(isTeamsLeaveProps); + }); + + it('should return false if neither teamName or teamId is provided', () => { + assert.isFalse(isTeamsLeaveProps({})); + }); + + it('should return true if teamId is provided', () => { + assert.isTrue(isTeamsLeaveProps({ teamId: 'teamId' })); + }); + + it('should return true if teamName is provided', () => { + assert.isTrue(isTeamsLeaveProps({ teamName: 'teamName' })); + }); + + it('should return false if teamId and roomsToRemove are provided, but roomsToRemove is empty', () => { + assert.isFalse(isTeamsLeaveProps({ teamId: 'teamId', rooms: [] })); + }); + + it('should return false if teamName and rooms are provided, but rooms is empty', () => { + assert.isFalse(isTeamsLeaveProps({ teamName: 'teamName', rooms: [] })); + }); + + it('should return true if teamId and rooms are provided', () => { + assert.isTrue(isTeamsLeaveProps({ teamId: 'teamId', rooms: ['roomId'] })); + }); + + it('should return true if teamName and rooms are provided', () => { + assert.isTrue(isTeamsLeaveProps({ teamName: 'teamName', rooms: ['roomId'] })); + }); + + it('should return false if teamId and rooms are provided, but rooms is not an array', () => { + assert.isFalse(isTeamsLeaveProps({ teamId: 'teamId', rooms: {} })); + }); + + it('should return false if teamName and rooms are provided, but rooms is not an array', () => { + assert.isFalse(isTeamsLeaveProps({ teamName: 'teamName', rooms: {} })); + }); + + it('should return false if teamId and rooms are provided, but rooms is not an array of strings', () => { + assert.isFalse(isTeamsLeaveProps({ teamId: 'teamId', rooms: [1] })); + }); + + it('should return false if teamName and rooms are provided, but rooms is not an array of strings', () => { + assert.isFalse(isTeamsLeaveProps({ teamName: 'teamName', rooms: [1] })); + }); + + it('should return false if teamName and rooms are provided but an extra property is provided', () => { + assert.isFalse(isTeamsLeaveProps({ teamName: 'teamName', rooms: ['rooms'], extra: 'extra' })); + }); + + it('should return false if teamId and rooms are provided but an extra property is provided', () => { + assert.isFalse(isTeamsLeaveProps({ teamId: 'teamId', rooms: ['rooms'], extra: 'extra' })); + }); + }); +}); diff --git a/definition/rest/v1/teams/TeamsRemoveMemberProps.spec.ts b/definition/rest/v1/teams/TeamsRemoveMemberProps.spec.ts new file mode 100644 index 0000000000000..2d3afdaac3346 --- /dev/null +++ b/definition/rest/v1/teams/TeamsRemoveMemberProps.spec.ts @@ -0,0 +1,60 @@ +import { assert } from 'chai'; + +import { isTeamsRemoveMemberProps } from './TeamsRemoveMemberProps'; + +describe('Teams (definition/rest/v1)', () => { + describe('isTeamsRemoveMemberProps', () => { + it('should be a function', () => { + assert.isFunction(isTeamsRemoveMemberProps); + }); + it('should return false if parameter is empty', () => { + assert.isFalse(isTeamsRemoveMemberProps({})); + }); + it('should return false if teamId is is informed but missing userId', () => { + assert.isFalse(isTeamsRemoveMemberProps({ teamId: 'teamId' })); + }); + it('should return false if teamName is is informed but missing userId', () => { + assert.isFalse(isTeamsRemoveMemberProps({ teamName: 'teamName' })); + }); + + it('should return true if teamId and userId are informed', () => { + assert.isTrue(isTeamsRemoveMemberProps({ teamId: 'teamId', userId: 'userId' })); + }); + it('should return true if teamName and userId are informed', () => { + assert.isTrue(isTeamsRemoveMemberProps({ teamName: 'teamName', userId: 'userId' })); + }); + + + it('should return false if teamName and userId are informed but rooms are empty', () => { + assert.isFalse(isTeamsRemoveMemberProps({ teamName: 'teamName', userId: 'userId', rooms: [] })); + }); + + it('should return false if teamId and userId are informed and rooms are empty', () => { + assert.isFalse(isTeamsRemoveMemberProps({ teamId: 'teamId', userId: 'userId', rooms: [] })); + }); + + it('should return false if teamId and userId are informed but rooms are empty', () => { + assert.isFalse(isTeamsRemoveMemberProps({ teamId: 'teamId', userId: 'userId', rooms: [] })); + }); + + it('should return true if teamId and userId are informed and rooms are informed', () => { + assert.isTrue(isTeamsRemoveMemberProps({ teamId: 'teamId', userId: 'userId', rooms: ['room'] })); + }); + + it('should return false if teamId and userId are informed and rooms are informed but rooms is not an array of strings', () => { + assert.isFalse(isTeamsRemoveMemberProps({ teamId: 'teamId', userId: 'userId', rooms: [123] })); + }); + + it('should return false if teamName and userId are informed and rooms are informed but there is an extra property', () => { + assert.isFalse(isTeamsRemoveMemberProps({ teamName: 'teamName', userId: 'userId', rooms: ['room'], extra: 'extra' })); + }); + + it('should return false if teamId and userId are informed and rooms are informed but there is an extra property', () => { + assert.isFalse(isTeamsRemoveMemberProps({ teamId: 'teamId', userId: 'userId', rooms: ['room'], extra: 'extra' })); + }); + + it('should return false if teamName and userId are informed and rooms are informed but there is an extra property', () => { + assert.isFalse(isTeamsRemoveMemberProps({ teamName: 'teamName', userId: 'userId', rooms: ['room'], extra: 'extra' })); + }); + }); +}); diff --git a/definition/rest/v1/teams/TeamsRemoveRoomProps.spec.ts b/definition/rest/v1/teams/TeamsRemoveRoomProps.spec.ts new file mode 100644 index 0000000000000..4a6a065831d7d --- /dev/null +++ b/definition/rest/v1/teams/TeamsRemoveRoomProps.spec.ts @@ -0,0 +1,26 @@ +import { assert } from 'chai'; + +import { isTeamsRemoveRoomProps } from './TeamsRemoveRoomProps'; + +describe('TeamsRemoveRoomProps (definition/rest/v1)', () => { + describe('isTeamsRemoveRoomProps', () => { + it('should be a function', () => { + assert.isFunction(isTeamsRemoveRoomProps); + }); + it('should return false if roomId is not provided', () => { + assert.isFalse(isTeamsRemoveRoomProps({})); + }); + it('should return false if roomId is provided but no teamId or teamName were provided', () => { + assert.isFalse(isTeamsRemoveRoomProps({ roomId: 'roomId' })); + }); + it('should return false if roomId is provided and teamId is provided', () => { + assert.isTrue(isTeamsRemoveRoomProps({ roomId: 'roomId', teamId: 'teamId' })); + }); + it('should return true if roomId is provided and teamName is provided', () => { + assert.isTrue(isTeamsRemoveRoomProps({ roomId: 'roomId', teamName: 'teamName' })); + }); + it('should return false if roomId and teamName are provided but an additional property is provided', () => { + assert.isFalse(isTeamsRemoveRoomProps({ roomId: 'roomId', teamName: 'teamName', foo: 'bar' })); + }); + }); +}); diff --git a/definition/rest/v1/teams/TeamsUpdateMemberProps.spec.ts b/definition/rest/v1/teams/TeamsUpdateMemberProps.spec.ts new file mode 100644 index 0000000000000..8e25f1516059a --- /dev/null +++ b/definition/rest/v1/teams/TeamsUpdateMemberProps.spec.ts @@ -0,0 +1,58 @@ +import { assert } from 'chai'; + +import { isTeamsUpdateMemberProps } from './TeamsUpdateMemberProps'; + +describe('TeamsUpdateMemberProps (definition/rest/v1)', () => { + describe('isTeamsUpdateMemberProps', () => { + it('should be a function', () => { + assert.isFunction(isTeamsUpdateMemberProps); + }); + it('should return false if the parameter is empty', () => { + assert.isFalse(isTeamsUpdateMemberProps({})); + }); + + it('should return false if teamId is provided but no member was provided', () => { + assert.isFalse(isTeamsUpdateMemberProps({ teamId: '123' })); + }); + + it('should return false if teamName is provided but no member was provided', () => { + assert.isFalse(isTeamsUpdateMemberProps({ teamName: '123' })); + }); + + it('should return false if member is provided but no teamId or teamName were provided', () => { + assert.isFalse(isTeamsUpdateMemberProps({ member: { userId: '123' } })); + }); + + it('should return false if member with role is provided but no teamId or teamName were provided', () => { + assert.isFalse(isTeamsUpdateMemberProps({ member: { userId: '123', roles: ['123'] } })); + }); + + it('should return true if member is provided and teamId is provided', () => { + assert.isTrue(isTeamsUpdateMemberProps({ member: { userId: '123' }, teamId: '123' })); + }); + + it('should return true if member is provided and teamName is provided', () => { + assert.isTrue(isTeamsUpdateMemberProps({ member: { userId: '123' }, teamName: '123' })); + }); + + it('should return true if member with role is provided and teamId is provided', () => { + assert.isTrue(isTeamsUpdateMemberProps({ member: { userId: '123', roles: ['123'] }, teamId: '123' })); + }); + + it('should return true if member with role is provided and teamName is provided', () => { + assert.isTrue(isTeamsUpdateMemberProps({ member: { userId: '123', roles: ['123'] }, teamName: '123' })); + }); + + it('should return false if teamName was provided and member contains an invalid property', () => { + assert.isFalse(isTeamsUpdateMemberProps({ member: { userId: '123', invalid: '123' }, teamName: '123' })); + }); + + it('should return false if teamId was provided and member contains an invalid property', () => { + assert.isFalse(isTeamsUpdateMemberProps({ member: { userId: '123', invalid: '123' }, teamId: '123' })); + }); + + it('should return false if contains an invalid property', () => { + assert.isFalse(isTeamsUpdateMemberProps({ member: { userId: '123', roles: ['123'] }, teamName: '123', invalid: true })); + }); + }); +}); diff --git a/definition/rest/v1/teams/TeamsUpdateProps.spec.ts b/definition/rest/v1/teams/TeamsUpdateProps.spec.ts new file mode 100644 index 0000000000000..8f8fdb8b072f2 --- /dev/null +++ b/definition/rest/v1/teams/TeamsUpdateProps.spec.ts @@ -0,0 +1,160 @@ +import { assert } from 'chai'; + +import { isTeamsUpdateProps } from './TeamsUpdateProps'; + +describe('TeamsUpdateMemberProps (definition/rest/v1)', () => { + describe('isTeamsUpdateProps', () => { + it('should be a function', () => { + assert.isFunction(isTeamsUpdateProps); + }); + it('should return false when provided anything that is not an TeamsUpdateProps', () => { + assert.isFalse(isTeamsUpdateProps(undefined)); + assert.isFalse(isTeamsUpdateProps(null)); + assert.isFalse(isTeamsUpdateProps('')); + assert.isFalse(isTeamsUpdateProps(123)); + assert.isFalse(isTeamsUpdateProps({})); + assert.isFalse(isTeamsUpdateProps([])); + assert.isFalse(isTeamsUpdateProps(new Date())); + assert.isFalse(isTeamsUpdateProps(new Error())); + }); + it('should return false when only teamName is provided to TeamsUpdateProps', () => { + assert.isFalse(isTeamsUpdateProps({ + teamName: 'teamName', + })); + }); + + it('should return false when only teamId is provided to TeamsUpdateProps', () => { + assert.isFalse(isTeamsUpdateProps({ + teamId: 'teamId', + })); + }); + + it('should return false when teamName and data are provided to TeamsUpdateProps but data is an empty object', () => { + assert.isFalse(isTeamsUpdateProps({ + teamName: 'teamName', + data: {}, + })); + }); + + it('should return false when teamId and data are provided to TeamsUpdateProps but data is an empty object', () => { + assert.isFalse(isTeamsUpdateProps({ + teamId: 'teamId', + data: {}, + })); + }); + + it('should return false when teamName and data are provided to TeamsUpdateProps but data is not an object', () => { + assert.isFalse(isTeamsUpdateProps({ + teamName: 'teamName', + data: 'data', + })); + }); + + it('should return false when teamId and data are provided to TeamsUpdateProps but data is not an object', () => { + assert.isFalse(isTeamsUpdateProps({ + teamId: 'teamId', + data: 'data', + })); + }); + + it('should return true when teamName and data.name are provided to TeamsUpdateProps', () => { + assert.isTrue(isTeamsUpdateProps({ + teamName: 'teamName', + data: { + name: 'name', + }, + })); + }); + + it('should return true when teamId and data.name are provided to TeamsUpdateProps', () => { + assert.isTrue(isTeamsUpdateProps({ + teamId: 'teamId', + data: { + name: 'name', + }, + })); + }); + + it('should return true when teamName and data.type are provided to TeamsUpdateProps', () => { + assert.isTrue(isTeamsUpdateProps({ + teamName: 'teamName', + data: { + type: 0, + }, + })); + }); + + it('should return true when teamId and data.type are provided to TeamsUpdateProps', () => { + assert.isTrue(isTeamsUpdateProps({ + teamId: 'teamId', + data: { + type: 0, + }, + })); + }); + + it('should return true when teamName and data.name and data.type are provided to TeamsUpdateProps', () => { + assert.isTrue(isTeamsUpdateProps({ + teamName: 'teamName', + data: { + name: 'name', + type: 0, + }, + })); + }); + + it('should return true when teamId and data.name and data.type are provided to TeamsUpdateProps', () => { + assert.isTrue(isTeamsUpdateProps({ + teamId: 'teamId', + data: { + name: 'name', + type: 0, + }, + })); + }); + + it('should return false when teamName, data.name, data.type are some more extra data are provided to TeamsUpdateProps', () => { + assert.isFalse(isTeamsUpdateProps({ + teamName: 'teamName', + data: { + name: 'name', + type: 0, + extra: 'extra', + }, + })); + }); + + it('should return false when teamId, data.name, data.type are some more extra data are provided to TeamsUpdateProps', () => { + assert.isFalse(isTeamsUpdateProps({ + teamId: 'teamId', + data: { + name: 'name', + type: 0, + extra: 'extra', + }, + })); + }); + + it('should return false when teamName, data.name, data.type are some more extra parameter are provided to TeamsUpdateProps', () => { + assert.isFalse(isTeamsUpdateProps({ + teamName: 'teamName', + extra: 'extra', + data: { + name: 'name', + type: 0, + }, + })); + }); + + it('should return false when teamId, data.name, data.type are some more extra parameter are provided to TeamsUpdateProps', () => { + assert.isFalse(isTeamsUpdateProps({ + teamId: 'teamId', + extra: 'extra', + data: { + name: 'name', + type: 0, + }, + })); + }); + }); +}); diff --git a/package-lock.json b/package-lock.json index 7aa70cb89903c..d9df5f975bd9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9375,6 +9375,145 @@ "unist-util-find-all-after": "^3.0.2" } }, + "@testing-library/dom": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.11.1.tgz", + "integrity": "sha512-3KQDyx9r0RKYailW2MiYrSSKEfH0GTkI51UGEvJenvcoDoeRYs0PZpi2SXqtnMClQvCqdtTTpOfFETDTVADpAg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^4.2.0", + "aria-query": "^5.0.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.4.4", + "pretty-format": "^27.0.2" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", + "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.16.0" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "dev": true + }, + "@babel/highlight": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", + "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.15.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@testing-library/react": { + "version": "12.1.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-12.1.2.tgz", + "integrity": "sha512-ihQiEOklNyHIpo2Y8FREkyD1QAea054U0MVbwH1m8N9TxeFz+KoJ9LkqoKqJlzx2JDm56DVwaJ1r36JYxZM05g==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^8.0.0" + } + }, + "@testing-library/user-event": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz", + "integrity": "sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5" + } + }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -9423,6 +9562,12 @@ "@types/node": "*" } }, + "@types/aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==", + "dev": true + }, "@types/bad-words": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@types/bad-words/-/bad-words-3.0.1.tgz", @@ -9477,9 +9622,27 @@ "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" }, "@types/chai": { - "version": "4.2.19", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.19.tgz", - "integrity": "sha512-jRJgpRBuY+7izT7/WNXP/LsMO9YonsstuL+xuvycDyESpoDoIAsMd7suwpB4h9oEWB+ZlPTqJJ8EHomzNhwTPQ==" + "version": "4.2.22", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.22.tgz", + "integrity": "sha512-tFfcE+DSTzWAgifkjik9AySNqIyNoYwmR+uecPwwD/XRNfvOjmC/FjCxpiUGDkDVDphPfCUecSQVFw+lN3M3kQ==" + }, + "@types/chai-datetime": { + "version": "0.0.37", + "resolved": "https://registry.npmjs.org/@types/chai-datetime/-/chai-datetime-0.0.37.tgz", + "integrity": "sha512-teAlKuUV2mxuN0hRxfSXnk7v5lDZUtQWMZ72pIvm5OJ8SuMmgjQgNiebha+MYr7EiSVCQxDY8yH1j7TIXy3nEQ==", + "dev": true, + "requires": { + "@types/chai": "*" + } + }, + "@types/chai-dom": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@types/chai-dom/-/chai-dom-0.0.11.tgz", + "integrity": "sha512-qLGx6pHrcCfjkpfuh8aBj7xBm8wo2F9aTgv9p+/X0zBAsR3c1wxJguDFnV5cXxUQJKY9BdwE6PVy7W4PT5IAlA==", + "dev": true, + "requires": { + "@types/chai": "*" + } }, "@types/chai-spies": { "version": "1.0.3", @@ -9717,6 +9880,15 @@ "@types/tough-cookie": "*" } }, + "@types/jsdom-global": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/jsdom-global/-/jsdom-global-3.0.2.tgz", + "integrity": "sha512-CFIPEDpO5vQWdQrVXrdSR2j5giiDuyb0hzZD04OJqMfizt7sh6WoqaLBdnP4w74yHDDcQkc0k5TzrXffhua3Jg==", + "dev": true, + "requires": { + "@types/jsdom": "*" + } + }, "@types/json-schema": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz", @@ -9853,9 +10025,9 @@ } }, "@types/mocha": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.2.tgz", - "integrity": "sha512-Lwh0lzzqT5Pqh6z61P3c3P5nm6fzQK/MMHl9UKeneAeInVflBSz1O2EkX6gM6xfJd7FBXBY5purtLx7fUiZ7Hw==" + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.3.tgz", + "integrity": "sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw==" }, "@types/mock-require": { "version": "2.0.0", @@ -10553,6 +10725,40 @@ } } }, + "@typescript-eslint/scope-manager": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.3.1.tgz", + "integrity": "sha512-XksFVBgAq0Y9H40BDbuPOTUIp7dn4u8oOuhcgGq7EoDP50eqcafkMVGrypyVGvDYHzjhdUCUwuwVUK4JhkMAMg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.3.1", + "@typescript-eslint/visitor-keys": "5.3.1" + }, + "dependencies": { + "@typescript-eslint/types": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.3.1.tgz", + "integrity": "sha512-bG7HeBLolxKHtdHG54Uac750eXuQQPpdJfCYuw4ZI3bZ7+GgKClMWM8jExBtp7NSP4m8PmLRM8+lhzkYnSmSxQ==", + "dev": true + }, + "@typescript-eslint/visitor-keys": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.3.1.tgz", + "integrity": "sha512-3cHUzUuVTuNHx0Gjjt5pEHa87+lzyqOiHXy/Gz+SJOCW1mpw9xQHIIEwnKn+Thph1mgWyZ90nboOcSuZr/jTTQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.3.1", + "eslint-visitor-keys": "^3.0.0" + } + }, + "eslint-visitor-keys": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz", + "integrity": "sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA==", + "dev": true + } + } + }, "@typescript-eslint/types": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz", @@ -11517,6 +11723,12 @@ } } }, + "aria-query": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz", + "integrity": "sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==", + "dev": true + }, "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", @@ -12577,201 +12789,6 @@ } } }, - "babel-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", - "dev": true, - "requires": { - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "detect-indent": "^4.0.0", - "jsesc": "^1.3.0", - "lodash": "^4.17.4", - "source-map": "^0.5.7", - "trim-right": "^1.0.1" - }, - "dependencies": { - "jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", - "dev": true - } - } - }, - "babel-helper-bindify-decorators": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz", - "integrity": "sha1-FMGeXxQte0fxmlJDHlKxzLxAozA=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-helper-builder-binary-assignment-operator-visitor": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", - "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", - "dev": true, - "requires": { - "babel-helper-explode-assignable-expression": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-helper-builder-react-jsx": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz", - "integrity": "sha1-Of+DE7dci2Xc7/HzHTg+D/KkCKA=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "esutils": "^2.0.2" - } - }, - "babel-helper-call-delegate": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", - "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", - "dev": true, - "requires": { - "babel-helper-hoist-variables": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-helper-define-map": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", - "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", - "dev": true, - "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" - } - }, - "babel-helper-explode-assignable-expression": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz", - "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-helper-explode-class": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-explode-class/-/babel-helper-explode-class-6.24.1.tgz", - "integrity": "sha1-fcKjkQ3uAHBW4eMdZAztPVTqqes=", - "dev": true, - "requires": { - "babel-helper-bindify-decorators": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-helper-function-name": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", - "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", - "dev": true, - "requires": { - "babel-helper-get-function-arity": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-helper-get-function-arity": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", - "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-helper-hoist-variables": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", - "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-helper-optimise-call-expression": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", - "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-helper-regex": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", - "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" - } - }, - "babel-helper-remap-async-to-generator": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz", - "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", - "dev": true, - "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-helper-replace-supers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", - "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", - "dev": true, - "requires": { - "babel-helper-optimise-call-expression": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-helpers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", - "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, "babel-loader": { "version": "8.2.2", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.2.tgz", @@ -12911,77 +12928,6 @@ "babel-runtime": "^6.22.0" } }, - "babel-mocha-es6-compiler": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/babel-mocha-es6-compiler/-/babel-mocha-es6-compiler-0.1.0.tgz", - "integrity": "sha1-QMnkBoCvRhWP7usntJQUtrgOxDg=", - "dev": true, - "requires": { - "babel-core": "~6.9.0", - "babel-plugin-add-module-exports": "~0.2.1", - "babel-preset-es2015": "~6.3.13", - "babel-preset-react": "~6.3.13", - "babel-preset-stage-0": "~6.3.13" - }, - "dependencies": { - "babel-core": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.9.1.tgz", - "integrity": "sha1-SNRx7r9N5GngqUL+RW3MlLGL6A0=", - "dev": true, - "requires": { - "babel-code-frame": "^6.8.0", - "babel-generator": "^6.9.0", - "babel-helpers": "^6.8.0", - "babel-messages": "^6.8.0", - "babel-register": "^6.9.0", - "babel-runtime": "^6.9.1", - "babel-template": "^6.9.0", - "babel-traverse": "^6.9.0", - "babel-types": "^6.9.1", - "babylon": "^6.7.0", - "convert-source-map": "^1.1.0", - "debug": "^2.1.1", - "json5": "^0.4.0", - "lodash": "^4.2.0", - "minimatch": "^2.0.3", - "path-exists": "^1.0.0", - "path-is-absolute": "^1.0.0", - "private": "^0.1.6", - "shebang-regex": "^1.0.0", - "slash": "^1.0.0", - "source-map": "^0.5.0" - } - }, - "json5": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.4.0.tgz", - "integrity": "sha1-BUNS5MTIDIbAkjh31EneF2pzLI0=", - "dev": true - }, - "minimatch": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", - "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", - "dev": true, - "requires": { - "brace-expansion": "^1.0.0" - } - }, - "path-exists": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-1.0.0.tgz", - "integrity": "sha1-1aiZjrce83p0w06w2eum6HjuoIE=", - "dev": true - } - } - }, - "babel-plugin-add-module-exports": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/babel-plugin-add-module-exports/-/babel-plugin-add-module-exports-0.2.1.tgz", - "integrity": "sha1-mumh9KjcZ/DN7E9K7aHkOl/2XiU=", - "dev": true - }, "babel-plugin-add-react-displayname": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/babel-plugin-add-react-displayname/-/babel-plugin-add-react-displayname-0.0.5.tgz", @@ -13012,15 +12958,6 @@ "integrity": "sha1-z1RS6Bx7gD+3lZ8QRayI4uwo/3Y=", "dev": true }, - "babel-plugin-check-es2015-constants": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", - "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, "babel-plugin-dynamic-import-node": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", @@ -13093,699 +13030,98 @@ "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "dev": true, - "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - } - } - } - }, - "babel-plugin-named-asset-import": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.7.tgz", - "integrity": "sha512-squySRkf+6JGnvjoUtDEjSREJEBirnXi9NqP6rjSYsylxQxqBTz+pkmf395i9E2zsvmYUaI40BHo6SqZUdydlw==", - "dev": true - }, - "babel-plugin-polyfill-corejs2": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.2.tgz", - "integrity": "sha512-kISrENsJ0z5dNPq5eRvcctITNHYXWOA4DUZRFYCz3jYCcvTb/A546LIddmoGNMVYg2U38OyFeNosQwI9ENTqIQ==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.13.11", - "@babel/helper-define-polyfill-provider": "^0.2.2", - "semver": "^6.1.1" - }, - "dependencies": { - "@babel/compat-data": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.5.tgz", - "integrity": "sha512-kixrYn4JwfAVPa0f2yfzc2AWti6WRRyO3XjWW5PJAvtE11qhSayrrcrEnee05KAtNaPC+EwehE8Qt1UedEVB8w==", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "babel-plugin-polyfill-corejs3": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.3.tgz", - "integrity": "sha512-rCOFzEIJpJEAU14XCcV/erIf/wZQMmMT5l5vXOpL5uoznyOGfDIjPj6FVytMvtzaKSTSVKouOCTPJ5OMUZH30g==", - "dev": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.2.2", - "core-js-compat": "^3.14.0" - } - }, - "babel-plugin-polyfill-regenerator": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.2.tgz", - "integrity": "sha512-Goy5ghsc21HgPDFtzRkSirpZVW35meGoTmTOb2bxqdl60ghub4xOidgNTHaZfQ2FaxQsKmwvXtOAkcIS4SMBWg==", - "dev": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.2.2" - } - }, - "babel-plugin-react-docgen": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/babel-plugin-react-docgen/-/babel-plugin-react-docgen-4.2.1.tgz", - "integrity": "sha512-UQ0NmGHj/HAqi5Bew8WvNfCk8wSsmdgNd8ZdMjBCICtyCJCq9LiqgqvjCYe570/Wg7AQArSq1VQ60Dd/CHN7mQ==", - "dev": true, - "requires": { - "ast-types": "^0.14.2", - "lodash": "^4.17.15", - "react-docgen": "^5.0.0" - }, - "dependencies": { - "ast-types": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.14.2.tgz", - "integrity": "sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==", - "dev": true, - "requires": { - "tslib": "^2.0.1" - } - }, - "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true - } - } - }, - "babel-plugin-syntax-async-functions": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", - "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", - "dev": true - }, - "babel-plugin-syntax-async-generators": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz", - "integrity": "sha1-a8lj67FuzLrmuStZbrfzXDQqi5o=", - "dev": true - }, - "babel-plugin-syntax-class-constructor-call": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-constructor-call/-/babel-plugin-syntax-class-constructor-call-6.18.0.tgz", - "integrity": "sha1-nLnTn+Q8hgC+yBRkVt3L1OGnZBY=", - "dev": true - }, - "babel-plugin-syntax-class-properties": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz", - "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=", - "dev": true - }, - "babel-plugin-syntax-decorators": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz", - "integrity": "sha1-MSVjtNvePMgGzuPkFszurd0RrAs=", - "dev": true - }, - "babel-plugin-syntax-do-expressions": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-do-expressions/-/babel-plugin-syntax-do-expressions-6.13.0.tgz", - "integrity": "sha1-V0d1YTmqJtOQ0JQQsDdEugfkeW0=", - "dev": true - }, - "babel-plugin-syntax-dynamic-import": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz", - "integrity": "sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo=", - "dev": true - }, - "babel-plugin-syntax-exponentiation-operator": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", - "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=", - "dev": true - }, - "babel-plugin-syntax-export-extensions": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-export-extensions/-/babel-plugin-syntax-export-extensions-6.13.0.tgz", - "integrity": "sha1-cKFITw+QiaToStRLrDU8lbmxJyE=", - "dev": true - }, - "babel-plugin-syntax-flow": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz", - "integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=", - "dev": true - }, - "babel-plugin-syntax-function-bind": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-function-bind/-/babel-plugin-syntax-function-bind-6.13.0.tgz", - "integrity": "sha1-SMSV8Xe98xqYHnMvVa3AvdJgH0Y=", - "dev": true - }, - "babel-plugin-syntax-jsx": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", - "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=", - "dev": true - }, - "babel-plugin-syntax-object-rest-spread": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", - "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", - "dev": true - }, - "babel-plugin-syntax-trailing-function-commas": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", - "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=", - "dev": true - }, - "babel-plugin-transform-async-generator-functions": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz", - "integrity": "sha1-8FiQAUX9PpkHpt3yjaWfIVJYpds=", - "dev": true, - "requires": { - "babel-helper-remap-async-to-generator": "^6.24.1", - "babel-plugin-syntax-async-generators": "^6.5.0", - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-async-to-generator": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz", - "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", - "dev": true, - "requires": { - "babel-helper-remap-async-to-generator": "^6.24.1", - "babel-plugin-syntax-async-functions": "^6.8.0", - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-class-constructor-call": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-constructor-call/-/babel-plugin-transform-class-constructor-call-6.24.1.tgz", - "integrity": "sha1-gNwoVQWsBn3LjWxl4vbxGrd2Xvk=", - "dev": true, - "requires": { - "babel-plugin-syntax-class-constructor-call": "^6.18.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "babel-plugin-transform-class-properties": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz", - "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=", - "dev": true, - "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-plugin-syntax-class-properties": "^6.8.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "babel-plugin-transform-decorators": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz", - "integrity": "sha1-eIAT2PjGtSIr33s0Q5Df13Vp4k0=", - "dev": true, - "requires": { - "babel-helper-explode-class": "^6.24.1", - "babel-plugin-syntax-decorators": "^6.13.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-plugin-transform-do-expressions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-do-expressions/-/babel-plugin-transform-do-expressions-6.22.0.tgz", - "integrity": "sha1-KMyvkoEtlJws0SgfaQyP3EaK6bs=", - "dev": true, - "requires": { - "babel-plugin-syntax-do-expressions": "^6.8.0", - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-arrow-functions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", - "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-block-scoped-functions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", - "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-block-scoping": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", - "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" - } - }, - "babel-plugin-transform-es2015-classes": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", - "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", - "dev": true, - "requires": { - "babel-helper-define-map": "^6.24.1", - "babel-helper-function-name": "^6.24.1", - "babel-helper-optimise-call-expression": "^6.24.1", - "babel-helper-replace-supers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-computed-properties": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", - "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-destructuring": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", - "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-for-of": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", - "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-function-name": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", - "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", - "dev": true, - "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-literals": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", - "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-modules-commonjs": { - "version": "6.26.2", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", - "integrity": "sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==", - "dev": true, - "requires": { - "babel-plugin-transform-strict-mode": "^6.24.1", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-types": "^6.26.0" - } - }, - "babel-plugin-transform-es2015-object-super": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", - "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", - "dev": true, - "requires": { - "babel-helper-replace-supers": "^6.24.1", - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-parameters": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", - "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", - "dev": true, - "requires": { - "babel-helper-call-delegate": "^6.24.1", - "babel-helper-get-function-arity": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-shorthand-properties": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", - "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-spread": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", - "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-sticky-regex": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", - "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", - "dev": true, - "requires": { - "babel-helper-regex": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-template-literals": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", - "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-typeof-symbol": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", - "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-unicode-regex": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", - "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", - "dev": true, - "requires": { - "babel-helper-regex": "^6.24.1", - "babel-runtime": "^6.22.0", - "regexpu-core": "^2.0.0" - } - }, - "babel-plugin-transform-exponentiation-operator": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", - "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", - "dev": true, - "requires": { - "babel-helper-builder-binary-assignment-operator-visitor": "^6.24.1", - "babel-plugin-syntax-exponentiation-operator": "^6.8.0", - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-export-extensions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-export-extensions/-/babel-plugin-transform-export-extensions-6.22.0.tgz", - "integrity": "sha1-U3OLR+deghhYnuqUbLvTkQm75lM=", - "dev": true, - "requires": { - "babel-plugin-syntax-export-extensions": "^6.8.0", - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-flow-strip-types": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz", - "integrity": "sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=", - "dev": true, - "requires": { - "babel-plugin-syntax-flow": "^6.18.0", - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-function-bind": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-function-bind/-/babel-plugin-transform-function-bind-6.22.0.tgz", - "integrity": "sha1-xvuOlqwpajELjPjqQBRiQH3fapc=", - "dev": true, - "requires": { - "babel-plugin-syntax-function-bind": "^6.8.0", - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-object-rest-spread": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz", - "integrity": "sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=", - "dev": true, - "requires": { - "babel-plugin-syntax-object-rest-spread": "^6.8.0", - "babel-runtime": "^6.26.0" - } - }, - "babel-plugin-transform-react-display-name": { - "version": "6.25.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz", - "integrity": "sha1-Z+K/Hx6ck6sI25Z5LgU5K/LMKNE=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-react-jsx": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz", - "integrity": "sha1-hAoCjn30YN/DotKfDA2R9jduZqM=", - "dev": true, - "requires": { - "babel-helper-builder-react-jsx": "^6.24.1", - "babel-plugin-syntax-jsx": "^6.8.0", - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-react-jsx-source": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz", - "integrity": "sha1-ZqwSFT9c0tF7PBkmj0vwGX9E7NY=", - "dev": true, - "requires": { - "babel-plugin-syntax-jsx": "^6.8.0", - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-regenerator": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", - "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", - "dev": true, - "requires": { - "regenerator-transform": "^0.10.0" + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + } } }, - "babel-plugin-transform-strict-mode": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", - "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } + "babel-plugin-named-asset-import": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.7.tgz", + "integrity": "sha512-squySRkf+6JGnvjoUtDEjSREJEBirnXi9NqP6rjSYsylxQxqBTz+pkmf395i9E2zsvmYUaI40BHo6SqZUdydlw==", + "dev": true }, - "babel-polyfill": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", - "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", + "babel-plugin-polyfill-corejs2": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.2.tgz", + "integrity": "sha512-kISrENsJ0z5dNPq5eRvcctITNHYXWOA4DUZRFYCz3jYCcvTb/A546LIddmoGNMVYg2U38OyFeNosQwI9ENTqIQ==", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "regenerator-runtime": "^0.10.5" + "@babel/compat-data": "^7.13.11", + "@babel/helper-define-polyfill-provider": "^0.2.2", + "semver": "^6.1.1" }, "dependencies": { - "core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "@babel/compat-data": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.5.tgz", + "integrity": "sha512-kixrYn4JwfAVPa0f2yfzc2AWti6WRRyO3XjWW5PJAvtE11qhSayrrcrEnee05KAtNaPC+EwehE8Qt1UedEVB8w==", "dev": true }, - "regenerator-runtime": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true } } }, - "babel-preset-es2015": { - "version": "6.3.13", - "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.3.13.tgz", - "integrity": "sha1-l9zn7ykuGMubK3VF2AxZPCjZUX8=", - "dev": true, - "requires": { - "babel-plugin-check-es2015-constants": "^6.3.13", - "babel-plugin-transform-es2015-arrow-functions": "^6.3.13", - "babel-plugin-transform-es2015-block-scoped-functions": "^6.3.13", - "babel-plugin-transform-es2015-block-scoping": "^6.3.13", - "babel-plugin-transform-es2015-classes": "^6.3.13", - "babel-plugin-transform-es2015-computed-properties": "^6.3.13", - "babel-plugin-transform-es2015-destructuring": "^6.3.13", - "babel-plugin-transform-es2015-for-of": "^6.3.13", - "babel-plugin-transform-es2015-function-name": "^6.3.13", - "babel-plugin-transform-es2015-literals": "^6.3.13", - "babel-plugin-transform-es2015-modules-commonjs": "^6.3.13", - "babel-plugin-transform-es2015-object-super": "^6.3.13", - "babel-plugin-transform-es2015-parameters": "^6.3.13", - "babel-plugin-transform-es2015-shorthand-properties": "^6.3.13", - "babel-plugin-transform-es2015-spread": "^6.3.13", - "babel-plugin-transform-es2015-sticky-regex": "^6.3.13", - "babel-plugin-transform-es2015-template-literals": "^6.3.13", - "babel-plugin-transform-es2015-typeof-symbol": "^6.3.13", - "babel-plugin-transform-es2015-unicode-regex": "^6.3.13", - "babel-plugin-transform-regenerator": "^6.3.13" - } - }, - "babel-preset-react": { - "version": "6.3.13", - "resolved": "https://registry.npmjs.org/babel-preset-react/-/babel-preset-react-6.3.13.tgz", - "integrity": "sha1-E9VeBqZfqqoHw5v2Op2DbgMhFvo=", - "dev": true, - "requires": { - "babel-plugin-syntax-flow": "^6.3.13", - "babel-plugin-syntax-jsx": "^6.3.13", - "babel-plugin-transform-flow-strip-types": "^6.3.13", - "babel-plugin-transform-react-display-name": "^6.3.13", - "babel-plugin-transform-react-jsx": "^6.3.13", - "babel-plugin-transform-react-jsx-source": "^6.3.13" - } - }, - "babel-preset-stage-0": { - "version": "6.3.13", - "resolved": "https://registry.npmjs.org/babel-preset-stage-0/-/babel-preset-stage-0-6.3.13.tgz", - "integrity": "sha1-eKN8VvCzmI8qeZMtywzrj/N3sNE=", - "dev": true, - "requires": { - "babel-plugin-transform-do-expressions": "^6.3.13", - "babel-plugin-transform-function-bind": "^6.3.13", - "babel-preset-stage-1": "^6.3.13" - } - }, - "babel-preset-stage-1": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-preset-stage-1/-/babel-preset-stage-1-6.24.1.tgz", - "integrity": "sha1-dpLNfc1oSZB+auSgqFWJz7niv7A=", - "dev": true, - "requires": { - "babel-plugin-transform-class-constructor-call": "^6.24.1", - "babel-plugin-transform-export-extensions": "^6.22.0", - "babel-preset-stage-2": "^6.24.1" - } - }, - "babel-preset-stage-2": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-preset-stage-2/-/babel-preset-stage-2-6.24.1.tgz", - "integrity": "sha1-2eKWD7PXEYfw5k7sYrwHdnIZvcE=", + "babel-plugin-polyfill-corejs3": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.3.tgz", + "integrity": "sha512-rCOFzEIJpJEAU14XCcV/erIf/wZQMmMT5l5vXOpL5uoznyOGfDIjPj6FVytMvtzaKSTSVKouOCTPJ5OMUZH30g==", "dev": true, "requires": { - "babel-plugin-syntax-dynamic-import": "^6.18.0", - "babel-plugin-transform-class-properties": "^6.24.1", - "babel-plugin-transform-decorators": "^6.24.1", - "babel-preset-stage-3": "^6.24.1" + "@babel/helper-define-polyfill-provider": "^0.2.2", + "core-js-compat": "^3.14.0" } }, - "babel-preset-stage-3": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz", - "integrity": "sha1-g2raCp56f6N8sTj7kyb4eTSkg5U=", + "babel-plugin-polyfill-regenerator": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.2.tgz", + "integrity": "sha512-Goy5ghsc21HgPDFtzRkSirpZVW35meGoTmTOb2bxqdl60ghub4xOidgNTHaZfQ2FaxQsKmwvXtOAkcIS4SMBWg==", "dev": true, "requires": { - "babel-plugin-syntax-trailing-function-commas": "^6.22.0", - "babel-plugin-transform-async-generator-functions": "^6.24.1", - "babel-plugin-transform-async-to-generator": "^6.24.1", - "babel-plugin-transform-exponentiation-operator": "^6.24.1", - "babel-plugin-transform-object-rest-spread": "^6.22.0" + "@babel/helper-define-polyfill-provider": "^0.2.2" } }, - "babel-register": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", - "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", + "babel-plugin-react-docgen": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/babel-plugin-react-docgen/-/babel-plugin-react-docgen-4.2.1.tgz", + "integrity": "sha512-UQ0NmGHj/HAqi5Bew8WvNfCk8wSsmdgNd8ZdMjBCICtyCJCq9LiqgqvjCYe570/Wg7AQArSq1VQ60Dd/CHN7mQ==", "dev": true, "requires": { - "babel-core": "^6.26.0", - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "home-or-tmp": "^2.0.0", - "lodash": "^4.17.4", - "mkdirp": "^0.5.1", - "source-map-support": "^0.4.15" - }, - "dependencies": { - "babel-core": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-helpers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "convert-source-map": "^1.5.1", - "debug": "^2.6.9", - "json5": "^0.5.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.4", - "path-is-absolute": "^1.0.1", - "private": "^0.1.8", - "slash": "^1.0.0", - "source-map": "^0.5.7" + "ast-types": "^0.14.2", + "lodash": "^4.17.15", + "react-docgen": "^5.0.0" + }, + "dependencies": { + "ast-types": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.14.2.tgz", + "integrity": "sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==", + "dev": true, + "requires": { + "tslib": "^2.0.1" } }, - "core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", "dev": true } } }, + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=", + "dev": true + }, "babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", @@ -13810,19 +13146,6 @@ } } }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" - } - }, "babel-traverse": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", @@ -15054,6 +14377,12 @@ "chai": ">1.9.0" } }, + "chai-dom": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/chai-dom/-/chai-dom-1.10.0.tgz", + "integrity": "sha512-/FE0NvEGMXx1x1YQlc8ihLrEhH8JawflchuGe6ypIAX/4Zwmkr4cC3mfR9pDytbxsE/2LSm719TeU7VF/TCmtg==", + "dev": true + }, "chai-spies": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/chai-spies/-/chai-spies-1.0.0.tgz", @@ -15933,9 +15262,9 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, "cookiejar": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", + "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==", "dev": true }, "copy-concurrently": { @@ -17175,15 +16504,6 @@ "repeat-string": "^1.5.4" } }, - "detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } - }, "detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", @@ -17242,6 +16562,12 @@ "esutils": "^2.0.2" } }, + "dom-accessibility-api": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.10.tgz", + "integrity": "sha512-Xu9mD0UjrJisTmv7lmVSDMagQcU9R5hwAbxsaAE/35XPnPLJobbuREfV/rraiSaEj/UOvgrzQs66zyTWTlyd+g==", + "dev": true + }, "dom-converter": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", @@ -18607,18 +17933,207 @@ "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + } + } + }, + "eslint-plugin-react-hooks": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz", + "integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==", + "dev": true + }, + "eslint-plugin-testing-library": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.0.0.tgz", + "integrity": "sha512-lojlPN8nsb7JTFYhJuLNwwI8kALRC0TBz5JRO1lvV7Ifzqu7IoddjDFRCxeM+0d2/zuEO7Sb5oc7ErDqhd4MBw==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "^5.0.0" + }, + "dependencies": { + "@types/json-schema": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", + "dev": true + }, + "@typescript-eslint/experimental-utils": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.3.1.tgz", + "integrity": "sha512-RgFn5asjZ5daUhbK5Sp0peq0SSMytqcrkNfU4pnDma2D8P3ElZ6JbYjY8IMSFfZAJ0f3x3tnO3vXHweYg0g59w==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.3.1", + "@typescript-eslint/types": "5.3.1", + "@typescript-eslint/typescript-estree": "5.3.1", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + } + }, + "@typescript-eslint/types": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.3.1.tgz", + "integrity": "sha512-bG7HeBLolxKHtdHG54Uac750eXuQQPpdJfCYuw4ZI3bZ7+GgKClMWM8jExBtp7NSP4m8PmLRM8+lhzkYnSmSxQ==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.3.1.tgz", + "integrity": "sha512-PwFbh/PKDVo/Wct6N3w+E4rLZxUDgsoII/GrWM2A62ETOzJd4M6s0Mu7w4CWsZraTbaC5UQI+dLeyOIFF1PquQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.3.1", + "@typescript-eslint/visitor-keys": "5.3.1", + "debug": "^4.3.2", + "globby": "^11.0.4", + "is-glob": "^4.0.3", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.3.1.tgz", + "integrity": "sha512-3cHUzUuVTuNHx0Gjjt5pEHa87+lzyqOiHXy/Gz+SJOCW1mpw9xQHIIEwnKn+Thph1mgWyZ90nboOcSuZr/jTTQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.3.1", + "eslint-visitor-keys": "^3.0.0" + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz", + "integrity": "sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA==", + "dev": true + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "globby": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", + "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + } + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true } } }, - "eslint-plugin-react-hooks": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz", - "integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==", - "dev": true - }, "eslint-scope": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", @@ -19837,9 +19352,9 @@ "dev": true }, "formidable": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", - "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz", + "integrity": "sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==", "dev": true }, "forwarded": { @@ -22147,16 +21662,6 @@ "react-is": "^16.7.0" } }, - "home-or-tmp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", - "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", - "dev": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.1" - } - }, "hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -23166,15 +22671,6 @@ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, - "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", @@ -25254,6 +24750,12 @@ } } }, + "lz-string": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", + "integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=", + "dev": true + }, "mailparser": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.2.0.tgz", @@ -26822,16 +26324,16 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" }, "mocha": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.0.0.tgz", - "integrity": "sha512-GRGG/q9bIaUkHJB9NL+KZNjDhMBHB30zW3bZW9qOiYr+QChyLjPzswaxFWkI1q6lGlSL28EQYzAi2vKWNkPx+g==", + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.1.3.tgz", + "integrity": "sha512-Xcpl9FqXOAYqI3j79pEtHBBnQgVXIhpULjGQa7DVb0Po+VzmSIK9kanAiWLHoRR/dbZ2qpdPshuXr8l1VaHCzw==", "dev": true, "requires": { "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.5.1", - "debug": "4.3.1", + "chokidar": "3.5.2", + "debug": "4.3.2", "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", @@ -26842,13 +26344,12 @@ "log-symbols": "4.1.0", "minimatch": "3.0.4", "ms": "2.1.3", - "nanoid": "3.1.23", - "serialize-javascript": "5.0.1", + "nanoid": "3.1.25", + "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.1.4", + "workerpool": "6.1.5", "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" @@ -26875,25 +26376,10 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, "chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -26911,22 +26397,6 @@ } } }, - "chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", - "dev": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.3.1", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - } - }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -26937,9 +26407,9 @@ } }, "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "dev": true, "requires": { "ms": "2.1.2" @@ -26959,15 +26429,6 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, "find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -26978,13 +26439,6 @@ "path-exists": "^4.0.0" } }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, "glob": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", @@ -26999,45 +26453,12 @@ "path-is-absolute": "^1.0.0" } }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, "js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -27072,6 +26493,12 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, + "nanoid": { + "version": "3.1.25", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz", + "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==", + "dev": true + }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -27105,19 +26532,10 @@ "safe-buffer": "^5.1.0" } }, - "readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, "serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", "dev": true, "requires": { "randombytes": "^2.1.0" @@ -27138,15 +26556,6 @@ "has-flag": "^4.0.0" } }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -28486,12 +27895,6 @@ "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", "dev": true }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true - }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -30473,6 +29876,105 @@ "renderkid": "^2.0.4" } }, + "pretty-format": { + "version": "27.3.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.3.1.tgz", + "integrity": "sha512-DR/c+pvFc52nLimLROYjnXPtolawm+uWDxr4FjuLDLUn+ktWnSN851KoHwHzzqq6rfCOjkzN8FLgDrSub6UDuA==", + "dev": true, + "requires": { + "@jest/types": "^27.2.5", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "dependencies": { + "@jest/types": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.2.5.tgz", + "integrity": "sha512-nmuM4VuDtCZcY+eTpw+0nvstwReMsjPoj7ZR80/BbixulhLaiX+fbv8oeLW8WZlJMcsGQsTmMKT/iTZu1Uy/lQ==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + } + }, + "@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + } + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", @@ -31929,17 +31431,6 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" }, - "regenerator-transform": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", - "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", - "dev": true, - "requires": { - "babel-runtime": "^6.18.0", - "babel-types": "^6.19.0", - "private": "^0.1.6" - } - }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -31966,40 +31457,6 @@ "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", "dev": true }, - "regexpu-core": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", - "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", - "dev": true, - "requires": { - "regenerate": "^1.2.1", - "regjsgen": "^0.2.0", - "regjsparser": "^0.1.4" - } - }, - "regjsgen": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", - "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", - "dev": true - }, - "regjsparser": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", - "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", - "dev": true, - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true - } - } - }, "relateurl": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", @@ -32361,15 +31818,6 @@ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, - "requires": { - "is-finite": "^1.0.0" - } - }, "request": { "version": "2.88.0", "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", @@ -33101,9 +32549,9 @@ "dev": true }, "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, "slice-ansi": { @@ -33320,15 +32768,6 @@ "urix": "^0.1.0" } }, - "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "requires": { - "source-map": "^0.5.6" - } - }, "source-map-url": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", @@ -34895,9 +34334,9 @@ } }, "mime": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", - "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "dev": true }, "ms": { @@ -35741,12 +35180,6 @@ "escape-string-regexp": "^1.0.2" } }, - "trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", - "dev": true - }, "trim-trailing-lines": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz", @@ -37714,9 +37147,9 @@ } }, "workerpool": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.4.tgz", - "integrity": "sha512-jGWPzsUqzkow8HoAvqaPWTUPCrlPJaJ5tY8Iz7n1uCz3tTp6s3CDG0FF1NsX42WNlkRSW6Mr+CDZGnNoSsKa7g==", + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.5.tgz", + "integrity": "sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==", "dev": true }, "wrap-ansi": { diff --git a/package.json b/package.json index 38665a2c4d951..2122ded6b449b 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,10 @@ "coverage": "nyc -r html mocha --config ./.mocharc.js", "testci": "node .scripts/start.js", "testui": "cypress run --project tests", - "testapi": "mocha --config ./mocha_end_to_end.opts.js", + "testapi": "mocha --config ./.mocharc.api.js", "testunit": "mocha --config ./.mocharc.js", + "testunit-client": "mocha --config ./.mocharc.client.js", + "testunit-definition": "mocha --config ./.mocharc.definition.js", "testunit-watch": "mocha --watch --config ./.mocharc.js", "test": "npm run testapi && npm run testui", "translation-diff": "node .scripts/translationDiff.js", @@ -64,12 +66,16 @@ "@storybook/addon-postcss": "^2.0.0", "@storybook/addons": "^6.3.9", "@storybook/react": "^6.3.9", + "@testing-library/react": "^12.1.2", + "@testing-library/user-event": "^13.5.0", "@types/adm-zip": "^0.4.34", "@types/agenda": "^2.0.9", "@types/bad-words": "^3.0.1", "@types/bcrypt": "^5.0.0", "@types/body-parser": "^1.19.0", - "@types/chai": "^4.2.19", + "@types/chai": "^4.2.22", + "@types/chai-datetime": "0.0.37", + "@types/chai-dom": "0.0.11", "@types/chai-spies": "^1.0.3", "@types/clipboard": "^2.0.1", "@types/dompurify": "^2.2.2", @@ -78,6 +84,7 @@ "@types/fibers": "^3.1.0", "@types/imap": "^0.8.35", "@types/jsdom": "^16.2.12", + "@types/jsdom-global": "^3.0.2", "@types/ldapjs": "^2.2.1", "@types/less": "^3.0.2", "@types/lodash.get": "^4.4.6", @@ -85,7 +92,7 @@ "@types/marked": "^1.2.2", "@types/meteor": "1.4.74", "@types/mkdirp": "^1.0.1", - "@types/mocha": "^8.2.2", + "@types/mocha": "^8.2.3", "@types/mock-require": "^2.0.0", "@types/moment-timezone": "^0.5.30", "@types/mongodb": "^3.6.19", @@ -112,11 +119,10 @@ "autoprefixer": "^9.8.6", "babel-eslint": "^10.1.0", "babel-loader": "^8.2.2", - "babel-mocha-es6-compiler": "^0.1.0", "babel-plugin-array-includes": "^2.0.3", - "babel-polyfill": "^6.26.0", "chai": "^4.3.4", "chai-datetime": "^1.8.0", + "chai-dom": "^1.10.0", "chai-spies": "^1.0.0", "cross-env": "^7.0.3", "cypress": "^4.12.1", @@ -127,11 +133,12 @@ "eslint-plugin-prettier": "^3.4.0", "eslint-plugin-react": "^7.24.0", "eslint-plugin-react-hooks": "^4.2.0", + "eslint-plugin-testing-library": "^5.0.0", "fast-glob": "^3.2.5", "husky": "^7.0.1", "i18next": "^20.3.2", - "jsdom-global": "3.0.2", - "mocha": "^9.0.0", + "jsdom-global": "^3.0.2", + "mocha": "^9.1.3", "mock-require": "^3.0.3", "pino-pretty": "^7.1.0", "postcss": "^8.3.5", diff --git a/server/lib/fileUtils.tests.ts b/server/lib/fileUtils.tests.ts index 6c5b5853acbbb..5e82ad360864f 100644 --- a/server/lib/fileUtils.tests.ts +++ b/server/lib/fileUtils.tests.ts @@ -1,4 +1,3 @@ -/* eslint-env mocha */ import { expect } from 'chai'; import { fileName, joinPath } from './fileUtils'; diff --git a/tests/cypress/integration/12-settings.js b/tests/cypress/integration/12-settings.js index be803c1f5c305..c413e0c3b348e 100644 --- a/tests/cypress/integration/12-settings.js +++ b/tests/cypress/integration/12-settings.js @@ -46,8 +46,8 @@ describe('[Api Settings Change]', () => { }); it('/login', () => { - expect(credentials).to.have.property('X-Auth-Token').with.length.at.least(1); - expect(credentials).to.have.property('X-User-Id').with.length.at.least(1); + expect(credentials).to.have.property('X-Auth-Token').with.lengthOf.at.least(1); + expect(credentials).to.have.property('X-User-Id').with.lengthOf.at.least(1); }); describe('message edit:', () => { diff --git a/tests/cypress/integration/14-setting-permissions.js b/tests/cypress/integration/14-setting-permissions.js index a8270a0a00e4f..4bcf2ac1d4236 100644 --- a/tests/cypress/integration/14-setting-permissions.js +++ b/tests/cypress/integration/14-setting-permissions.js @@ -1,4 +1,3 @@ -/* eslint-env mocha */ import { assert } from 'chai'; import { adminUsername, adminEmail, adminPassword, username, email, password } from '../../data/user.js'; diff --git a/tests/cypress/integration/16-discussion.js b/tests/cypress/integration/16-discussion.js index 832a7c06fdd7a..238d22dffac64 100644 --- a/tests/cypress/integration/16-discussion.js +++ b/tests/cypress/integration/16-discussion.js @@ -1,4 +1,3 @@ -/* eslint-env mocha */ /* eslint-disable func-names, prefer-arrow-callback, no-var, space-before-function-paren, quotes, prefer-template, no-undef, no-unused-vars*/ diff --git a/tests/end-to-end/api/00-miscellaneous.js b/tests/end-to-end/api/00-miscellaneous.js index 6dc6b084ae447..e8d04bda5480b 100644 --- a/tests/end-to-end/api/00-miscellaneous.js +++ b/tests/end-to-end/api/00-miscellaneous.js @@ -43,8 +43,8 @@ describe('miscellaneous', function() { }); it('/login', () => { - expect(credentials).to.have.property('X-Auth-Token').with.length.at.least(1); - expect(credentials).to.have.property('X-User-Id').with.length.at.least(1); + expect(credentials).to.have.property('X-Auth-Token').with.lengthOf.at.least(1); + expect(credentials).to.have.property('X-User-Id').with.lengthOf.at.least(1); }); it('/login (wrapper username)', (done) => { diff --git a/tests/mocks/client/RouterContextMock.tsx b/tests/mocks/client/RouterContextMock.tsx new file mode 100644 index 0000000000000..71b765bde494a --- /dev/null +++ b/tests/mocks/client/RouterContextMock.tsx @@ -0,0 +1,44 @@ +import React, { ContextType, ReactElement, ReactNode, useMemo } from 'react'; +import { Subscription } from 'use-subscription'; + +import { RouterContext } from '../../../client/contexts/RouterContext'; + +type RouterContextMockProps = { + children?: ReactNode; + pushRoute?: (name: string, parameters?: Record, queryStringParameters?: Record) => void; + replaceRoute?: (name: string, parameters?: Record, queryStringParameters?: Record) => void; +}; + +const RouterContextMock = ({ children, pushRoute, replaceRoute }: RouterContextMockProps): ReactElement => { + const value = useMemo>(() => ({ + queryRoutePath: (): Subscription => ({ + getCurrentValue: (): undefined => undefined, + subscribe: () => (): void => undefined, + }), + queryRouteUrl: (): Subscription => ({ + getCurrentValue: (): undefined => undefined, + subscribe: () => (): void => undefined, + }), + pushRoute: pushRoute ?? ((): void => undefined), + replaceRoute: replaceRoute ?? ((): void => undefined), + queryRouteParameter: (): Subscription => ({ + getCurrentValue: (): undefined => undefined, + subscribe: () => (): void => undefined, + }), + queryQueryStringParameter: (): Subscription => ({ + getCurrentValue: (): undefined => undefined, + subscribe: () => (): void => undefined, + }), + queryCurrentRoute: (): Subscription<[undefined, {}, {}, undefined]> => ({ + getCurrentValue: (): [undefined, {}, {}, undefined] => [undefined, {}, {}, undefined], + subscribe: () => (): void => undefined, + }), + }), [pushRoute, replaceRoute]); + + return ; +}; + +export default RouterContextMock; diff --git a/tests/mocks/client/blobUrls.ts b/tests/mocks/client/blobUrls.ts new file mode 100644 index 0000000000000..5e01a99dc9276 --- /dev/null +++ b/tests/mocks/client/blobUrls.ts @@ -0,0 +1,23 @@ +import uuid from 'uuid'; + +export const enableBlobUrlsMock = (): void => { + const urlByBlob = new WeakMap(); + const blobByUrl = new Map(); + + window.URL.createObjectURL = (blob: Blob): string => { + const url = urlByBlob.get(blob) ?? `blob://${ uuid.v4() }`; + urlByBlob.set(blob, url); + blobByUrl.set(url, blob); + return url; + }; + + window.URL.revokeObjectURL = (url: string): void => { + const blob = blobByUrl.get(url); + if (!blob) { + return; + } + + urlByBlob.delete(blob); + blobByUrl.delete(url); + }; +}; diff --git a/tests/mocks/client/jsdom.ts b/tests/mocks/client/jsdom.ts new file mode 100644 index 0000000000000..857dc69896ae4 --- /dev/null +++ b/tests/mocks/client/jsdom.ts @@ -0,0 +1,10 @@ +import globalJsdom from 'jsdom-global'; + +export const enableJsdom = (): void => { + globalJsdom( + '', + { + url: 'http://localhost:3000', + }, + ); +}; diff --git a/tests/setup/chaiPlugins.ts b/tests/setup/chaiPlugins.ts new file mode 100644 index 0000000000000..a9f0a1dc5b869 --- /dev/null +++ b/tests/setup/chaiPlugins.ts @@ -0,0 +1,8 @@ +import chai from 'chai'; +import chaiSpies from 'chai-spies'; +import chaiDatetime from 'chai-datetime'; +import chaiDom from 'chai-dom'; + +chai.use(chaiSpies); +chai.use(chaiDatetime); +chai.use(chaiDom); diff --git a/tests/setup/cleanupTestingLibrary.ts b/tests/setup/cleanupTestingLibrary.ts new file mode 100644 index 0000000000000..663890796ac8c --- /dev/null +++ b/tests/setup/cleanupTestingLibrary.ts @@ -0,0 +1,17 @@ +import { cleanup } from '@testing-library/react'; + +/** + * Usually the testing library attachs its `cleanup` function by itself when an `afterEach` function is present at the + * global scope. It provides a simple mechanism for, e.g., unmounting React components after tests to avoid leaking + * memory and breaking the idempotence of subsequent tests. Despite working fine at a single run, when Mocha is run in + * _watch mode_ all hooks previously attached are discarded and reloaded from **tests files only**, and its supposed to + * work that way. + * + * See https://testing-library.com/docs/react-testing-library/setup#auto-cleanup-in-mochas-watch-mode + */ + +export const mochaHooks = { + afterEach(): void { + cleanup(); + }, +}; diff --git a/tests/setup/registerWebApiMocks.ts b/tests/setup/registerWebApiMocks.ts new file mode 100644 index 0000000000000..83bf826fa0d00 --- /dev/null +++ b/tests/setup/registerWebApiMocks.ts @@ -0,0 +1,5 @@ +import { enableBlobUrlsMock } from '../mocks/client/blobUrls'; +import { enableJsdom } from '../mocks/client/jsdom'; + +enableJsdom(); +enableBlobUrlsMock(); From 791ef5d84ddc92e767dd2d405257e537f7a6ee46 Mon Sep 17 00:00:00 2001 From: Lucas Dousse Date: Tue, 16 Nov 2021 15:46:23 +0100 Subject: [PATCH 073/137] [FIX] Fix typo in FR translation (#23711) --- packages/rocketchat-i18n/i18n/fr.i18n.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/rocketchat-i18n/i18n/fr.i18n.json b/packages/rocketchat-i18n/i18n/fr.i18n.json index 02e94993b189a..dda149f2be845 100644 --- a/packages/rocketchat-i18n/i18n/fr.i18n.json +++ b/packages/rocketchat-i18n/i18n/fr.i18n.json @@ -2919,7 +2919,7 @@ "Message_QuoteChainLimit": "Nombre maximum de citations enchaînées", "Message_Read_Receipt_Enabled": "Afficher les confirmations de lecture", "Message_Read_Receipt_Store_Users": "Reçus de lecture détaillés", - "Message_Read_Receipt_Store_Users_Description": "Affiche les conformations de lecture de chaque utilisateur", + "Message_Read_Receipt_Store_Users_Description": "Afficher les confirmations de lecture de chaque utilisateur", "Message_removed": "Message supprimé", "Message_sent_by_email": "Message envoyé par e-mail", "Message_ShowDeletedStatus": "Afficher le statut de suppression", @@ -4723,4 +4723,4 @@ "Your_temporary_password_is_password": "Votre mot de passe temporaire est [password].", "Your_TOTP_has_been_reset": "Votre TOTP à deux facteurs a été réinitialisé.", "Your_workspace_is_ready": "Votre espace de travail est prêt à l'emploi 🎉" -} \ No newline at end of file +} From c320e377a570e7b73ba57d093dfefe00ed43bf27 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Tue, 16 Nov 2021 09:54:04 -0600 Subject: [PATCH 074/137] Chore: add `no-bidi` rule (#23695) --- .eslintrc | 6 +- package-lock.json | 401 +++++++++++++++++++++++++++++++++++++++------- package.json | 1 + 3 files changed, 345 insertions(+), 63 deletions(-) diff --git a/.eslintrc b/.eslintrc index 212170f0e1c2c..0d96bb0a34f80 100644 --- a/.eslintrc +++ b/.eslintrc @@ -72,7 +72,8 @@ }, "plugins": [ "react", - "@typescript-eslint" + "@typescript-eslint", + "anti-trojan-source" ], "rules": { "func-call-spacing": "off", @@ -122,7 +123,8 @@ "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "ignoreRestSiblings": true - }] + }], + "anti-trojan-source/no-bidi": "error" }, "env": { "browser": true, diff --git a/package-lock.json b/package-lock.json index d9df5f975bd9b..3cd9701c99ac1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", - "dev": true, "requires": { "@babel/highlight": "^7.0.0" } @@ -1701,7 +1700,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", - "dev": true, "requires": { "chalk": "^2.0.0", "esutils": "^2.0.2", @@ -1712,7 +1710,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -1721,7 +1718,6 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -1732,7 +1728,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -1740,14 +1735,12 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -5086,7 +5079,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "requires": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -5095,14 +5087,12 @@ "@nodelib/fs.stat": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" }, "@nodelib/fs.walk": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.7.tgz", "integrity": "sha512-BTIhocbPBSrRmHxOAJFtR18oLhxTtAFDAvL8hY1S3iU8k+E60W/YFs4jrixGzQjMpF4qPXxIQHcjVD9dz1C2QA==", - "dev": true, "requires": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -10115,8 +10105,7 @@ "@types/normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", - "dev": true + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==" }, "@types/npmlog": { "version": "4.1.2", @@ -11469,6 +11458,316 @@ } } }, + "anti-trojan-source": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/anti-trojan-source/-/anti-trojan-source-1.3.2.tgz", + "integrity": "sha512-7ZlSTSBW+AULLsSkBuLU1naoWcV7pdjfYH54XgzXf4vZaxCgLF+iCiy4EOrpDtIXUQ1rOBdY91O2fxY/m0rXbg==", + "requires": { + "globby": "^12.0.2", + "meow": "^10.1.1" + }, + "dependencies": { + "@types/minimist": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==" + }, + "array-union": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz", + "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==" + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==" + }, + "camelcase-keys": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-7.0.1.tgz", + "integrity": "sha512-P331lEls98pW8JLyodNWfzuz91BEDVA4VpW2/SwXnyv2K495tq1N777xzDbFgnEigfA7UIY0xa6PwR/H9jijjA==", + "requires": { + "camelcase": "^6.2.0", + "map-obj": "^4.1.0", + "quick-lru": "^5.1.1", + "type-fest": "^1.2.1" + } + }, + "decamelize": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-5.0.1.tgz", + "integrity": "sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==" + }, + "fast-glob": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", + "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "requires": { + "is-glob": "^4.0.1" + } + }, + "globby": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-12.0.2.tgz", + "integrity": "sha512-lAsmb/5Lww4r7MM9nCCliDZVIKbZTavrsunAsHLr9oHthrZP1qi7/gAnHOsUs9bLvEt2vKVJhHmxuL7QbDuPdQ==", + "requires": { + "array-union": "^3.0.1", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.7", + "ignore": "^5.1.8", + "merge2": "^1.4.1", + "slash": "^4.0.0" + } + }, + "hosted-git-info": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz", + "integrity": "sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==" + }, + "is-core-module": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", + "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", + "requires": { + "has": "^1.0.3" + } + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "requires": { + "p-locate": "^5.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "meow": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-10.1.1.tgz", + "integrity": "sha512-uzOAEBTGujHAD6bVzIQQk5kDTgatxmpVmr1pj9QhwsHLEG2AiB+9F08/wmjrZIk4h5pWxERd7+jqGZywYx3ZFw==", + "requires": { + "@types/minimist": "^1.2.2", + "camelcase-keys": "^7.0.0", + "decamelize": "^5.0.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.2", + "read-pkg-up": "^8.0.0", + "redent": "^4.0.0", + "trim-newlines": "^4.0.2", + "type-fest": "^1.2.2", + "yargs-parser": "^20.2.9" + } + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + }, + "normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "requires": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "requires": { + "p-limit": "^3.0.2" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==" + }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" + }, + "read-pkg": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-6.0.0.tgz", + "integrity": "sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==", + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^3.0.2", + "parse-json": "^5.2.0", + "type-fest": "^1.0.1" + } + }, + "read-pkg-up": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-8.0.0.tgz", + "integrity": "sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==", + "requires": { + "find-up": "^5.0.0", + "read-pkg": "^6.0.0", + "type-fest": "^1.0.1" + } + }, + "redent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-4.0.0.tgz", + "integrity": "sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==", + "requires": { + "indent-string": "^5.0.0", + "strip-indent": "^4.0.0" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==" + }, + "strip-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.0.0.tgz", + "integrity": "sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==", + "requires": { + "min-indent": "^1.0.1" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + }, + "trim-newlines": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-4.0.2.tgz", + "integrity": "sha512-GJtWyq9InR/2HRiLZgpIKv+ufIKrVrvjQWEj7PxAXNc5dwbNJkqhAUoAGgzRmULAnoOM5EIpveYd3J2VeSAIew==" + }, + "type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" + } + } + }, "any-observable": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz", @@ -16321,7 +16620,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", - "dev": true, "requires": { "decamelize": "^1.1.0", "map-obj": "^1.0.0" @@ -16330,14 +16628,12 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" } } }, @@ -16548,7 +16844,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, "requires": { "path-type": "^4.0.0" } @@ -16996,7 +17291,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, "requires": { "is-arrayish": "^0.2.1" }, @@ -17004,8 +17298,7 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" } } }, @@ -17556,6 +17849,14 @@ } } }, + "eslint-plugin-anti-trojan-source": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/eslint-plugin-anti-trojan-source/-/eslint-plugin-anti-trojan-source-1.0.6.tgz", + "integrity": "sha512-UrX0RNLMvRaT0TJA+Hy7cEEJOvRHUOQs0umrPKqb54aylHwHAMy5Ms+nhgABTuDhGcbJgjAmTDX6cxxEFmx4Jg==", + "requires": { + "anti-trojan-source": "^1.3.1" + } + }, "eslint-plugin-import": { "version": "2.24.2", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.24.2.tgz", @@ -18818,7 +19119,6 @@ "version": "1.11.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz", "integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==", - "dev": true, "requires": { "reusify": "^1.0.4" } @@ -21336,8 +21636,7 @@ "hard-rejection": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "dev": true + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==" }, "has": { "version": "1.0.3", @@ -22155,8 +22454,7 @@ "ignore": { "version": "5.1.8", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", - "dev": true + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==" }, "image-size": { "version": "1.0.0", @@ -22668,8 +22966,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-fullwidth-code-point": { "version": "1.0.0", @@ -23505,8 +23802,7 @@ "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "json-schema": { "version": "0.2.3", @@ -24191,8 +24487,7 @@ "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", - "dev": true + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" }, "linkify-it": { "version": "3.0.2", @@ -24837,8 +25132,7 @@ "map-obj": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.2.1.tgz", - "integrity": "sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==", - "dev": true + "integrity": "sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==" }, "map-or-similar": { "version": "1.5.0", @@ -25284,8 +25578,7 @@ "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" }, "merkle-lib": { "version": "2.0.10", @@ -26093,8 +26386,7 @@ "min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==" }, "minimalistic-assert": { "version": "1.0.1", @@ -26123,7 +26415,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, "requires": { "arrify": "^1.0.1", "is-plain-obj": "^1.1.0", @@ -26133,14 +26424,12 @@ "is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" } } }, @@ -28206,8 +28495,7 @@ "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" }, "path.js": { "version": "1.0.7", @@ -30500,8 +30788,7 @@ "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" }, "quick-format-unescaped": { "version": "4.0.4", @@ -32004,8 +32291,7 @@ "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" }, "rewire": { "version": "5.0.0", @@ -32054,7 +32340,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "requires": { "queue-microtask": "^1.2.2" } @@ -32793,7 +33078,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -32802,14 +33086,12 @@ "spdx-exceptions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" }, "spdx-expression-parse": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -32818,8 +33100,7 @@ "spdx-license-ids": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz", - "integrity": "sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ==", - "dev": true + "integrity": "sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ==" }, "speakeasy": { "version": "2.0.0", @@ -36440,7 +36721,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, "requires": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" @@ -37445,8 +37725,7 @@ "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" }, "zip-stream": { "version": "2.1.3", diff --git a/package.json b/package.json index 2122ded6b449b..2e1aac5947f68 100644 --- a/package.json +++ b/package.json @@ -219,6 +219,7 @@ "ejson": "^2.2.1", "emailreplyparser": "^0.0.5", "emojione": "^4.5.0", + "eslint-plugin-anti-trojan-source": "^1.0.6", "eventemitter3": "^4.0.7", "exif-be-gone": "^1.2.0", "express": "^4.17.1", From 7ed68465e5a948a42b8e4fa6c2b63c084c78d326 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Tue, 16 Nov 2021 12:54:53 -0300 Subject: [PATCH 075/137] [FIX] PhotoSwipe crashing on show (#23499) Co-authored-by: dougfabris --- app/ui-master/client/main.html | 1 - app/ui/client/{index.js => index.ts} | 3 +- app/ui/client/views/app/photoswipe.js | 103 ------------ ...photoswipe.html => photoswipeContent.html} | 4 +- app/ui/client/views/app/photoswipeContent.ts | 158 ++++++++++++++++++ app/ui/{index.js => index.ts} | 0 package-lock.json | 6 + package.json | 1 + 8 files changed, 167 insertions(+), 109 deletions(-) rename app/ui/client/{index.js => index.ts} (94%) delete mode 100644 app/ui/client/views/app/photoswipe.js rename app/ui/client/views/app/{photoswipe.html => photoswipeContent.html} (95%) create mode 100644 app/ui/client/views/app/photoswipeContent.ts rename app/ui/{index.js => index.ts} (100%) diff --git a/app/ui-master/client/main.html b/app/ui-master/client/main.html index f226be71093a8..587453a419366 100644 --- a/app/ui-master/client/main.html +++ b/app/ui-master/client/main.html @@ -35,7 +35,6 @@ {{/if}} {{/unless}} {{ CustomScriptLoggedIn }} - {{> photoswipe}} {{/unless}} {{else}} {{> loading}} diff --git a/app/ui/client/index.js b/app/ui/client/index.ts similarity index 94% rename from app/ui/client/index.js rename to app/ui/client/index.ts index c30bf6f343704..c577eba51959c 100644 --- a/app/ui/client/index.js +++ b/app/ui/client/index.ts @@ -17,7 +17,6 @@ import './views/app/pageCustomContainer.html'; import './views/app/roomSearch.html'; import './views/app/secretURL.html'; import './views/app/userSearch.html'; -import './views/app/photoswipe.html'; import './views/cmsPage'; import './views/404/roomNotFound'; import './views/app/burger'; @@ -25,7 +24,7 @@ import './views/app/home'; import './views/app/roomSearch'; import './views/app/secretURL'; import './views/app/invite'; -import './views/app/photoswipe'; +import './views/app/photoswipeContent.ts'; // without the *.ts extension, *.html gets loaded first import './components/icon'; import './components/table.html'; import './components/table'; diff --git a/app/ui/client/views/app/photoswipe.js b/app/ui/client/views/app/photoswipe.js deleted file mode 100644 index e42ccdfe94954..0000000000000 --- a/app/ui/client/views/app/photoswipe.js +++ /dev/null @@ -1,103 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { Blaze } from 'meteor/blaze'; -import { Template } from 'meteor/templating'; -import { escapeHTML } from '@rocket.chat/string-helpers'; - -Meteor.startup(() => { - let currentGallery = null; - const initGallery = async (items, options) => { - Blaze.render(Template.photoswipeContent, document.body); - const [PhotoSwipeImport, PhotoSwipeUI_DefaultImport] = await Promise.all([import('photoswipe'), import('photoswipe/dist/photoswipe-ui-default'), import('photoswipe/dist/photoswipe.css')]); - if (!currentGallery) { - const PhotoSwipe = PhotoSwipeImport.default; - const PhotoSwipeUI_Default = PhotoSwipeUI_DefaultImport.default; - currentGallery = new PhotoSwipe(document.getElementById('pswp'), PhotoSwipeUI_Default, items, options); - currentGallery.listen('destroy', () => { - currentGallery = null; - }); - currentGallery.init(); - } - }; - - const defaultGalleryOptions = { - bgOpacity: 0.7, - showHideOpacity: true, - counterEl: false, - shareEl: false, - clickToCloseNonZoomable: false, - }; - - const createEventListenerFor = (className) => (event) => { - event.preventDefault(); - event.stopPropagation(); - - const galleryOptions = { - ...defaultGalleryOptions, - index: 0, - addCaptionHTMLFn(item, captionEl) { - captionEl.children[0].innerHTML = `${ escapeHTML(item.title) }
    ${ escapeHTML(item.description) }`; - return true; - }, - }; - - const items = Array.from(document.querySelectorAll(className)) - .map((element, i) => { - if (element === event.currentTarget) { - galleryOptions.index = i; - } - - const item = { - src: element.src, - w: element.naturalWidth, - h: element.naturalHeight, - title: element.dataset.title || element.title, - description: element.dataset.description, - }; - - if (element.dataset.src || element.href) { - // use stored sizes if available - if (element.dataset.width && element.dataset.height) { - return { - ...item, - h: element.dataset.height, - w: element.dataset.width, - src: element.dataset.src || element.href, - }; - } - - const img = new Image(); - - img.addEventListener('load', () => { - if (!currentGallery) { - return; - } - - // stores loaded sizes on original image element - element.dataset.width = img.naturalWidth; - element.dataset.height = img.naturalHeight; - - delete currentGallery.items[i].html; - currentGallery.items[i].src = img.src; - currentGallery.items[i].w = img.naturalWidth; - currentGallery.items[i].h = img.naturalHeight; - currentGallery.invalidateCurrItems(); - currentGallery.updateSize(true); - }); - - img.src = element.dataset.src || element.href; - - return { - ...item, - msrc: element.src, - src: element.dataset.src || element.href, - }; - } - - return item; - }); - - initGallery(items, galleryOptions); - }; - - $(document).on('click', '.gallery-item', createEventListenerFor('.gallery-item')); -}); diff --git a/app/ui/client/views/app/photoswipe.html b/app/ui/client/views/app/photoswipeContent.html similarity index 95% rename from app/ui/client/views/app/photoswipe.html rename to app/ui/client/views/app/photoswipeContent.html index d4ee792610e04..0eb80ecbd7fa6 100644 --- a/app/ui/client/views/app/photoswipe.html +++ b/app/ui/client/views/app/photoswipeContent.html @@ -1,5 +1,3 @@ - - \ No newline at end of file + diff --git a/app/ui/client/views/app/photoswipeContent.ts b/app/ui/client/views/app/photoswipeContent.ts new file mode 100644 index 0000000000000..7e6ee33b9176b --- /dev/null +++ b/app/ui/client/views/app/photoswipeContent.ts @@ -0,0 +1,158 @@ +import { Meteor } from 'meteor/meteor'; +import { Blaze } from 'meteor/blaze'; +import { Template } from 'meteor/templating'; +import { escapeHTML } from '@rocket.chat/string-helpers'; +import type PhotoSwipe from 'photoswipe'; +import type PhotoSwipeUiDefault from 'photoswipe/dist/photoswipe-ui-default'; + +const parseLength = (x: unknown): number | undefined => { + const length = typeof x === 'string' ? parseInt(x, 10) : undefined; + return Number.isFinite(length) ? length : undefined; +}; + +const getImageSize = (src: string): Promise<[w: number, h: number]> => new Promise((resolve, reject) => { + const img = new Image(); + + img.addEventListener('load', () => { + resolve([img.naturalWidth, img.naturalHeight]); + }); + + img.addEventListener('error', (error) => { + reject(error.error); + }); + + img.src = src; +}); + +type Slide = PhotoSwipeUiDefault.Item & { description?: string }; + +const fromElementToSlide = async (element: Element): Promise => { + if (!(element instanceof HTMLElement)) { + return null; + } + + const title = element.dataset.title || element.title; + const { description } = element.dataset; + + if (element instanceof HTMLAnchorElement) { + const src = element.dataset.src || element.href; + let w = parseLength(element.dataset.width); + let h = parseLength(element.dataset.height); + + if (w === undefined || h === undefined) { + [w, h] = await getImageSize(src); + } + + return { src, w, h, title, description }; + } + + if (element instanceof HTMLImageElement) { + let msrc: string | undefined; + let { src } = element; + let w: number | undefined = element.naturalWidth; + let h: number | undefined = element.naturalHeight; + + if (element.dataset.src) { + msrc = element.src; + src = element.dataset.src; + w = parseLength(element.dataset.width); + h = parseLength(element.dataset.height); + + if (w === undefined || h === undefined) { + [w, h] = await getImageSize(src); + } + } + + return { msrc, src, w, h, title, description }; + } + + return null; +}; + +let currentGallery: PhotoSwipe | null = null; + +const initGallery = async (items: Slide[], options: PhotoSwipeUiDefault.Options): Promise => { + const [ + { default: PhotoSwipe }, + { default: PhotoSwipeUiDefault }, // eslint-disable-line @typescript-eslint/camelcase + ] = await Promise.all([ + import('photoswipe'), + import('photoswipe/dist/photoswipe-ui-default'), + // @ts-ignore + import('photoswipe/dist/photoswipe.css'), + // @ts-ignore + import('./photoswipeContent.html'), + ]); + + Blaze.render(Template.photoswipeContent, document.body); + + if (!currentGallery) { + const container = document.getElementById('pswp'); + + if (!container) { + throw new Error('Photoswipe container element not found'); + } + + currentGallery = new PhotoSwipe(container, PhotoSwipeUiDefault, items, options); + + currentGallery.listen('destroy', () => { + currentGallery = null; + }); + + currentGallery.init(); + } +}; + +const defaultGalleryOptions: PhotoSwipeUiDefault.Options = { + bgOpacity: 0.7, + showHideOpacity: true, + counterEl: false, + shareEl: false, + clickToCloseNonZoomable: false, + index: 0, + addCaptionHTMLFn(item: Slide, captionEl: HTMLElement): boolean { + captionEl.children[0].innerHTML = ` + ${ escapeHTML(item.title ?? '') }
    + ${ escapeHTML(item.description ?? '') } + `; + return true; + }, +}; + +const createEventListenerFor = (className: string) => (event: JQuery.ClickEvent): void => { + event.preventDefault(); + event.stopPropagation(); + + const { currentTarget } = event; + + Array.from(document.querySelectorAll(className)) + .sort((a, b) => { + if (a === currentTarget) { + return -1; + } + + if (b === currentTarget) { + return 1; + } + + return 0; + }) + .map((element) => fromElementToSlide(element)) + .reduce((p, curr) => p.then(() => curr).then(async (slide) => { + if (!slide) { + return; + } + + if (!currentGallery) { + return initGallery([slide], defaultGalleryOptions); + } + + currentGallery.items.push(slide); + currentGallery.invalidateCurrItems(); + currentGallery.updateSize(true); + }), Promise.resolve()); +}; + +Meteor.startup(() => { + $(document).on('click', '.gallery-item', createEventListenerFor('.gallery-item')); +}); diff --git a/app/ui/index.js b/app/ui/index.ts similarity index 100% rename from app/ui/index.js rename to app/ui/index.ts diff --git a/package-lock.json b/package-lock.json index 3cd9701c99ac1..d76d900ad1760 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10161,6 +10161,12 @@ "@types/node": "*" } }, + "@types/photoswipe": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@types/photoswipe/-/photoswipe-4.1.2.tgz", + "integrity": "sha512-HA9TtCAQKToldgxRiyJ1DbsElg/cQV/SQ8COVjqIqghjy60Zxfh78E1WiFotthquqkS86nz13Za9wEbToe0svQ==", + "dev": true + }, "@types/pretty-hrtime": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/pretty-hrtime/-/pretty-hrtime-1.0.1.tgz", diff --git a/package.json b/package.json index 2e1aac5947f68..5a7bcf01fbf73 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,7 @@ "@types/node-rsa": "^1.1.1", "@types/nodemailer": "^6.4.2", "@types/parseurl": "^1.3.1", + "@types/photoswipe": "^4.1.2", "@types/psl": "^1.1.0", "@types/react": "^17.0.32", "@types/react-dom": "^17.0.10", From d232e8164fcab564463e4cc3a5e68bdb864109ac Mon Sep 17 00:00:00 2001 From: Tiago Evangelista Pinto Date: Tue, 16 Nov 2021 13:24:05 -0300 Subject: [PATCH 076/137] [FIX] Prevent UserAction.addStream without Subscription (#23705) Co-authored-by: Guilherme Gazzo --- app/ui/client/lib/UserAction.ts | 6 ------ client/lib/RoomManager.ts | 4 ++-- client/views/room/contexts/RoomContext.ts | 1 + client/views/room/providers/RoomProvider.tsx | 20 +++++++++++++++++++- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/app/ui/client/lib/UserAction.ts b/app/ui/client/lib/UserAction.ts index 847de8e190791..8925e0351b99e 100644 --- a/app/ui/client/lib/UserAction.ts +++ b/app/ui/client/lib/UserAction.ts @@ -1,7 +1,5 @@ import { Meteor } from 'meteor/meteor'; -import { Tracker } from 'meteor/tracker'; import { ReactiveDict } from 'meteor/reactive-dict'; -import { Session } from 'meteor/session'; import { debounce } from 'lodash'; import { settings } from '../../../settings/client'; @@ -66,10 +64,6 @@ function handleStreamAction(rid: string, username: string, activityTypes: string performingUsers.set(rid, roomActivities); } export const UserAction = new class { - constructor() { - Tracker.autorun(() => Session.get('openedRoom') && this.addStream(Session.get('openedRoom'))); - } - addStream(rid: string): void { if (rooms.get(rid)) { return; diff --git a/client/lib/RoomManager.ts b/client/lib/RoomManager.ts index 78a4104bddeb1..f10a06c4257e4 100644 --- a/client/lib/RoomManager.ts +++ b/client/lib/RoomManager.ts @@ -132,7 +132,7 @@ export const RoomManager = new (class RoomManager extends Emitter<{ } })(); -const subscribeVistedRooms: Subscription = { +const subscribeVisitedRooms: Subscription = { getCurrentValue: () => RoomManager.visitedRooms(), subscribe(callback) { return RoomManager.on('changed', callback); @@ -166,7 +166,7 @@ export const useHandleRoom = (rid: IRoom['_id']): AsyncState return state; }; -export const useVisitedRooms = (): IRoom['_id'][] => useSubscription(subscribeVistedRooms); +export const useVisitedRooms = (): IRoom['_id'][] => useSubscription(subscribeVisitedRooms); export const useOpenedRoom = (): IRoom['_id'] | undefined => useSubscription(subscribeOpenedRoom); diff --git a/client/views/room/contexts/RoomContext.ts b/client/views/room/contexts/RoomContext.ts index 624cc4b981660..bea6ae8af3dfd 100644 --- a/client/views/room/contexts/RoomContext.ts +++ b/client/views/room/contexts/RoomContext.ts @@ -5,6 +5,7 @@ import { IRoom, IOmnichannelRoom, isOmnichannelRoom } from '../../../../definiti export type RoomContextValue = { rid: IRoom['_id']; room: IRoom; + subscribed: boolean; }; export const RoomContext = createContext(null); diff --git a/client/views/room/providers/RoomProvider.tsx b/client/views/room/providers/RoomProvider.tsx index ada05e29c237a..8630ecc4741e0 100644 --- a/client/views/room/providers/RoomProvider.tsx +++ b/client/views/room/providers/RoomProvider.tsx @@ -1,7 +1,9 @@ import React, { ReactNode, useMemo, memo, useEffect } from 'react'; +import { UserAction } from '../../../../app/ui'; import { roomTypes } from '../../../../app/utils/client'; import { IRoom } from '../../../../definition/IRoom'; +import { useUserSubscription } from '../../../contexts/UserContext'; import { RoomManager, useHandleRoom } from '../../../lib/RoomManager'; import { AsyncStatePhase } from '../../../lib/asyncState'; import Skeleton from '../Room/Skeleton'; @@ -13,18 +15,23 @@ export type Props = { rid: IRoom['_id']; }; +const fields = {}; + const RoomProvider = ({ rid, children }: Props): JSX.Element => { const { phase, value: room } = useHandleRoom(rid); + + const subscribed = Boolean(useUserSubscription(rid, fields)); const context = useMemo(() => { if (!room) { return null; } room._id = rid; return { + subscribed, rid, room: { ...room, name: roomTypes.getRoomName(room.t, room) }, }; - }, [room, rid]); + }, [room, rid, subscribed]); useEffect(() => { RoomManager.open(rid); @@ -33,6 +40,17 @@ const RoomProvider = ({ rid, children }: Props): JSX.Element => { }; }, [rid]); + useEffect(() => { + if (!subscribed) { + return (): void => undefined; + } + + UserAction.addStream(rid); + return (): void => { + UserAction.cancel(rid); + }; + }, [rid, subscribed]); + if (phase === AsyncStatePhase.LOADING || !room) { return ; } From eb0f205096c4bc7ae6a3c4e07dec8c155c7f24b7 Mon Sep 17 00:00:00 2001 From: Aman-Maheshwari <50165440+Aman-Maheshwari@users.noreply.github.com> Date: Tue, 16 Nov 2021 21:57:42 +0530 Subject: [PATCH 077/137] [FIX] broken avatar preview when changing avatar (#23659) --- .../avatar/UserAvatarEditor/UserAvatarEditor.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/client/components/avatar/UserAvatarEditor/UserAvatarEditor.js b/client/components/avatar/UserAvatarEditor/UserAvatarEditor.js index 46a94c62379ea..0c4832943b065 100644 --- a/client/components/avatar/UserAvatarEditor/UserAvatarEditor.js +++ b/client/components/avatar/UserAvatarEditor/UserAvatarEditor.js @@ -19,10 +19,20 @@ function UserAvatarEditor({ const [newAvatarSource, setNewAvatarSource] = useState(); const [urlEmpty, setUrlEmpty] = useState(true); + const toDataURL = (file, callback) => { + const reader = new FileReader(); + reader.onload = function (e) { + callback(e.target.result); + }; + reader.readAsDataURL(file); + }; + const setUploadedPreview = useCallback( async (file, avatarObj) => { setAvatarObj(avatarObj); - setNewAvatarSource(URL.createObjectURL(file)); + toDataURL(file, (dataurl) => { + setNewAvatarSource(dataurl); + }); }, [setAvatarObj], ); From 7660464d086ecdaef18cf3bd9eae7684ff317f18 Mon Sep 17 00:00:00 2001 From: Leonardo Ostjen Couto Date: Tue, 16 Nov 2021 13:43:13 -0300 Subject: [PATCH 078/137] [FIX] Fixed E2E default room settings not being honoured (#23468) Co-authored-by: Tasso Evangelista --- app/e2e/server/beforeCreateRoom.js | 5 ++-- app/e2e/server/settings.ts | 2 ++ tests/end-to-end/api/03-groups.js | 45 ++++++++++++++++++++++++++++-- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/app/e2e/server/beforeCreateRoom.js b/app/e2e/server/beforeCreateRoom.js index ce3b21ad69355..a8c6a89335196 100644 --- a/app/e2e/server/beforeCreateRoom.js +++ b/app/e2e/server/beforeCreateRoom.js @@ -3,9 +3,8 @@ import { settings } from '../../settings/server'; callbacks.add('beforeCreateRoom', ({ type, extraData }) => { if ( - settings.get('E2E_Enabled') && ((type === 'd' && settings.get('E2E_Enabled_Default_DirectRooms')) - || (type === 'p' && settings.get('E2E_Enabled_Default_PrivateRooms'))) - ) { + settings.get('E2E_Enable') && ((type === 'd' && settings.get('E2E_Enabled_Default_DirectRooms')) + || (type === 'p' && settings.get('E2E_Enabled_Default_PrivateRooms')))) { extraData.encrypted = extraData.encrypted ?? true; } }); diff --git a/app/e2e/server/settings.ts b/app/e2e/server/settings.ts index 3b3aad9e6dc79..20c624fed9b54 100644 --- a/app/e2e/server/settings.ts +++ b/app/e2e/server/settings.ts @@ -11,11 +11,13 @@ settingsRegistry.addGroup('E2E Encryption', function() { this.add('E2E_Enabled_Default_DirectRooms', false, { type: 'boolean', + public: true, enableQuery: { _id: 'E2E_Enable', value: true }, }); this.add('E2E_Enabled_Default_PrivateRooms', false, { type: 'boolean', + public: true, enableQuery: { _id: 'E2E_Enable', value: true }, }); }); diff --git a/tests/end-to-end/api/03-groups.js b/tests/end-to-end/api/03-groups.js index b20d253fb1701..5cc6f531aa4a9 100644 --- a/tests/end-to-end/api/03-groups.js +++ b/tests/end-to-end/api/03-groups.js @@ -3,7 +3,7 @@ import { expect } from 'chai'; import { getCredentials, api, request, credentials, group, apiPrivateChannelName } from '../../data/api-data.js'; import { adminUsername, password } from '../../data/user.js'; import { createUser, login } from '../../data/users.helper'; -import { updatePermission } from '../../data/permissions.helper'; +import { updatePermission, updateSetting } from '../../data/permissions.helper'; import { createRoom } from '../../data/rooms.helper'; import { createIntegration, removeIntegration } from '../../data/integration.helper'; @@ -43,7 +43,49 @@ describe('[Groups]', function() { }) .end(done); }); + describe('/groups.create (encrypted)', () => { + it('should create a new encrypted group', async () => { + await request.post(api('groups.create')) + .set(credentials) + .send({ + name: `encrypted-${ apiPrivateChannelName }`, + extraData: { + encrypted: true, + }, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.nested.property('group.name', `encrypted-${ apiPrivateChannelName }`); + expect(res.body).to.have.nested.property('group.t', 'p'); + expect(res.body).to.have.nested.property('group.msgs', 0); + expect(res.body).to.have.nested.property('group.encrypted', true); + }); + }); + it('should create the encrypted room by default', async () => { + await updateSetting('E2E_Enabled_Default_PrivateRooms', true); + try { + await request.post(api('groups.create')) + .set(credentials) + .send({ + name: `default-encrypted-${ apiPrivateChannelName }`, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.nested.property('group.name', `default-encrypted-${ apiPrivateChannelName }`); + expect(res.body).to.have.nested.property('group.t', 'p'); + expect(res.body).to.have.nested.property('group.msgs', 0); + expect(res.body).to.have.nested.property('group.encrypted', true); + }); + } finally { + await updateSetting('E2E_Enabled_Default_PrivateRooms', false); + } + }); + }); describe('[/groups.info]', () => { let testGroup = {}; let groupMessage = {}; @@ -177,7 +219,6 @@ describe('[Groups]', function() { it('/groups.invite', async () => { const roomInfo = await getRoomInfo(group._id); - return request.post(api('groups.invite')) .set(credentials) .send({ From 485676a0053804063ba16c47fc5ea3cd54f7caba Mon Sep 17 00:00:00 2001 From: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> Date: Wed, 17 Nov 2021 19:35:22 +0530 Subject: [PATCH 079/137] Improve naming for twilio department param (#23725) --- app/livechat/imports/server/rest/sms.js | 4 ++-- packages/rocketchat-i18n/i18n/en.i18n.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/livechat/imports/server/rest/sms.js b/app/livechat/imports/server/rest/sms.js index 95625ded26d34..ac180f4c652ea 100644 --- a/app/livechat/imports/server/rest/sms.js +++ b/app/livechat/imports/server/rest/sms.js @@ -69,8 +69,8 @@ API.v1.addRoute('livechat/sms-incoming/:service', { post() { const SMSService = SMS.getService(this.urlParams.service); const sms = SMSService.parse(this.bodyParams); - const { departmentName } = this.queryParams; - let targetDepartment = defineDepartment(departmentName || SMS.department); + const { department } = this.queryParams; + let targetDepartment = defineDepartment(department || SMS.department); if (!targetDepartment) { targetDepartment = defineDepartment(SMS.department); } diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 6fd8880bbb617..a616292d9191e 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -3911,7 +3911,7 @@ "Smileys_and_People": "Smileys & People", "SMS": "SMS", "SMS_Default_Omnichannel_Department": "Omnichannel Department (Default)", - "SMS_Default_Omnichannel_Department_Description": "If set, all new incoming chats initiated by this integration will be routed to this department.\nThis setting can be overwritten by passing departmentName query param in the request.\ne.g. https:///api/v1/livechat/sms-incoming/twilio?departmentName=< departmentName>.\nDepartment name should be URL safe.", + "SMS_Default_Omnichannel_Department_Description": "If set, all new incoming chats initiated by this integration will be routed to this department.\nThis setting can be overwritten by passing department query param in the request.\ne.g. https:///api/v1/livechat/sms-incoming/twilio?department=.\nNote: if you're using Department Name, then it should be URL safe.", "SMS_Enabled": "SMS Enabled", "SMTP": "SMTP", "SMTP_Host": "SMTP Host", From 56b6d334b963e4f8a1b8f85920e04fd3bf082e44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Felchar?= <11652381+cauefcr@users.noreply.github.com> Date: Wed, 17 Nov 2021 11:12:54 -0300 Subject: [PATCH 080/137] [NEW] Allow Omnichannel statistics to be collected. (#23694) * partial reconstruction, as progress was lost * Remade code * fixing typo * Update app/statistics/server/lib/statistics.js Co-authored-by: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> * Update app/statistics/server/lib/statistics.js Co-authored-by: Kevin Aleman * Adding index and fixing broken commit * Applying code suggestion * changed index creation location Co-authored-by: Caue Felchar Co-authored-by: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> Co-authored-by: Kevin Aleman --- app/models/server/models/LivechatRooms.js | 1 + app/models/server/raw/Rooms.js | 19 +++++++++++++++++++ app/statistics/server/lib/statistics.js | 13 ++++++++++++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/app/models/server/models/LivechatRooms.js b/app/models/server/models/LivechatRooms.js index 6ef6dd0f44e65..d899284b46863 100644 --- a/app/models/server/models/LivechatRooms.js +++ b/app/models/server/models/LivechatRooms.js @@ -21,6 +21,7 @@ export class LivechatRooms extends Base { this.tryEnsureIndex({ 'v.token': 1, 'email.thread': 1 }, { sparse: true }); this.tryEnsureIndex({ 'v._id': 1 }, { sparse: true }); this.tryEnsureIndex({ t: 1, departmentId: 1, closedAt: 1 }, { partialFilterExpression: { closedAt: { $exists: true } } }); + this.tryEnsureIndex({ source: 1 }, { sparse: true }); } findLivechat(filter = {}, offset = 0, limit = 20) { diff --git a/app/models/server/raw/Rooms.js b/app/models/server/raw/Rooms.js index 0da61636ef86a..4be1c8a913ceb 100644 --- a/app/models/server/raw/Rooms.js +++ b/app/models/server/raw/Rooms.js @@ -397,4 +397,23 @@ export class RoomsRaw extends BaseRaw { findOneByNameOrFname(name, options = {}) { return this.col.findOne({ $or: [{ name }, { fname: name }] }, options); } + + allRoomSourcesCount() { + return this.col.aggregate([ + { + $match: { + source: { + $exists: true, + }, + t: 'l', + }, + }, + { + $group: { + _id: '$source', + count: { $sum: 1 }, + }, + }, + ]); + } } diff --git a/app/statistics/server/lib/statistics.js b/app/statistics/server/lib/statistics.js index c9d99c049dc53..3fa09484ed4d3 100644 --- a/app/statistics/server/lib/statistics.js +++ b/app/statistics/server/lib/statistics.js @@ -17,7 +17,7 @@ import { settings } from '../../../settings/server'; import { Info, getMongoInfo } from '../../../utils/server'; import { getControl } from '../../../../server/lib/migrations'; import { getStatistics as federationGetStatistics } from '../../../federation/server/functions/dashboard'; -import { NotificationQueue, Users as UsersRaw, Statistics, Sessions, Integrations, Uploads } from '../../../models/server/raw'; +import { NotificationQueue, Users as UsersRaw, Rooms as RoomsRaw, Statistics, Sessions, Integrations, Uploads } from '../../../models/server/raw'; import { readSecondaryPreferred } from '../../../../server/database/readSecondaryPreferred'; import { getAppsStatistics } from './getAppsStatistics'; import { getServicesStatistics } from './getServicesStatistics'; @@ -116,6 +116,17 @@ export const statistics = { // livechat enabled statistics.livechatEnabled = settings.get('Livechat_enabled'); + // Count and types of omnichannel rooms + statistics.omnichannelSources = Promise.await(RoomsRaw.allRoomSourcesCount().toArray()).map(({ + _id: { id, alias, type }, + count, + }) => ({ + id, + alias, + type, + count, + })); + // Message statistics statistics.totalChannelMessages = _.reduce(Rooms.findByType('c', { fields: { msgs: 1 } }).fetch(), function _countChannelMessages(num, room) { return num + room.msgs; }, 0); statistics.totalPrivateGroupMessages = _.reduce(Rooms.findByType('p', { fields: { msgs: 1 } }).fetch(), function _countPrivateGroupMessages(num, room) { return num + room.msgs; }, 0); From 6ede9394136aa0acb8520c414c533086a1b0811b Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Wed, 17 Nov 2021 12:16:07 -0300 Subject: [PATCH 081/137] [FIX] Discussions created inside discussions (#23733) --- app/discussion/client/createDiscussionMessageAction.js | 4 ++-- app/discussion/client/discussionFromMessageBox.js | 2 +- app/discussion/server/methods/createDiscussion.js | 2 +- app/ui-message/client/messageBox/messageBox.js | 2 ++ 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/discussion/client/createDiscussionMessageAction.js b/app/discussion/client/createDiscussionMessageAction.js index 7d107be268b22..120001c9e52d3 100644 --- a/app/discussion/client/createDiscussionMessageAction.js +++ b/app/discussion/client/createDiscussionMessageAction.js @@ -22,12 +22,12 @@ Meteor.startup(function() { label: 'Discussion_start', context: ['message', 'message-mobile'], async action() { - const { msg: message } = messageArgs(this); + const { msg: message, room } = messageArgs(this); imperativeModal.open({ component: CreateDiscussion, props: { - defaultParentRoom: message.rid, + defaultParentRoom: room.prid || room._id, onClose: imperativeModal.close, parentMessageId: message._id, nameSuggestion: message?.msg?.substr(0, 140), diff --git a/app/discussion/client/discussionFromMessageBox.js b/app/discussion/client/discussionFromMessageBox.js index 668cf6ac75b54..e2a8b29c845bb 100644 --- a/app/discussion/client/discussionFromMessageBox.js +++ b/app/discussion/client/discussionFromMessageBox.js @@ -20,7 +20,7 @@ Meteor.startup(function() { imperativeModal.open({ component: CreateDiscussion, props: { - defaultParentRoom: data.rid, + defaultParentRoom: data.prid || data.rid, onClose: imperativeModal.close, }, }); diff --git a/app/discussion/server/methods/createDiscussion.js b/app/discussion/server/methods/createDiscussion.js index b9d28a0d58df7..8c1ea7dbb3d35 100644 --- a/app/discussion/server/methods/createDiscussion.js +++ b/app/discussion/server/methods/createDiscussion.js @@ -38,7 +38,7 @@ const mentionMessage = (rid, { _id, username, name }, message_embedded) => { }; const create = ({ prid, pmid, t_name, reply, users, user, encrypted }) => { - // if you set both, prid and pmid, and the rooms doesnt match... should throw an error) + // if you set both, prid and pmid, and the rooms dont match... should throw an error) let message = false; if (pmid) { message = Messages.findOne({ _id: pmid }); diff --git a/app/ui-message/client/messageBox/messageBox.js b/app/ui-message/client/messageBox/messageBox.js index 8d67e3466bdc3..d8cf0424deff1 100644 --- a/app/ui-message/client/messageBox/messageBox.js +++ b/app/ui-message/client/messageBox/messageBox.js @@ -477,6 +477,7 @@ Template.messageBox.events({ data: { rid: this.rid, tmid: this.tmid, + prid: this.subscription.prid, messageBox: instance.firstNode, }, activeElement: event.currentTarget, @@ -494,6 +495,7 @@ Template.messageBox.events({ rid: this.rid, tmid: this.tmid, messageBox: instance.firstNode, + prid: this.subscription.prid, event, }); }); From 7bcaaace258a35441a77631337c89f5387ac6485 Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Wed, 17 Nov 2021 14:06:02 -0300 Subject: [PATCH 082/137] [FIX] Missing user roles in edit user tab (#23734) --- client/views/admin/users/EditUser.js | 2 +- client/views/admin/users/UserInfo.js | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/client/views/admin/users/EditUser.js b/client/views/admin/users/EditUser.js index bb9195a1236fc..978c9dcabd95e 100644 --- a/client/views/admin/users/EditUser.js +++ b/client/views/admin/users/EditUser.js @@ -11,7 +11,7 @@ import { useForm } from '../../../hooks/useForm'; import UserForm from './UserForm'; const getInitialValue = (data) => ({ - roles: data.rolesId, + roles: data.roles, name: data.name ?? '', password: '', username: data.username, diff --git a/client/views/admin/users/UserInfo.js b/client/views/admin/users/UserInfo.js index 81cead31e9ca9..01ee1762c4b51 100644 --- a/client/views/admin/users/UserInfo.js +++ b/client/views/admin/users/UserInfo.js @@ -6,6 +6,7 @@ import { getUserEmailAddress } from '../../../../lib/getUserEmailAddress'; import { FormSkeleton } from '../../../components/Skeleton'; import UserCard from '../../../components/UserCard'; import { UserStatus } from '../../../components/UserStatus'; +import { useRolesDescription } from '../../../contexts/AuthorizationContext'; import { useSetting } from '../../../contexts/SettingsContext'; import { useTranslation } from '../../../contexts/TranslationContext'; import { AsyncStatePhase } from '../../../hooks/useAsyncState'; @@ -17,6 +18,7 @@ import { UserInfoActions } from './UserInfoActions'; export function UserInfoWithData({ uid, username, onReload, ...props }) { const t = useTranslation(); const showRealNames = useSetting('UI_Use_Real_Name'); + const getRoles = useRolesDescription(); const approveManuallyUsers = useSetting('Accounts_ManuallyApproveNewUsers'); const { @@ -57,7 +59,9 @@ export function UserInfoWithData({ uid, username, onReload, ...props }) { username, lastLogin, showRealNames, - roles: roles.map((role, index) => {role}), + roles: + roles && + getRoles(roles).map((role, index) => {role}), bio, phone: user.phone, utcOffset, @@ -74,7 +78,7 @@ export function UserInfoWithData({ uid, username, onReload, ...props }) { customStatus: statusText, nickname, }; - }, [approveManuallyUsers, data, showRealNames]); + }, [approveManuallyUsers, data, showRealNames, getRoles]); if (state === AsyncStatePhase.LOADING) { return ( From c54ef380d29f99bfde38c31f8dccbf841ffef455 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Wed, 17 Nov 2021 17:07:37 -0300 Subject: [PATCH 083/137] [FIX][ENTERPRISE] Private rooms and discussions can't be audited (#23673) * Add support to audit discussions and private rooms * Change tab name to Rooms Co-authored-by: gabriellsh --- app/api/server/lib/rooms.js | 27 ++++++++- app/api/server/v1/rooms.js | 16 ++++- app/models/server/raw/Rooms.js | 17 ++++++ ee/client/audit/AuditPageBase.js | 2 +- .../RoomAutoComplete/RoomAutoComplete.js | 2 +- tests/end-to-end/api/09-rooms.js | 60 +++++++++++++++++++ 6 files changed, 120 insertions(+), 4 deletions(-) diff --git a/app/api/server/lib/rooms.js b/app/api/server/lib/rooms.js index ea184b1d2fa20..a841973e02dbc 100644 --- a/app/api/server/lib/rooms.js +++ b/app/api/server/lib/rooms.js @@ -1,4 +1,4 @@ -import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; +import { hasPermissionAsync, hasAtLeastOnePermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { Rooms } from '../../../models/server/raw'; import { Subscriptions } from '../../../models/server'; @@ -119,6 +119,31 @@ export async function findChannelAndPrivateAutocomplete({ uid, selector }) { }; } +export async function findAdminRoomsAutocomplete({ uid, selector }) { + if (!await hasAtLeastOnePermissionAsync(uid, ['view-room-administration', 'can-audit'])) { + throw new Error('error-not-authorized'); + } + const options = { + fields: { + _id: 1, + fname: 1, + name: 1, + t: 1, + avatarETag: 1, + }, + limit: 10, + sort: { + name: 1, + }, + }; + + const rooms = await Rooms.findRoomsByNameOrFnameStarting(selector.name, options).toArray(); + + return { + items: rooms, + }; +} + export async function findChannelAndPrivateAutocompleteWithPagination({ uid, selector, pagination: { offset, count, sort } }) { const userRoomsIds = Subscriptions.cachedFindByUserId(uid, { fields: { rid: 1 } }) .fetch() diff --git a/app/api/server/v1/rooms.js b/app/api/server/v1/rooms.js index 9310ac8c7b224..df793f68226be 100644 --- a/app/api/server/v1/rooms.js +++ b/app/api/server/v1/rooms.js @@ -3,7 +3,7 @@ import { Meteor } from 'meteor/meteor'; import { FileUpload } from '../../../file-upload'; import { Rooms, Messages } from '../../../models'; import { API } from '../api'; -import { findAdminRooms, findChannelAndPrivateAutocomplete, findAdminRoom, findRoomsAvailableForTeams, findChannelAndPrivateAutocompleteWithPagination } from '../lib/rooms'; +import { findAdminRooms, findChannelAndPrivateAutocomplete, findAdminRoom, findAdminRoomsAutocomplete, findRoomsAvailableForTeams, findChannelAndPrivateAutocompleteWithPagination } from '../lib/rooms'; import { sendFile, sendViaEmail } from '../../../../server/lib/channelExport'; import { canAccessRoom, hasPermission } from '../../../authorization/server'; import { Media } from '../../../../server/sdk'; @@ -286,6 +286,20 @@ API.v1.addRoute('rooms.adminRooms', { authRequired: true }, { }, }); +API.v1.addRoute('rooms.autocomplete.adminRooms', { authRequired: true }, { + get() { + const { selector } = this.queryParams; + if (!selector) { + return API.v1.failure('The \'selector\' param is required'); + } + + return API.v1.success(Promise.await(findAdminRoomsAutocomplete({ + uid: this.userId, + selector: JSON.parse(selector), + }))); + }, +}); + API.v1.addRoute('rooms.adminRooms.getRoom', { authRequired: true }, { get() { const { rid } = this.requestParams(); diff --git a/app/models/server/raw/Rooms.js b/app/models/server/raw/Rooms.js index 4be1c8a913ceb..6dfc2212d8ed8 100644 --- a/app/models/server/raw/Rooms.js +++ b/app/models/server/raw/Rooms.js @@ -181,6 +181,23 @@ export class RoomsRaw extends BaseRaw { return this.find(query, options); } + findRoomsByNameOrFnameStarting(name, options) { + const nameRegex = new RegExp(`^${ escapeRegExp(name).trim() }`, 'i'); + + const query = { + t: { + $in: ['c', 'p'], + }, + $or: [{ + name: nameRegex, + }, { + fname: nameRegex, + }], + }; + + return this.find(query, options); + } + findRoomsWithoutDiscussionsByRoomIds(name, roomIds, options) { const nameRegex = new RegExp(`^${ escapeRegExp(name).trim() }`, 'i'); diff --git a/ee/client/audit/AuditPageBase.js b/ee/client/audit/AuditPageBase.js index 1cb930867bac5..56e81915a1405 100644 --- a/ee/client/audit/AuditPageBase.js +++ b/ee/client/audit/AuditPageBase.js @@ -56,7 +56,7 @@ export const AuditPageBase = ({ - {t('Channels')} + {t('Rooms')} {t('Users')} diff --git a/ee/client/audit/RoomAutoComplete/RoomAutoComplete.js b/ee/client/audit/RoomAutoComplete/RoomAutoComplete.js index 1582d020f9739..ec3e023da219d 100644 --- a/ee/client/audit/RoomAutoComplete/RoomAutoComplete.js +++ b/ee/client/audit/RoomAutoComplete/RoomAutoComplete.js @@ -9,7 +9,7 @@ const query = (name = '') => ({ selector: JSON.stringify({ name }) }); const RoomAutoComplete = (props) => { const [filter, setFilter] = useState(''); const { value: data } = useEndpointData( - 'rooms.autocomplete.channelAndPrivate', + 'rooms.autocomplete.adminRooms', useMemo(() => query(filter), [filter]), ); const options = useMemo( diff --git a/tests/end-to-end/api/09-rooms.js b/tests/end-to-end/api/09-rooms.js index 4153cfc26ca33..ca9fe2735327e 100644 --- a/tests/end-to-end/api/09-rooms.js +++ b/tests/end-to-end/api/09-rooms.js @@ -923,6 +923,66 @@ describe('[Rooms]', function() { .end(done); }); }); + describe('[/rooms.autocomplete.adminRooms]', () => { + let testGroup; + const testGroupName = `channel.test.adminRoom${ Date.now() }-${ Math.random() }`; + const name = { + name: testGroupName, + }; + before((done) => { + createRoom({ type: 'p', name: testGroupName }) + .end((err, res) => { + testGroup = res.body.group; + request.post(api('rooms.createDiscussion')) + .set(credentials) + .send({ + prid: testGroup._id, + t_name: `${ testGroupName }-discussion`, + }) + .end(done); + }); + }); + it('should return an error when the required parameter "selector" is not provided', (done) => { + updatePermission('can-audit', ['admin']).then(() => { + request.get(api('rooms.autocomplete.adminRooms')) + .set(credentials) + .query({}) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body.error).to.be.equal('The \'selector\' param is required'); + }) + .end(done); + }); + }); + it('should return the rooms to fill auto complete', (done) => { + request.get(api('rooms.autocomplete.adminRooms?selector={}')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('items').and.to.be.an('array'); + }) + .end(done); + }); + it('should return FIX the rooms to fill auto complete', (done) => { + request.get(api('rooms.autocomplete.adminRooms?')) + .set(credentials) + .query({ + selector: JSON.stringify(name), + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('items').and.to.be.an('array'); + expect(res.body).to.have.property('items').that.have.lengthOf(2); + }) + .end(done); + }); + }); describe('/rooms.adminRooms', () => { it('should throw an error when the user tries to gets a list of discussion and he cannot access the room', (done) => { updatePermission('view-room-administration', []).then(() => { From bc91e46414c1a256931b8c83878c9be44c871483 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Thu, 18 Nov 2021 12:10:06 -0600 Subject: [PATCH 084/137] Await promise to handle error when attempting to transfer a room (#23739) --- app/livechat/server/lib/Livechat.js | 2 +- app/livechat/server/lib/stream/agentStatus.ts | 20 ++++++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/app/livechat/server/lib/Livechat.js b/app/livechat/server/lib/Livechat.js index aa399564bfa5f..6ccd549152874 100644 --- a/app/livechat/server/lib/Livechat.js +++ b/app/livechat/server/lib/Livechat.js @@ -598,7 +598,7 @@ export const Livechat = { const user = Users.findOneById(userId); const { _id, username, name } = user; const transferredBy = normalizeTransferredByData({ _id, username, name }, room); - this.transfer(room, guest, { roomId: room._id, transferredBy, departmentId: guest.department }); + Promise.await(this.transfer(room, guest, { roomId: room._id, transferredBy, departmentId: guest.department })); }); }, diff --git a/app/livechat/server/lib/stream/agentStatus.ts b/app/livechat/server/lib/stream/agentStatus.ts index ed46bacbd0f89..12985a42f58d9 100644 --- a/app/livechat/server/lib/stream/agentStatus.ts +++ b/app/livechat/server/lib/stream/agentStatus.ts @@ -2,6 +2,9 @@ import { Meteor } from 'meteor/meteor'; import { Livechat } from '../Livechat'; import { settings } from '../../../../settings/server'; +import { Logger } from '../../../../logger/server'; + +const logger = new Logger('AgentStatusWatcher'); export let monitorAgents = false; let actionTimeout = 60000; @@ -64,12 +67,19 @@ export const onlineAgents = { onlineAgents.users.delete(userId); onlineAgents.queue.delete(userId); - if (action === 'close') { - return Livechat.closeOpenChats(userId, comment); - } + try { + if (action === 'close') { + return Livechat.closeOpenChats(userId, comment); + } - if (action === 'forward') { - return Livechat.forwardOpenChats(userId); + if (action === 'forward') { + return Livechat.forwardOpenChats(userId); + } + } catch (e) { + logger.error({ + msg: `Cannot perform action ${ action }`, + err: e, + }); } }), }; From c2b198c95b4724616c7501daeecda4853e5e1b44 Mon Sep 17 00:00:00 2001 From: siva <63258524+siva2204@users.noreply.github.com> Date: Fri, 19 Nov 2021 02:53:50 +0530 Subject: [PATCH 085/137] [FIX] User logout after reset E2E key #21314 --- client/views/account/security/EndToEnd.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/client/views/account/security/EndToEnd.js b/client/views/account/security/EndToEnd.js index 740ad54c5828b..e7d1121dd7cd6 100644 --- a/client/views/account/security/EndToEnd.js +++ b/client/views/account/security/EndToEnd.js @@ -1,16 +1,22 @@ import { Box, Margins, PasswordInput, Field, FieldGroup, Button } from '@rocket.chat/fuselage'; -import { useLocalStorage } from '@rocket.chat/fuselage-hooks'; +import { useLocalStorage, useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { Meteor } from 'meteor/meteor'; import React, { useCallback, useEffect } from 'react'; +import { callbacks } from '../../../../app/callbacks/lib/callbacks'; import { e2e } from '../../../../app/e2e/client/rocketchat.e2e'; +import { useRoute } from '../../../contexts/RouterContext'; import { useMethod } from '../../../contexts/ServerContext'; import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext'; import { useTranslation } from '../../../contexts/TranslationContext'; +import { useUser } from '../../../contexts/UserContext'; import { useForm } from '../../../hooks/useForm'; const EndToEnd = (props) => { const t = useTranslation(); const dispatchToastMessage = useToastMessageDispatch(); + const homeRoute = useRoute('home'); + const user = useUser(); const publicKey = useLocalStorage('public_key'); const privateKey = useLocalStorage('private_key'); @@ -30,6 +36,14 @@ const EndToEnd = (props) => { : undefined; const canSave = keysExist && !passwordError && passwordConfirm.length > 0; + const handleLogout = useMutableCallback(() => { + Meteor.logout(() => { + callbacks.run('afterLogoutCleanUp', user); + Meteor.call('logoutCleanUp', user); + homeRoute.push({}); + }); + }); + const saveNewPassword = useCallback(async () => { try { await e2e.changePassword(password); @@ -45,11 +59,12 @@ const EndToEnd = (props) => { const result = await resetE2eKey(); if (result) { dispatchToastMessage({ type: 'success', message: t('User_e2e_key_was_reset') }); + handleLogout(); } } catch (error) { dispatchToastMessage({ type: 'error', message: error }); } - }, [dispatchToastMessage, resetE2eKey, t]); + }, [dispatchToastMessage, resetE2eKey, handleLogout, t]); useEffect(() => { if (password.trim() === '') { From cea0e40180d0c041096b95d2519880c49454521f Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Thu, 18 Nov 2021 18:44:03 -0300 Subject: [PATCH 086/137] Chore: Generic Table (#23745) --- client/components/FilterByText.tsx | 21 +-- .../components/GenericTable/GenericTable.tsx | 95 ++++++-------- .../GenericTable/V2/GenericTable.tsx | 24 ++++ .../GenericTable/V2/GenericTableBody.tsx | 4 + .../GenericTable/V2/GenericTableCell.tsx | 6 + .../GenericTable/V2/GenericTableHeader.tsx | 10 ++ .../V2/GenericTableHeaderCell.tsx | 30 +++++ .../V2/GenericTableLoadingRow.tsx | 25 ++++ .../V2/GenericTableLoadingTable.tsx | 15 +++ .../GenericTable/V2/GenericTableRow.tsx | 6 + .../GenericTable/hooks/useCurrent.ts | 9 ++ .../GenericTable/hooks/useItemsPerPage.ts | 11 ++ .../hooks/useItemsPerPageLabel.ts | 8 ++ .../GenericTable/hooks/usePagination.ts | 30 +++++ .../hooks/useShowingResultsLabel.ts | 19 +++ .../components/GenericTable/hooks/useSort.ts | 34 +++++ client/components/GenericTable/index.ts | 9 ++ client/views/admin/customEmoji/CustomEmoji.js | 65 ---------- .../views/admin/customEmoji/CustomEmoji.tsx | 121 ++++++++++++++++++ .../admin/customEmoji/CustomEmojiRoute.js | 45 +------ definition/rest/v1/emojiCustom.ts | 11 ++ 21 files changed, 427 insertions(+), 171 deletions(-) create mode 100644 client/components/GenericTable/V2/GenericTable.tsx create mode 100644 client/components/GenericTable/V2/GenericTableBody.tsx create mode 100644 client/components/GenericTable/V2/GenericTableCell.tsx create mode 100644 client/components/GenericTable/V2/GenericTableHeader.tsx create mode 100644 client/components/GenericTable/V2/GenericTableHeaderCell.tsx create mode 100644 client/components/GenericTable/V2/GenericTableLoadingRow.tsx create mode 100644 client/components/GenericTable/V2/GenericTableLoadingTable.tsx create mode 100644 client/components/GenericTable/V2/GenericTableRow.tsx create mode 100644 client/components/GenericTable/hooks/useCurrent.ts create mode 100644 client/components/GenericTable/hooks/useItemsPerPage.ts create mode 100644 client/components/GenericTable/hooks/useItemsPerPageLabel.ts create mode 100644 client/components/GenericTable/hooks/usePagination.ts create mode 100644 client/components/GenericTable/hooks/useShowingResultsLabel.ts create mode 100644 client/components/GenericTable/hooks/useSort.ts delete mode 100644 client/views/admin/customEmoji/CustomEmoji.js create mode 100644 client/views/admin/customEmoji/CustomEmoji.tsx diff --git a/client/components/FilterByText.tsx b/client/components/FilterByText.tsx index 0c06774c6e6dc..170c0c1944adb 100644 --- a/client/components/FilterByText.tsx +++ b/client/components/FilterByText.tsx @@ -6,19 +6,22 @@ import { useTranslation } from '../contexts/TranslationContext'; type FilterByTextProps = { placeholder?: string; onChange: (filter: { text: string }) => void; - displayButton: boolean; + inputRef?: () => void; +}; + +type FilterByTextPropsWithButton = FilterByTextProps & { + displayButton: true; textButton: string; onButtonClick: () => void; - inputRef: () => void; }; +const isFilterByTextPropsWithButton = (props: any): props is FilterByTextPropsWithButton => + 'displayButton' in props && props.displayButton === true; const FilterByText: FC = ({ placeholder, onChange: setFilter, - displayButton: display = false, - textButton = '', - onButtonClick, inputRef, + children: _, ...props }) => { const t = useTranslation(); @@ -53,9 +56,11 @@ const FilterByText: FC = ({ onChange={handleInputChange} value={text} /> - + {isFilterByTextPropsWithButton(props) && ( + + )}
    ); }; diff --git a/client/components/GenericTable/GenericTable.tsx b/client/components/GenericTable/GenericTable.tsx index 99124d8186fd5..c6e5317d24ed4 100644 --- a/client/components/GenericTable/GenericTable.tsx +++ b/client/components/GenericTable/GenericTable.tsx @@ -1,20 +1,23 @@ -import { Box, Pagination, Table, Tile } from '@rocket.chat/fuselage'; +import { Pagination, Tile } from '@rocket.chat/fuselage'; import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; import React, { useState, useEffect, - useCallback, forwardRef, ReactNode, ReactElement, Key, - RefAttributes, + useMemo, + Ref, } from 'react'; import flattenChildren from 'react-keyed-flatten-children'; import { useTranslation } from '../../contexts/TranslationContext'; -import ScrollableContentWrapper from '../ScrollableContentWrapper'; -import LoadingRow from './LoadingRow'; +import { GenericTable as GenericTableV2 } from './V2/GenericTable'; +import { GenericTableBody } from './V2/GenericTableBody'; +import { GenericTableHeader } from './V2/GenericTableHeader'; +import { GenericTableLoadingTable } from './V2/GenericTableLoadingTable'; +import { usePagination } from './hooks/usePagination'; const defaultParamsValue = { text: '', current: 0, itemsPerPage: 25 } as const; const defaultSetParamsValue = (): void => undefined; @@ -37,14 +40,10 @@ type GenericTableProps< pagination?: boolean; } & FilterProps; -const GenericTable: { - < - FilterProps extends { onChange?: (params: GenericTableParams) => void }, - ResultProps extends { _id?: Key }, - >( - props: GenericTableProps & RefAttributes, - ): ReactElement | null; -} = forwardRef(function GenericTable( +const GenericTable = forwardRef(function GenericTable< + FilterProps extends { onChange?: (params: GenericTableParams) => void }, + ResultProps extends { _id?: Key }, +>( { children, fixed = true, @@ -57,41 +56,31 @@ const GenericTable: { total, pagination = true, ...props - }, - ref, + }: GenericTableProps, + ref: Ref, ) { const t = useTranslation(); const [filter, setFilter] = useState(paramsDefault); - const [itemsPerPage, setItemsPerPage] = useState<25 | 50 | 100>(25); - - const [current, setCurrent] = useState(0); + const { + itemsPerPage, + setItemsPerPage, + current, + setCurrent, + itemsPerPageLabel, + showingResultsLabel, + } = usePagination(); const params = useDebouncedValue(filter, 500); useEffect(() => { - setParams({ ...params, current, itemsPerPage }); + setParams({ text: params.text || '', current, itemsPerPage }); }, [params, current, itemsPerPage, setParams]); - const Loading = useCallback(() => { - const headerCells = flattenChildren(header); - return ( - <> - {Array.from({ length: 10 }, (_, i) => ( - - ))} - - ); - }, [header]); - - const showingResultsLabel = useCallback( - ({ count, current, itemsPerPage }) => - t('Showing_results_of', current + 1, Math.min(current + itemsPerPage, count), count), - [t], - ); + const headerCells = useMemo(() => flattenChildren(header).length, [header]); - const itemsPerPageLabel = useCallback(() => t('Items_per_page:'), [t]); + const isLoading = !results; return ( <> @@ -104,28 +93,18 @@ const GenericTable: { ) : ( <> - - - - {header && ( - - {header} - - )} - - {RenderRow && - (results ? ( - results.map((props, index) => ( - - )) - ) : ( - - ))} - {children && (results ? results.map(children) : )} - -
    -
    -
    + + {header && {header}} + + {isLoading && } + {!isLoading && + ((RenderRow && + results?.map((props, index: number) => ( + + ))) || + (children && results?.map(children)))} + + {pagination && ( (function GenericTable( + { fixed = true, children }, + ref, +) { + return ( + + + + {children} +
    +
    +
    + ); +}); diff --git a/client/components/GenericTable/V2/GenericTableBody.tsx b/client/components/GenericTable/V2/GenericTableBody.tsx new file mode 100644 index 0000000000000..945688f9efafe --- /dev/null +++ b/client/components/GenericTable/V2/GenericTableBody.tsx @@ -0,0 +1,4 @@ +import { Table } from '@rocket.chat/fuselage'; +import React, { FC } from 'react'; + +export const GenericTableBody: FC = (props) => ; diff --git a/client/components/GenericTable/V2/GenericTableCell.tsx b/client/components/GenericTable/V2/GenericTableCell.tsx new file mode 100644 index 0000000000000..883de10b3f3ad --- /dev/null +++ b/client/components/GenericTable/V2/GenericTableCell.tsx @@ -0,0 +1,6 @@ +import { Table } from '@rocket.chat/fuselage'; +import React, { ComponentProps, FC } from 'react'; + +export const GenericTableCell: FC> = (props) => ( + +); diff --git a/client/components/GenericTable/V2/GenericTableHeader.tsx b/client/components/GenericTable/V2/GenericTableHeader.tsx new file mode 100644 index 0000000000000..90f3a340f5a1e --- /dev/null +++ b/client/components/GenericTable/V2/GenericTableHeader.tsx @@ -0,0 +1,10 @@ +import { Table } from '@rocket.chat/fuselage'; +import React, { FC } from 'react'; + +import { GenericTableRow } from './GenericTableRow'; + +export const GenericTableHeader: FC = ({ children, ...props }) => ( + + {children} + +); diff --git a/client/components/GenericTable/V2/GenericTableHeaderCell.tsx b/client/components/GenericTable/V2/GenericTableHeaderCell.tsx new file mode 100644 index 0000000000000..a4db4fbb31600 --- /dev/null +++ b/client/components/GenericTable/V2/GenericTableHeaderCell.tsx @@ -0,0 +1,30 @@ +import { Box, Table } from '@rocket.chat/fuselage'; +import React, { ComponentProps, ReactElement, useCallback } from 'react'; + +import SortIcon from '../SortIcon'; + +type GenericTableHeaderCellProps = Omit, 'onClick'> & { + active?: boolean; + direction?: 'asc' | 'desc'; + sort?: T; + onClick?: (sort: T) => void; +}; + +export const GenericTableHeaderCell = ({ + children, + active, + direction, + sort, + onClick, + ...props +}: GenericTableHeaderCellProps): ReactElement => { + const fn = useCallback(() => onClick && sort && onClick(sort), [sort, onClick]); + return ( + + + {children} + {sort && } + + + ); +}; diff --git a/client/components/GenericTable/V2/GenericTableLoadingRow.tsx b/client/components/GenericTable/V2/GenericTableLoadingRow.tsx new file mode 100644 index 0000000000000..ecc34e2e5e7f3 --- /dev/null +++ b/client/components/GenericTable/V2/GenericTableLoadingRow.tsx @@ -0,0 +1,25 @@ +import { Box, Skeleton, Table } from '@rocket.chat/fuselage'; +import React, { ReactElement } from 'react'; + +type GenericTableLoadingRowRowProps = { + cols: number; +}; + +export const GenericTableLoadingRow = ({ cols }: GenericTableLoadingRowRowProps): ReactElement => ( + + + + + + + + + + + {Array.from({ length: cols - 1 }, (_, i) => ( + + + + ))} + +); diff --git a/client/components/GenericTable/V2/GenericTableLoadingTable.tsx b/client/components/GenericTable/V2/GenericTableLoadingTable.tsx new file mode 100644 index 0000000000000..e7f2501bc7f4d --- /dev/null +++ b/client/components/GenericTable/V2/GenericTableLoadingTable.tsx @@ -0,0 +1,15 @@ +import React, { ReactElement } from 'react'; + +import { GenericTableLoadingRow } from './GenericTableLoadingRow'; + +export const GenericTableLoadingTable = ({ + headerCells, +}: { + headerCells: number; +}): ReactElement => ( + <> + {Array.from({ length: 10 }, (_, i) => ( + + ))} + +); diff --git a/client/components/GenericTable/V2/GenericTableRow.tsx b/client/components/GenericTable/V2/GenericTableRow.tsx new file mode 100644 index 0000000000000..cd31eb47d65c9 --- /dev/null +++ b/client/components/GenericTable/V2/GenericTableRow.tsx @@ -0,0 +1,6 @@ +import { Table } from '@rocket.chat/fuselage'; +import React, { ComponentProps, FC } from 'react'; + +export const GenericTableRow: FC> = (props) => ( + +); diff --git a/client/components/GenericTable/hooks/useCurrent.ts b/client/components/GenericTable/hooks/useCurrent.ts new file mode 100644 index 0000000000000..fc70a9f252d20 --- /dev/null +++ b/client/components/GenericTable/hooks/useCurrent.ts @@ -0,0 +1,9 @@ +import { useState } from 'react'; + +export const useCurrent = ( + currentInitialValue = 0, +): [number, React.Dispatch>] => { + const [current, setCurrent] = useState(currentInitialValue); + + return [current, setCurrent]; +}; diff --git a/client/components/GenericTable/hooks/useItemsPerPage.ts b/client/components/GenericTable/hooks/useItemsPerPage.ts new file mode 100644 index 0000000000000..3d9c36807a61e --- /dev/null +++ b/client/components/GenericTable/hooks/useItemsPerPage.ts @@ -0,0 +1,11 @@ +import { useState } from 'react'; + +type UseItemsPerPageValue = 25 | 50 | 100; + +export const useItemsPerPage = ( + itemsPerPageInitialValue: UseItemsPerPageValue = 25, +): [UseItemsPerPageValue, React.Dispatch>] => { + const [itemsPerPage, setItemsPerPage] = useState(itemsPerPageInitialValue); + + return [itemsPerPage, setItemsPerPage]; +}; diff --git a/client/components/GenericTable/hooks/useItemsPerPageLabel.ts b/client/components/GenericTable/hooks/useItemsPerPageLabel.ts new file mode 100644 index 0000000000000..79e65d88f3702 --- /dev/null +++ b/client/components/GenericTable/hooks/useItemsPerPageLabel.ts @@ -0,0 +1,8 @@ +import { useCallback } from 'react'; + +import { useTranslation } from '../../../contexts/TranslationContext'; + +export const useItemsPerPageLabel = (): (() => string) => { + const t = useTranslation(); + return useCallback(() => t('Items_per_page:'), [t]); +}; diff --git a/client/components/GenericTable/hooks/usePagination.ts b/client/components/GenericTable/hooks/usePagination.ts new file mode 100644 index 0000000000000..3f0558f4ac995 --- /dev/null +++ b/client/components/GenericTable/hooks/usePagination.ts @@ -0,0 +1,30 @@ +import { useCurrent } from './useCurrent'; +import { useItemsPerPage } from './useItemsPerPage'; +import { useItemsPerPageLabel } from './useItemsPerPageLabel'; +import { useShowingResultsLabel } from './useShowingResultsLabel'; + +export const usePagination = (): { + current: ReturnType[0]; + setCurrent: ReturnType[1]; + itemsPerPage: ReturnType[0]; + setItemsPerPage: ReturnType[1]; + itemsPerPageLabel: ReturnType; + showingResultsLabel: ReturnType; +} => { + const [itemsPerPage, setItemsPerPage] = useItemsPerPage(); + + const [current, setCurrent] = useCurrent(); + + const itemsPerPageLabel = useItemsPerPageLabel(); + + const showingResultsLabel = useShowingResultsLabel(); + + return { + itemsPerPage, + setItemsPerPage, + current, + setCurrent, + itemsPerPageLabel, + showingResultsLabel, + }; +}; diff --git a/client/components/GenericTable/hooks/useShowingResultsLabel.ts b/client/components/GenericTable/hooks/useShowingResultsLabel.ts new file mode 100644 index 0000000000000..b7a54ac084f90 --- /dev/null +++ b/client/components/GenericTable/hooks/useShowingResultsLabel.ts @@ -0,0 +1,19 @@ +import { Pagination } from '@rocket.chat/fuselage'; +import { ComponentProps, useCallback } from 'react'; + +import { useTranslation } from '../../../contexts/TranslationContext'; + +type Props< + T extends ComponentProps['showingResultsLabel'] = ComponentProps< + typeof Pagination + >['showingResultsLabel'], +> = T extends (...args: any[]) => any ? Parameters : never; + +export const useShowingResultsLabel = (): ((...params: Props) => string) => { + const t = useTranslation(); + return useCallback( + ({ count, current, itemsPerPage }) => + t('Showing_results_of', current + 1, Math.min(current + itemsPerPage, count), count), + [t], + ); +}; diff --git a/client/components/GenericTable/hooks/useSort.ts b/client/components/GenericTable/hooks/useSort.ts new file mode 100644 index 0000000000000..6858404208fcd --- /dev/null +++ b/client/components/GenericTable/hooks/useSort.ts @@ -0,0 +1,34 @@ +import { useCallback, useState } from 'react'; + +type Direction = 'asc' | 'desc'; + +export const useSort = ( + by: T, + initialDirection: Direction = 'asc', +): { + sortBy: T; + sortDirection: Direction; + setSort: (sortBy: T, direction?: Direction | undefined) => void; +} => { + const [sort, _setSort] = useState<[T, Direction]>(() => [by, initialDirection]); + + const setSort = useCallback((id: T, direction?: Direction | undefined) => { + _setSort(([sortBy, sortDirection]) => { + if (direction) { + return [id, direction]; + } + + if (sortBy === id) { + return [id, sortDirection === 'asc' ? 'desc' : 'asc']; + } + + return [id, 'asc']; + }); + }, []); + + return { + sortBy: sort[0], + sortDirection: sort[1], + setSort, + }; +}; diff --git a/client/components/GenericTable/index.ts b/client/components/GenericTable/index.ts index 8da6df3fc14e1..3a51504430245 100644 --- a/client/components/GenericTable/index.ts +++ b/client/components/GenericTable/index.ts @@ -4,3 +4,12 @@ import HeaderCell from './HeaderCell'; export default Object.assign(GenericTable, { HeaderCell, }); + +export * from './V2/GenericTable'; +export * from './V2/GenericTableBody'; +export * from './V2/GenericTableCell'; +export * from './V2/GenericTableHeader'; +export * from './V2/GenericTableHeaderCell'; +export * from './V2/GenericTableLoadingRow'; +export * from './V2/GenericTableLoadingTable'; +export * from './V2/GenericTableRow'; diff --git a/client/views/admin/customEmoji/CustomEmoji.js b/client/views/admin/customEmoji/CustomEmoji.js deleted file mode 100644 index b5474ab6d3ef1..0000000000000 --- a/client/views/admin/customEmoji/CustomEmoji.js +++ /dev/null @@ -1,65 +0,0 @@ -import { Box, Table } from '@rocket.chat/fuselage'; -import React, { useMemo } from 'react'; - -import FilterByText from '../../../components/FilterByText'; -import GenericTable from '../../../components/GenericTable'; -import { useTranslation } from '../../../contexts/TranslationContext'; - -function CustomEmoji({ data, sort, onClick, onHeaderClick, setParams, params }) { - const t = useTranslation(); - - const header = useMemo( - () => [ - - {t('Name')} - , - - {t('Aliases')} - , - ], - [onHeaderClick, sort, t], - ); - - const renderRow = (emojis) => { - const { _id, name, aliases } = emojis; - return ( - - - {name} - - - {aliases} - - - ); - }; - - return ( - } - /> - ); -} - -export default CustomEmoji; diff --git a/client/views/admin/customEmoji/CustomEmoji.tsx b/client/views/admin/customEmoji/CustomEmoji.tsx new file mode 100644 index 0000000000000..28db8f4b4cec9 --- /dev/null +++ b/client/views/admin/customEmoji/CustomEmoji.tsx @@ -0,0 +1,121 @@ +import { Box, Pagination } from '@rocket.chat/fuselage'; +import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; +import React, { FC, MutableRefObject, useEffect, useMemo, useState } from 'react'; + +import FilterByText from '../../../components/FilterByText'; +import { + GenericTable, + GenericTableBody, + GenericTableCell, + GenericTableHeader, + GenericTableHeaderCell, + GenericTableLoadingTable, + GenericTableRow, +} from '../../../components/GenericTable'; +import { usePagination } from '../../../components/GenericTable/hooks/usePagination'; +import { useSort } from '../../../components/GenericTable/hooks/useSort'; +import { useTranslation } from '../../../contexts/TranslationContext'; +import { useEndpointData } from '../../../hooks/useEndpointData'; +import { AsyncStatePhase } from '../../../lib/asyncState'; + +type CustomEmojiProps = { + reload: MutableRefObject<() => void>; + onClick: (emoji: string) => () => void; +}; + +const CustomEmoji: FC = function CustomEmoji({ onClick, reload }) { + const t = useTranslation(); + + const { + current, + itemsPerPage, + setItemsPerPage: onSetItemsPerPage, + setCurrent: onSetCurrent, + ...paginationProps + } = usePagination(); + + const [text, setText] = useState(''); + + const { sortBy, sortDirection, setSort } = useSort<'name'>('name'); + + const query = useDebouncedValue( + useMemo( + () => ({ + query: JSON.stringify({ name: { $regex: text || '', $options: 'i' } }), + sort: JSON.stringify({ [sortBy]: sortDirection === 'asc' ? 1 : -1 }), + count: itemsPerPage, + offset: current, + }), + [text, itemsPerPage, current, sortBy, sortDirection], + ), + 500, + ); + + const { value: data, phase, reload: reloadEndPoint } = useEndpointData('emoji-custom.all', query); + + useEffect(() => { + reload.current = reloadEndPoint; + }, [reload, reloadEndPoint]); + return ( + <> + setText(text)} /> + + + + {t('Name')} + + + {t('Aliases')} + + + + {phase === AsyncStatePhase.LOADING && } + {phase === AsyncStatePhase.RESOLVED && + data && + data.emojis && + data.emojis.length > 0 && + data?.emojis.map((emojis) => ( + + + {emojis.name} + + + {emojis.aliases} + + + ))} + {/* {phase === AsyncStatePhase.RESOLVED && + !data.emojis.length + ))} */} + + + {phase === AsyncStatePhase.RESOLVED && ( + + )} + + ); +}; + +export default CustomEmoji; diff --git a/client/views/admin/customEmoji/CustomEmojiRoute.js b/client/views/admin/customEmoji/CustomEmojiRoute.js index d1430d21062a5..36cb32d84a62e 100644 --- a/client/views/admin/customEmoji/CustomEmojiRoute.js +++ b/client/views/admin/customEmoji/CustomEmojiRoute.js @@ -1,6 +1,5 @@ import { Button, Icon } from '@rocket.chat/fuselage'; -import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; -import React, { useMemo, useState, useCallback } from 'react'; +import React, { useCallback, useRef } from 'react'; import NotAuthorizedPage from '../../../components/NotAuthorizedPage'; import Page from '../../../components/Page'; @@ -8,7 +7,6 @@ import VerticalBar from '../../../components/VerticalBar'; import { usePermission } from '../../../contexts/AuthorizationContext'; import { useRoute, useRouteParameter } from '../../../contexts/RouterContext'; import { useTranslation } from '../../../contexts/TranslationContext'; -import { useEndpointData } from '../../../hooks/useEndpointData'; import AddCustomEmoji from './AddCustomEmoji'; import CustomEmoji from './CustomEmoji'; import EditCustomEmojiWithData from './EditCustomEmojiWithData'; @@ -20,24 +18,6 @@ function CustomEmojiRoute() { const canManageEmoji = usePermission('manage-emoji'); const t = useTranslation(); - - const [params, setParams] = useState(() => ({ text: '', current: 0, itemsPerPage: 25 })); - const [sort, setSort] = useState(() => ['name', 'asc']); - - const { text, itemsPerPage, current } = useDebouncedValue(params, 500); - const [column, direction] = useDebouncedValue(sort, 500); - const query = useMemo( - () => ({ - query: JSON.stringify({ name: { $regex: text || '', $options: 'i' } }), - sort: JSON.stringify({ [column]: direction === 'asc' ? 1 : -1 }), - ...(itemsPerPage && { count: itemsPerPage }), - ...(current && { offset: current }), - }), - [text, itemsPerPage, current, column, direction], - ); - - const { value: data, reload } = useEndpointData('emoji-custom.all', query); - const handleItemClick = (_id) => () => { route.push({ context: 'edit', @@ -45,16 +25,6 @@ function CustomEmojiRoute() { }); }; - const handleHeaderClick = (id) => { - setSort(([sortBy, sortDirection]) => { - if (sortBy === id) { - return [id, sortDirection === 'asc' ? 'desc' : 'asc']; - } - - return [id, 'asc']; - }); - }; - const handleNewButtonClick = useCallback(() => { route.push({ context: 'new' }); }, [route]); @@ -63,8 +33,10 @@ function CustomEmojiRoute() { route.push({}); }; + const reload = useRef(() => null); + const handleChange = useCallback(() => { - reload(); + reload.current(); }, [reload]); if (!canManageEmoji) { @@ -80,14 +52,7 @@ function CustomEmojiRoute() { - + {context && ( diff --git a/definition/rest/v1/emojiCustom.ts b/definition/rest/v1/emojiCustom.ts index 0a49286ecfd53..c97caa027463e 100644 --- a/definition/rest/v1/emojiCustom.ts +++ b/definition/rest/v1/emojiCustom.ts @@ -1,6 +1,17 @@ import type { ICustomEmojiDescriptor } from '../../ICustomEmojiDescriptor'; +import { PaginatedRequest } from '../helpers/PaginatedRequest'; +import { PaginatedResult } from '../helpers/PaginatedResult'; export type EmojiCustomEndpoints = { + 'emoji-custom.all': { + GET: ( + params: { query: string } & PaginatedRequest & { + sort: string; // {name: 'asc' | 'desc';}>; + } + ) => { + emojis: ICustomEmojiDescriptor[]; + } & PaginatedResult; + }; 'emoji-custom.list': { GET: (params: { query: string }) => { emojis?: { From d9ccd5642619fa9da517e95b733929ed897b34c3 Mon Sep 17 00:00:00 2001 From: gabriellsh <40830821+gabriellsh@users.noreply.github.com> Date: Thu, 18 Nov 2021 18:46:49 -0300 Subject: [PATCH 087/137] [FIX] Autofocus on search input in admin (#23738) --- app/ui-utils/client/lib/SideNav.js | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/app/ui-utils/client/lib/SideNav.js b/app/ui-utils/client/lib/SideNav.js index fe0b5bab0c102..c6deaa23087ae 100644 --- a/app/ui-utils/client/lib/SideNav.js +++ b/app/ui-utils/client/lib/SideNav.js @@ -84,23 +84,6 @@ export const SideNav = new class { return AccountBox.toggle(); } - focusInput() { - const sideNavDivs = Array.from(this.sideNav[0].children).filter((el) => el.tagName === 'DIV' && !el.classList.contains('hidden')); - let highestZidx = 0; - let highestZidxElem; - sideNavDivs.forEach((el) => { - const zIndex = Number(window.getComputedStyle(el).zIndex); - if (zIndex > highestZidx) { - highestZidx = zIndex; - highestZidxElem = el; - } - }); - setTimeout(() => { - const ref = highestZidxElem && highestZidxElem.querySelector('input'); - return ref && ref.focus(); - }, 200); - } - validate() { const invalid = []; this.sideNav.find('input.required').each(function() { @@ -125,7 +108,6 @@ export const SideNav = new class { return; } this.toggleFlex(1, callback); - return this.focusInput(); } init() { From 733af4291c088cb51956cd949eff41a4b4b36a59 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Fri, 19 Nov 2021 07:59:21 -0600 Subject: [PATCH 088/137] [NEW] REST endpoints to manage Omnichannel Business Units (#23750) * add new endpoints to manage business units * change the way deprecations were being returned * change endpoints to be ts * Change to omnichannel * add deprecation warning to old endpoints --- app/api/server/helpers/deprecationWarning.ts | 6 +- definition/IOmnichannelBusinessUnit.ts | 9 ++ .../server/api/business-hours.ts | 1 - .../server/api/lib/monitors.js | 4 +- .../server/api/lib/units.js | 6 +- .../livechat-enterprise/server/api/units.js | 42 --------- .../livechat-enterprise/server/api/units.ts | 93 +++++++++++++++++++ ee/definition/rest/index.ts | 4 +- .../rest/v1/omnichannel/businessUnits.ts | 30 ++++++ ee/definition/rest/v1/omnichannel/index.ts | 4 + 10 files changed, 149 insertions(+), 50 deletions(-) create mode 100644 definition/IOmnichannelBusinessUnit.ts delete mode 100644 ee/app/livechat-enterprise/server/api/units.js create mode 100644 ee/app/livechat-enterprise/server/api/units.ts create mode 100644 ee/definition/rest/v1/omnichannel/businessUnits.ts create mode 100644 ee/definition/rest/v1/omnichannel/index.ts diff --git a/app/api/server/helpers/deprecationWarning.ts b/app/api/server/helpers/deprecationWarning.ts index edb347cd33b30..bfee0827733d4 100644 --- a/app/api/server/helpers/deprecationWarning.ts +++ b/app/api/server/helpers/deprecationWarning.ts @@ -1,7 +1,7 @@ import { API } from '../api'; import { apiDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -(API as any).helperMethods.set('deprecationWarning', function _deprecationWarning({ endpoint, versionWillBeRemoved, response }: { endpoint: string; versionWillBeRemoved: string; response: any }) { +export function deprecationWarning({ endpoint, versionWillBeRemoved = '5.0', response }: { endpoint: string; versionWillBeRemoved?: string; response: T }): T { const warningMessage = `The endpoint "${ endpoint }" is deprecated and will be removed after version ${ versionWillBeRemoved }`; apiDeprecationLogger.warn(warningMessage); if (process.env.NODE_ENV === 'development') { @@ -12,4 +12,6 @@ import { apiDeprecationLogger } from '../../../lib/server/lib/deprecationWarning } return response; -}); +} + +(API as any).helperMethods.set('deprecationWarning', deprecationWarning); diff --git a/definition/IOmnichannelBusinessUnit.ts b/definition/IOmnichannelBusinessUnit.ts new file mode 100644 index 0000000000000..9aacd2219effa --- /dev/null +++ b/definition/IOmnichannelBusinessUnit.ts @@ -0,0 +1,9 @@ +export interface IOmnichannelBusinessUnit { + _id: string; + name: string; + visibility: 'public' | 'private'; + type: string; + numMonitors: number; + numDepartments: number; + _updatedAt: Date; +} diff --git a/ee/app/livechat-enterprise/server/api/business-hours.ts b/ee/app/livechat-enterprise/server/api/business-hours.ts index 4640ad58ae3c3..0138efc1d55af 100644 --- a/ee/app/livechat-enterprise/server/api/business-hours.ts +++ b/ee/app/livechat-enterprise/server/api/business-hours.ts @@ -7,7 +7,6 @@ API.v1.addRoute('livechat/business-hours.list', { authRequired: true }, { const { sort } = this.parseJsonQuery(); const { name } = this.queryParams; - // @ts-ignore return API.v1.success(await findBusinessHours( this.userId, { diff --git a/ee/app/livechat-enterprise/server/api/lib/monitors.js b/ee/app/livechat-enterprise/server/api/lib/monitors.js index 6a9132c545089..04642b71bffde 100644 --- a/ee/app/livechat-enterprise/server/api/lib/monitors.js +++ b/ee/app/livechat-enterprise/server/api/lib/monitors.js @@ -17,7 +17,7 @@ export async function findMonitors({ userId, text, pagination: { offset, count, sort: sort || { name: 1 }, skip: offset, limit: count, - fields: { + projection: { username: 1, name: 1, status: 1, @@ -44,7 +44,7 @@ export async function findMonitorByUsername({ userId, username }) { throw new Error('error-not-authorized'); } const user = await Users.findOne({ username }, { - fields: { + projection: { username: 1, name: 1, status: 1, diff --git a/ee/app/livechat-enterprise/server/api/lib/units.js b/ee/app/livechat-enterprise/server/api/lib/units.js index b2838a310b063..6b0f8deedbc62 100644 --- a/ee/app/livechat-enterprise/server/api/lib/units.js +++ b/ee/app/livechat-enterprise/server/api/lib/units.js @@ -45,5 +45,9 @@ export async function findUnitById({ userId, unitId }) { if (!await hasPermissionAsync(userId, 'manage-livechat-units')) { throw new Error('error-not-authorized'); } - return LivechatUnit.findOneById(unitId); + const unit = LivechatUnit.findOneById(unitId); + + return { + unit, + }; } diff --git a/ee/app/livechat-enterprise/server/api/units.js b/ee/app/livechat-enterprise/server/api/units.js deleted file mode 100644 index 616e700f8f754..0000000000000 --- a/ee/app/livechat-enterprise/server/api/units.js +++ /dev/null @@ -1,42 +0,0 @@ -import { API } from '../../../../../app/api/server'; -import { findUnits, findUnitById, findUnitMonitors } from './lib/units'; - -API.v1.addRoute('livechat/units.list', { authRequired: true }, { - get() { - const { offset, count } = this.getPaginationItems(); - const { sort } = this.parseJsonQuery(); - const { text } = this.queryParams; - - return API.v1.success(Promise.await(findUnits({ - userId: this.userId, - text, - pagination: { - offset, - count, - sort, - }, - }))); - }, -}); - -API.v1.addRoute('livechat/units.getOne', { authRequired: true }, { - get() { - const { unitId } = this.queryParams; - - return API.v1.success(Promise.await(findUnitById({ - userId: this.userId, - unitId, - }))); - }, -}); - -API.v1.addRoute('livechat/unitMonitors.list', { authRequired: true }, { - get() { - const { unitId } = this.queryParams; - - return API.v1.success(Promise.await(findUnitMonitors({ - userId: this.userId, - unitId, - }))); - }, -}); diff --git a/ee/app/livechat-enterprise/server/api/units.ts b/ee/app/livechat-enterprise/server/api/units.ts new file mode 100644 index 0000000000000..9f6d94ee663c4 --- /dev/null +++ b/ee/app/livechat-enterprise/server/api/units.ts @@ -0,0 +1,93 @@ +import { API } from '../../../../../app/api/server'; +import { deprecationWarning } from '../../../../../app/api/server/helpers/deprecationWarning'; +import { findUnits, findUnitById, findUnitMonitors } from './lib/units'; +import { LivechatEnterprise } from '../lib/LivechatEnterprise'; +import { IOmnichannelBusinessUnit } from '../../../../../definition/IOmnichannelBusinessUnit'; + +API.v1.addRoute('livechat/units.list', { authRequired: true }, { + async get() { + const { offset, count } = this.getPaginationItems(); + const { sort } = this.parseJsonQuery(); + const { text } = this.queryParams; + + const response = await findUnits({ + userId: this.userId, + text, + pagination: { + offset, + count, + sort, + }, + }); + + return API.v1.success(deprecationWarning({ response, endpoint: 'livechat/units.list' })); + }, +}); + +API.v1.addRoute('livechat/units.getOne', { authRequired: true }, { + async get() { + const { id } = this.urlParams; + const { unit } = await findUnitById({ + userId: this.userId, + unitId: id, + }) as { unit: IOmnichannelBusinessUnit }; + + return API.v1.success(deprecationWarning({ response: unit, endpoint: 'livechat/units.getOne' })); + }, +}); + +API.v1.addRoute('livechat/unitMonitors.list', { authRequired: true }, { + async get() { + const { unitId } = this.queryParams; + + return API.v1.success(await findUnitMonitors({ + userId: this.userId, + unitId, + })); + }, +}); + +API.v1.addRoute('livechat/units', { authRequired: true, permissionsRequired: ['manage-livechat-units'] }, { + async get() { + const { offset, count } = this.getPaginationItems(); + const { sort } = this.parseJsonQuery(); + const { text } = this.queryParams; + + return API.v1.success(Promise.await(findUnits({ + userId: this.userId, + text, + pagination: { + offset, + count, + sort, + }, + }))); + }, + async post() { + const { unitData, unitMonitors, unitDepartments } = this.bodyParams?.(); + return LivechatEnterprise.saveUnit(null, unitData, unitMonitors, unitDepartments); + }, +}); + +API.v1.addRoute('livechat/units/:id', { authRequired: true, permissionsRequired: ['manage-livechat-units'] }, { + async get() { + const { id } = this.urlParams; + const { unit } = await findUnitById({ + userId: this.userId, + unitId: id, + }) as { unit: IOmnichannelBusinessUnit }; + + return API.v1.success(unit); + }, + async post() { + const { unitData, unitMonitors, unitDepartments } = this.bodyParams?.(); + const { id } = this.urlParams; + + return LivechatEnterprise.saveUnit(id, unitData, unitMonitors, unitDepartments); + }, + async delete() { + const { id } = this.urlParams; + + return LivechatEnterprise.removeUnit(id); + }, +}); diff --git a/ee/definition/rest/index.ts b/ee/definition/rest/index.ts index 292ebd766aa7c..052364327e57a 100644 --- a/ee/definition/rest/index.ts +++ b/ee/definition/rest/index.ts @@ -1,4 +1,4 @@ import type { EngagementDashboardEndpoints } from './v1/engagementDashboard'; -import type { OmnichannelBusinessHoursEndpoints } from './v1/omnichannel/businessHours'; +import type { OmnichannelEndpoints } from './v1/omnichannel'; -export type EnterpriseEndpoints = EngagementDashboardEndpoints & OmnichannelBusinessHoursEndpoints; +export type EnterpriseEndpoints = EngagementDashboardEndpoints & OmnichannelEndpoints; diff --git a/ee/definition/rest/v1/omnichannel/businessUnits.ts b/ee/definition/rest/v1/omnichannel/businessUnits.ts new file mode 100644 index 0000000000000..0d93cb56ebf81 --- /dev/null +++ b/ee/definition/rest/v1/omnichannel/businessUnits.ts @@ -0,0 +1,30 @@ +import { IOmnichannelBusinessUnit } from '../../../../../definition/IOmnichannelBusinessUnit'; +import { ILivechatMonitor } from '../../../../../definition/ILivechatMonitor'; + +type WithPagination = { + units: T; + count: number; + offset: number; + total: number; +} + +export type OmnichannelBusinessUnitsEndpoints = { + 'livechat/units.list': { + GET: () => (WithPagination); + }; + 'livechat/units.getOne': { + GET: () => (IOmnichannelBusinessUnit); + }; + 'livechat/unitMonitors.list': { + GET: () => ({ monitors: ILivechatMonitor[] }); + }; + 'livechat/units': { + GET: () => (WithPagination); + POST: () => IOmnichannelBusinessUnit; + }; + 'livechat/units/:id': { + GET: () => IOmnichannelBusinessUnit; + POST: () => IOmnichannelBusinessUnit; + DELETE: () => number; + }; +} diff --git a/ee/definition/rest/v1/omnichannel/index.ts b/ee/definition/rest/v1/omnichannel/index.ts new file mode 100644 index 0000000000000..36aecf6e9623f --- /dev/null +++ b/ee/definition/rest/v1/omnichannel/index.ts @@ -0,0 +1,4 @@ +import type { OmnichannelBusinessHoursEndpoints } from './businessHours'; +import type { OmnichannelBusinessUnitsEndpoints } from './businessUnits'; + +export type OmnichannelEndpoints = OmnichannelBusinessHoursEndpoints & OmnichannelBusinessUnitsEndpoints; From 9a3c2cf00bfdaa480fbb755ca20f17b5d5c73a53 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Fri, 19 Nov 2021 15:55:49 -0300 Subject: [PATCH 089/137] Regression: Units endpoint to TS (#23757) --- definition/rest/v1/omnichannel.ts | 1 + .../server/api/lib/units.js | 53 ---------------- .../server/api/lib/units.ts | 60 +++++++++++++++++++ .../livechat-enterprise/server/api/units.ts | 39 +++++++----- ...itEditWithData.js => UnitEditWithData.tsx} | 13 +++- .../rest/v1/omnichannel/businessUnits.ts | 39 ++++++++---- 6 files changed, 121 insertions(+), 84 deletions(-) delete mode 100644 ee/app/livechat-enterprise/server/api/lib/units.js create mode 100644 ee/app/livechat-enterprise/server/api/lib/units.ts rename ee/client/omnichannel/units/{UnitEditWithData.js => UnitEditWithData.tsx} (86%) diff --git a/definition/rest/v1/omnichannel.ts b/definition/rest/v1/omnichannel.ts index 1598355cb5bac..bf098ad76b3b7 100644 --- a/definition/rest/v1/omnichannel.ts +++ b/definition/rest/v1/omnichannel.ts @@ -55,6 +55,7 @@ export type OmnichannelEndpoints = { }; }; 'livechat/departments.by-unit/': { + path: `livechat/departments.by-unit/${ string }`; GET: (params: { text: string; offset: number; count: number }) => { departments: ILivechatDepartment[]; total: number; diff --git a/ee/app/livechat-enterprise/server/api/lib/units.js b/ee/app/livechat-enterprise/server/api/lib/units.js deleted file mode 100644 index 6b0f8deedbc62..0000000000000 --- a/ee/app/livechat-enterprise/server/api/lib/units.js +++ /dev/null @@ -1,53 +0,0 @@ -import { escapeRegExp } from '@rocket.chat/string-helpers'; - -import { hasPermissionAsync } from '../../../../../../app/authorization/server/functions/hasPermission'; -import LivechatUnit from '../../../../models/server/models/LivechatUnit'; -import LivechatUnitMonitors from '../../../../models/server/models/LivechatUnitMonitors'; - -export async function findUnits({ userId, text, pagination: { offset, count, sort } }) { - if (!await hasPermissionAsync(userId, 'manage-livechat-units')) { - throw new Error('error-not-authorized'); - } - const filter = new RegExp(escapeRegExp(text), 'i'); - - const query = { ...text && { $or: [{ name: filter }] } }; - - const cursor = LivechatUnit.find(query, { - sort: sort || { name: 1 }, - skip: offset, - limit: count, - }); - - const total = cursor.count(); - - const units = cursor.fetch(); - - return { - units, - count: units.length, - offset, - total, - }; -} - -export async function findUnitMonitors({ userId, unitId }) { - if (!await hasPermissionAsync(userId, 'manage-livechat-monitors')) { - throw new Error('error-not-authorized'); - } - const monitors = LivechatUnitMonitors.find({ unitId }).fetch(); - - return { - monitors, - }; -} - -export async function findUnitById({ userId, unitId }) { - if (!await hasPermissionAsync(userId, 'manage-livechat-units')) { - throw new Error('error-not-authorized'); - } - const unit = LivechatUnit.findOneById(unitId); - - return { - unit, - }; -} diff --git a/ee/app/livechat-enterprise/server/api/lib/units.ts b/ee/app/livechat-enterprise/server/api/lib/units.ts new file mode 100644 index 0000000000000..ba37e5200e49e --- /dev/null +++ b/ee/app/livechat-enterprise/server/api/lib/units.ts @@ -0,0 +1,60 @@ +import { escapeRegExp } from '@rocket.chat/string-helpers'; + +import { hasPermissionAsync } from '../../../../../../app/authorization/server/functions/hasPermission'; +import LivechatUnit from '../../../../models/server/models/LivechatUnit'; +import LivechatUnitMonitors from '../../../../models/server/raw/LivechatUnitMonitors'; +import { IOmnichannelBusinessUnit } from '../../../../../../definition/IOmnichannelBusinessUnit'; +import { ILivechatMonitor } from '../../../../../../definition/ILivechatMonitor'; + +export async function findUnits({ userId, text, pagination: { offset, count, sort } }: { + userId: string; + text?: string; + pagination: { + offset: number; + count: number; + sort: Record; + }; +}): Promise<{ + units: IOmnichannelBusinessUnit[]; + count: number; + offset: number; + total: number; + }> { + if (!await hasPermissionAsync(userId, 'manage-livechat-units')) { + throw new Error('error-not-authorized'); + } + const filter = text && new RegExp(escapeRegExp(text), 'i'); + + const query = { ...text && { $or: [{ name: filter }] } }; + + const cursor = LivechatUnit.find(query, { + sort: sort || { name: 1 }, + skip: offset, + limit: count, + }); + + const total = cursor.count(); + + const units = cursor.fetch(); + + return { + units, + count: units.length, + offset, + total, + }; +} + +export async function findUnitMonitors({ userId, unitId }: { userId: string; unitId: string }): Promise { + if (!await hasPermissionAsync(userId, 'manage-livechat-monitors')) { + throw new Error('error-not-authorized'); + } + return LivechatUnitMonitors.find({ unitId }).toArray() as Promise; +} + +export async function findUnitById({ userId, unitId }: { userId: string; unitId: string }): Promise { + if (!await hasPermissionAsync(userId, 'manage-livechat-units')) { + throw new Error('error-not-authorized'); + } + return LivechatUnit.findOneById(unitId); +} diff --git a/ee/app/livechat-enterprise/server/api/units.ts b/ee/app/livechat-enterprise/server/api/units.ts index 9f6d94ee663c4..9132aaa7cd9ef 100644 --- a/ee/app/livechat-enterprise/server/api/units.ts +++ b/ee/app/livechat-enterprise/server/api/units.ts @@ -2,7 +2,6 @@ import { API } from '../../../../../app/api/server'; import { deprecationWarning } from '../../../../../app/api/server/helpers/deprecationWarning'; import { findUnits, findUnitById, findUnitMonitors } from './lib/units'; import { LivechatEnterprise } from '../lib/LivechatEnterprise'; -import { IOmnichannelBusinessUnit } from '../../../../../definition/IOmnichannelBusinessUnit'; API.v1.addRoute('livechat/units.list', { authRequired: true }, { async get() { @@ -26,11 +25,16 @@ API.v1.addRoute('livechat/units.list', { authRequired: true }, { API.v1.addRoute('livechat/units.getOne', { authRequired: true }, { async get() { - const { id } = this.urlParams; - const { unit } = await findUnitById({ + const { unitId } = this.queryParams; + + if (!unitId) { + return API.v1.failure('Missing "unitId" query parameter'); + } + + const unit = await findUnitById({ userId: this.userId, - unitId: id, - }) as { unit: IOmnichannelBusinessUnit }; + unitId, + }); return API.v1.success(deprecationWarning({ response: unit, endpoint: 'livechat/units.getOne' })); }, @@ -40,10 +44,15 @@ API.v1.addRoute('livechat/unitMonitors.list', { authRequired: true }, { async get() { const { unitId } = this.queryParams; - return API.v1.success(await findUnitMonitors({ - userId: this.userId, - unitId, - })); + if (!unitId) { + return API.v1.failure('The "unitId" parameter is required'); + } + return API.v1.success({ + monitors: await findUnitMonitors({ + userId: this.userId, + unitId, + }), + }); }, }); @@ -53,7 +62,7 @@ API.v1.addRoute('livechat/units', { authRequired: true, permissionsRequired: ['m const { sort } = this.parseJsonQuery(); const { text } = this.queryParams; - return API.v1.success(Promise.await(findUnits({ + return API.v1.success(await findUnits({ userId: this.userId, text, pagination: { @@ -61,10 +70,10 @@ API.v1.addRoute('livechat/units', { authRequired: true, permissionsRequired: ['m count, sort, }, - }))); + })); }, async post() { - const { unitData, unitMonitors, unitDepartments } = this.bodyParams?.(); + const { unitData, unitMonitors, unitDepartments } = this.bodyParams; return LivechatEnterprise.saveUnit(null, unitData, unitMonitors, unitDepartments); }, }); @@ -72,15 +81,15 @@ API.v1.addRoute('livechat/units', { authRequired: true, permissionsRequired: ['m API.v1.addRoute('livechat/units/:id', { authRequired: true, permissionsRequired: ['manage-livechat-units'] }, { async get() { const { id } = this.urlParams; - const { unit } = await findUnitById({ + const unit = await findUnitById({ userId: this.userId, unitId: id, - }) as { unit: IOmnichannelBusinessUnit }; + }); return API.v1.success(unit); }, async post() { - const { unitData, unitMonitors, unitDepartments } = this.bodyParams?.(); + const { unitData, unitMonitors, unitDepartments } = this.bodyParams; const { id } = this.urlParams; return LivechatEnterprise.saveUnit(id, unitData, unitMonitors, unitDepartments); diff --git a/ee/client/omnichannel/units/UnitEditWithData.js b/ee/client/omnichannel/units/UnitEditWithData.tsx similarity index 86% rename from ee/client/omnichannel/units/UnitEditWithData.js rename to ee/client/omnichannel/units/UnitEditWithData.tsx index 8057160888f2d..4c685f4d57f58 100644 --- a/ee/client/omnichannel/units/UnitEditWithData.js +++ b/ee/client/omnichannel/units/UnitEditWithData.tsx @@ -1,5 +1,5 @@ import { Callout } from '@rocket.chat/fuselage'; -import React, { useMemo } from 'react'; +import React, { useMemo, FC } from 'react'; import { FormSkeleton } from '../../../../client/components/Skeleton'; import { useTranslation } from '../../../../client/contexts/TranslationContext'; @@ -7,9 +7,15 @@ import { AsyncStatePhase } from '../../../../client/hooks/useAsyncState'; import { useEndpointData } from '../../../../client/hooks/useEndpointData'; import UnitEdit from './UnitEdit'; -function UnitEditWithData({ unitId, reload, title }) { +const UnitEditWithData: FC<{ + unitId: string; + title: string; + reload: () => void; +}> = function UnitEditWithData({ unitId, reload, title }) { const query = useMemo(() => ({ unitId }), [unitId]); + const { value: data, phase: state, error } = useEndpointData('livechat/units.getOne', query); + const { value: unitMonitors, phase: unitMonitorsState, @@ -44,8 +50,9 @@ function UnitEditWithData({ unitId, reload, title }) { unitMonitors={unitMonitors} unitDepartments={unitDepartments} reload={reload} + isNew={false} /> ); -} +}; export default UnitEditWithData; diff --git a/ee/definition/rest/v1/omnichannel/businessUnits.ts b/ee/definition/rest/v1/omnichannel/businessUnits.ts index 0d93cb56ebf81..9b18f13865cfe 100644 --- a/ee/definition/rest/v1/omnichannel/businessUnits.ts +++ b/ee/definition/rest/v1/omnichannel/businessUnits.ts @@ -1,30 +1,43 @@ import { IOmnichannelBusinessUnit } from '../../../../../definition/IOmnichannelBusinessUnit'; import { ILivechatMonitor } from '../../../../../definition/ILivechatMonitor'; - -type WithPagination = { - units: T; - count: number; - offset: number; - total: number; -} +import { PaginatedResult } from '../../../../../definition/rest/helpers/PaginatedResult'; export type OmnichannelBusinessUnitsEndpoints = { 'livechat/units.list': { - GET: () => (WithPagination); + GET: (params: { + text: string; + }) => (PaginatedResult & { + units: IOmnichannelBusinessUnit[]; + }); }; 'livechat/units.getOne': { - GET: () => (IOmnichannelBusinessUnit); + GET: (params: { + unitId: string; + }) => (IOmnichannelBusinessUnit); }; 'livechat/unitMonitors.list': { - GET: () => ({ monitors: ILivechatMonitor[] }); + GET: (params: { + unitId: string; + }) => ({ monitors: ILivechatMonitor[] }); }; 'livechat/units': { - GET: () => (WithPagination); - POST: () => IOmnichannelBusinessUnit; + GET: (params: { + text: string; + }) => (PaginatedResult & + { units: IOmnichannelBusinessUnit[] }); + POST: (params: { + unitData: string; + unitMonitors: string; + unitDepartments: string; + }) => IOmnichannelBusinessUnit; }; 'livechat/units/:id': { GET: () => IOmnichannelBusinessUnit; - POST: () => IOmnichannelBusinessUnit; + POST: (params: { + unitData: string; + unitMonitors: string; + unitDepartments: string; + }) => IOmnichannelBusinessUnit; DELETE: () => number; }; } From c83c0be48541e6834e304d566b0a05f29d69f41b Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Fri, 19 Nov 2021 17:13:22 -0300 Subject: [PATCH 090/137] [IMPROVE] Stricter API types (#23735) Co-authored-by: Guilherme Gazzo --- app/api/server/api.d.ts | 203 +++++++++++------- app/api/server/v1/banners.ts | 39 +--- app/api/server/v1/roles.ts | 45 ++-- app/api/server/v1/settings.ts | 4 +- app/api/server/v1/teams.ts | 147 +++++++++---- app/models/server/raw/Sessions.ts | 56 ++--- app/utils/client/lib/RestApiClient.d.ts | 43 ++++ app/utils/client/lib/RestApiClient.js | 1 + .../contexts/ServerContext/ServerContext.ts | 35 ++- client/hooks/useEndpointAction.ts | 20 +- client/hooks/useEndpointActionExperimental.ts | 21 +- client/hooks/useEndpointData.ts | 24 ++- client/lib/userData.ts | 34 ++- client/providers/ServerProvider.tsx | 18 +- client/startup/banners.ts | 17 +- .../admin/settings/groups/LDAPGroupPage.tsx | 8 +- .../views/hooks/useDepartmentsByUnitsList.ts | 4 +- definition/Serialized.ts | 19 +- definition/externals/meteor/check.d.ts | 7 + .../rest/helpers/ReplacePlaceholders.ts | 8 + definition/rest/index.ts | 111 ++++++---- definition/rest/v1/banners.ts | 8 +- definition/rest/v1/omnichannel.ts | 11 +- definition/rest/v1/roles.ts | 5 + definition/rest/v1/teams/index.ts | 28 ++- definition/utils.ts | 3 + .../rest/v1/omnichannel/businessHours.ts | 12 +- server/sdk/types/ITeamService.ts | 2 +- tests/end-to-end/api/21-banners.js | 7 - 29 files changed, 590 insertions(+), 350 deletions(-) create mode 100644 app/utils/client/lib/RestApiClient.d.ts create mode 100644 definition/externals/meteor/check.d.ts create mode 100644 definition/rest/helpers/ReplacePlaceholders.ts diff --git a/app/api/server/api.d.ts b/app/api/server/api.d.ts index 94b44c774fe6f..1edac24cae4cb 100644 --- a/app/api/server/api.d.ts +++ b/app/api/server/api.d.ts @@ -1,77 +1,44 @@ -import { Endpoints } from '../../../definition/rest'; -import { Awaited } from '../../../definition/utils'; -import { IUser } from '../../../definition/IUser'; +import type { JoinPathPattern, Method, MethodOf, OperationParams, OperationResult, PathPattern, UrlParams } from '../../../definition/rest'; +import type { IUser } from '../../../definition/IUser'; - -export type ChangeTypeOfKeys< - T extends object, - Keys extends keyof T, - NewType -> = { - [key in keyof T]: key extends Keys ? NewType : T[key] -} - -type This = { - getPaginationItems(): ({ - offset: number; - count: number; - }); - parseJsonQuery(): ({ - sort: Record; - fields: Record; - query: Record; - }); - readonly urlParams: Record; - getUserFromParams(): IUser; -} - -type ThisLoggedIn = { - readonly user: IUser; - readonly userId: string; -} - -type ThisLoggedOut = { - readonly user: null; - readonly userId: null; -} - -type EndpointWithExtraOptions any, A> = WrappedFunction | ({ action: WrappedFunction } & (A extends true ? { twoFactorRequired: boolean } : {})); - -export type Methods = { - [K in keyof T as `${Lowercase}`]: T[K] extends (...args: any) => any ? EndpointWithExtraOptions<(this: This & (A extends true ? ThisLoggedIn : ThisLoggedOut) & Params[0]>) => ReturnType, A> : never; +type SuccessResult = { + statusCode: 200; + body: + T extends object + ? { success: true } & T + : T; }; -type Params = K extends 'GET' ? { readonly queryParams: Partial

    } : K extends 'POST' ? { readonly bodyParams: Partial

    } : never; - -type SuccessResult = { - statusCode: 200; - success: true; -} & T extends (undefined) ? {} : { body: T } +type FailureResult = { + statusCode: 400; + body: + T extends object + ? { success: false } & T + : ({ + success: false; + error: T; + stack: TStack; + errorType: TErrorType; + details: TErrorDetails; + }) & ( + undefined extends TErrorType + ? {} + : { errorType: TErrorType } + ) & ( + undefined extends TErrorDetails + ? {} + : { details: TErrorDetails extends string ? unknown : TErrorDetails } + ); +}; -type UnauthorizedResult = { +type UnauthorizedResult = { statusCode: 403; body: { success: false; - error: string; + error: T | 'unauthorized'; }; } -type FailureResult = { - statusCode: 400; -} & FailureBody; - -type FailureBody = Exclude extends object ? { body: T & E } : { - body: E & { error: string } & E extends Error ? { details: string } : {} & ST extends undefined ? {} : { stack: string } & ET extends undefined ? {} : { errorType: string }; -} - -type Errors = FailureResult | FailureResult | FailureResult | UnauthorizedResult; - -type WrappedFunction any> = (this: ThisParameterType, ...args: Parameters) => ReturnTypes; - -type ReturnTypes any> = PromisedOrNot> | PromisedOrNot>; - -type PromisedOrNot = Promise | T; - type Options = { permissionsRequired?: string[]; twoFactorOptions?: unknown; @@ -79,32 +46,102 @@ type Options = { authRequired?: boolean; } -export type RestEndpoints

    = Methods; - -type ToLowerCaseKeys = { - [K in keyof T as `${Lowercase}`]: T[K]; +type ActionThis = { + urlParams: UrlParams; + // TODO make it unsafe + readonly queryParams: TMethod extends 'GET' ? Partial> : Record; + // TODO make it unsafe + readonly bodyParams: TMethod extends 'GET' ? Record : Partial>; + requestParams(): OperationParams; + getPaginationItems(): { + readonly offset: number; + readonly count: number; + }; + parseJsonQuery(): { + sort: Record; + fields: Record; + query: Record; + }; + getUserFromParams(): IUser; +} & ( + TOptions extends { authRequired: true } + ? { + readonly user: IUser; + readonly userId: string; + } + : { + readonly user: null; + readonly userId: null; + } +); + +export type ResultFor< + TMethod extends Method, + TPathPattern extends PathPattern +> = SuccessResult> | FailureResult | UnauthorizedResult; + +type Action = + ((this: ActionThis) => Promise>) + | ((this: ActionThis) => ResultFor); + +type Operation = Action | { + action: Action; +} & ({ twoFactorRequired: boolean }); + +type Operations = { + [M in MethodOf as Lowercase]: Operation, TPathPattern, TOptions>; }; -type ToResultType = { - [K in keyof T]: T[K] extends (...args: any) => any ? Awaited> : never; -} -export type ResultTypeEndpoints

    = ToResultType>; -declare class APIClass { - addRoute

    (route: P, endpoints: RestEndpoints

    ): void; +declare class APIClass { + addRoute< + TSubPathPattern extends string + >(subpath: TSubPathPattern, operations: Operations>): void; + + addRoute< + TSubPathPattern extends string, + TPathPattern extends JoinPathPattern + >(subpaths: TSubPathPattern[], operations: Operations): void; + + addRoute< + TSubPathPattern extends string, + TOptions extends Options + >( + subpath: TSubPathPattern, + options: TOptions, + operations: Operations, TOptions> + ): void; + + addRoute< + TSubPathPattern extends string, + TPathPattern extends JoinPathPattern, + TOptions extends Options + >( + subpaths: TSubPathPattern[], + options: TOptions, + operations: Operations + ): void; - addRoute

    (route: P, options: O, endpoints: RestEndpoints): void; + success(result: T): SuccessResult; - unauthorized(msg?: string): UnauthorizedResult; + success(): SuccessResult; - failure(result: string, errorType?: ET, stack?: ST, error?: E): FailureResult; + failure< + T, + TErrorType extends string, + TStack extends string, + TErrorDetails + >( + result: T, + errorType?: TErrorType, + stack?: TStack, + error?: { details: TErrorDetails } + ): FailureResult; - failure(result: object): FailureResult; + failure(result: T): FailureResult; - failure(): FailureResult; + failure(): FailureResult; - success(): SuccessResult; - - success(result: T): SuccessResult; + unauthorized(msg?: T): UnauthorizedResult; defaultFieldsToExclude: { joinCode: 0; @@ -115,6 +152,6 @@ declare class APIClass { } export declare const API: { - v1: APIClass; + v1: APIClass<'/v1'>; default: APIClass; }; diff --git a/app/api/server/v1/banners.ts b/app/api/server/v1/banners.ts index 678d6e726cf24..790dbd138afaa 100644 --- a/app/api/server/v1/banners.ts +++ b/app/api/server/v1/banners.ts @@ -1,4 +1,3 @@ -import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; import { API } from '../api'; @@ -54,20 +53,13 @@ import { BannerPlatform } from '../../../../definition/IBanner'; API.v1.addRoute('banners.getNew', { authRequired: true }, { // deprecated async get() { check(this.queryParams, Match.ObjectIncluding({ - platform: String, + platform: Match.OneOf(...Object.values(BannerPlatform)), bid: Match.Maybe(String), })); const { platform, bid: bannerId } = this.queryParams; - if (!platform) { - throw new Meteor.Error('error-missing-param', 'The required "platform" param is missing.'); - } - if (!Object.values(BannerPlatform).includes(platform)) { - throw new Meteor.Error('error-unknown-platform', 'Platform is unknown.'); - } - - const banners = await Banner.getBannersForUser(this.userId, platform, bannerId); + const banners = await Banner.getBannersForUser(this.userId, platform, bannerId ?? undefined); return API.v1.success({ banners }); }, @@ -122,23 +114,15 @@ API.v1.addRoute('banners.getNew', { authRequired: true }, { // deprecated API.v1.addRoute('banners/:id', { authRequired: true }, { // TODO: move to users/:id/banners async get() { check(this.urlParams, Match.ObjectIncluding({ - id: String, + id: Match.Where((id: unknown): id is string => typeof id === 'string' && Boolean(id.trim())), })); check(this.queryParams, Match.ObjectIncluding({ - platform: String, + platform: Match.OneOf(...Object.values(BannerPlatform)), })); const { platform } = this.queryParams; - - if (!platform) { - throw new Meteor.Error('error-missing-param', 'The required "platform" param is missing.'); - } - const { id } = this.urlParams; - if (!id) { - throw new Meteor.Error('error-missing-param', 'The required "id" param is missing.'); - } const banners = await Banner.getBannersForUser(this.userId, platform, id); @@ -186,17 +170,10 @@ API.v1.addRoute('banners/:id', { authRequired: true }, { // TODO: move to users/ API.v1.addRoute('banners', { authRequired: true }, { async get() { check(this.queryParams, Match.ObjectIncluding({ - platform: String, + platform: Match.OneOf(...Object.values(BannerPlatform)), })); const { platform } = this.queryParams; - if (!platform) { - throw new Meteor.Error('error-missing-param', 'The required "platform" param is missing.'); - } - - if (!Object.values(BannerPlatform).includes(platform)) { - throw new Meteor.Error('error-unknown-platform', 'Platform is unknown.'); - } const banners = await Banner.getBannersForUser(this.userId, platform); @@ -240,15 +217,11 @@ API.v1.addRoute('banners', { authRequired: true }, { API.v1.addRoute('banners.dismiss', { authRequired: true }, { async post() { check(this.bodyParams, Match.ObjectIncluding({ - bannerId: String, + bannerId: Match.Where((id: unknown): id is string => typeof id === 'string' && Boolean(id.trim())), })); const { bannerId } = this.bodyParams; - if (!bannerId || !bannerId.trim()) { - throw new Meteor.Error('error-missing-param', 'The required "bannerId" param is missing.'); - } - await Banner.dismiss(this.userId, bannerId); return API.v1.success(); }, diff --git a/app/api/server/v1/roles.ts b/app/api/server/v1/roles.ts index 95c4b34a5d7f3..ef92c3547c2d5 100644 --- a/app/api/server/v1/roles.ts +++ b/app/api/server/v1/roles.ts @@ -1,4 +1,5 @@ import { Meteor } from 'meteor/meteor'; +import { check, Match } from 'meteor/check'; import { Users } from '../../../models/server'; import { API } from '../api'; @@ -20,11 +21,11 @@ API.v1.addRoute('roles.list', { authRequired: true }, { API.v1.addRoute('roles.sync', { authRequired: true }, { async get() { - const { updatedSince } = this.queryParams; + check(this.queryParams, Match.ObjectIncluding({ + updatedSince: Match.Where((value: unknown): value is string => typeof value === 'string' && !Number.isNaN(Date.parse(value))), + })); - if (isNaN(Date.parse(updatedSince))) { - throw new Meteor.Error('error-updatedSince-param-invalid', 'The "updatedSince" query parameter must be a valid date.'); - } + const { updatedSince } = this.queryParams; return API.v1.success({ roles: { @@ -41,25 +42,23 @@ API.v1.addRoute('roles.create', { authRequired: true }, { throw new Meteor.Error('error-invalid-role-properties', 'The role properties are invalid.'); } - const roleData = { - name: this.bodyParams.name, - scope: this.bodyParams.scope, - description: this.bodyParams.description, - mandatory2fa: this.bodyParams.mandatory2fa, - }; + const { name, scope, description, mandatory2fa } = this.bodyParams; if (!await hasPermissionAsync(Meteor.userId(), 'access-permissions')) { throw new Meteor.Error('error-action-not-allowed', 'Accessing permissions is not allowed'); } - if (await Roles.findOneByIdOrName(roleData.name)) { + if (await Roles.findOneByIdOrName(name)) { throw new Meteor.Error('error-duplicate-role-names-not-allowed', 'Role name already exists'); } - if (['Users', 'Subscriptions'].includes(roleData.scope) === false) { - roleData.scope = 'Users'; - } - const roleId = (await Roles.createWithRandomId(roleData.name, roleData.scope, roleData.description, false, roleData.mandatory2fa)).insertedId; + const roleId = (await Roles.createWithRandomId( + name, + scope && ['Users', 'Subscriptions'].includes(scope) ? scope : 'Users', + description, + false, + mandatory2fa, + )).insertedId; if (settings.get('UI_DisplayRoles')) { api.broadcast('user.roleUpdate', { @@ -236,29 +235,25 @@ API.v1.addRoute('roles.removeUserFromRole', { authRequired: true }, { throw new Meteor.Error('error-invalid-role-properties', 'The role properties are invalid.'); } - const data = { - roleName: bodyParams.roleName, - username: bodyParams.username, - scope: this.bodyParams.scope, - }; + const { roleName, username, scope } = bodyParams; if (!await hasPermissionAsync(this.userId, 'access-permissions')) { throw new Meteor.Error('error-not-allowed', 'Accessing permissions is not allowed'); } - const user = Users.findOneByUsername(data.username); + const user = Users.findOneByUsername(username); if (!user) { throw new Meteor.Error('error-invalid-user', 'There is no user with this username'); } - const role = await Roles.findOneByIdOrName(data.roleName); + const role = await Roles.findOneByIdOrName(roleName); if (!role) { throw new Meteor.Error('error-invalid-roleId', 'This role does not exist'); } - if (!await hasRoleAsync(user._id, role.name, data.scope)) { + if (!await hasRoleAsync(user._id, role.name, scope)) { throw new Meteor.Error('error-user-not-in-role', 'User is not in this role'); } @@ -269,7 +264,7 @@ API.v1.addRoute('roles.removeUserFromRole', { authRequired: true }, { } } - await Roles.removeUserRoles(user._id, [role.name], data.scope); + await Roles.removeUserRoles(user._id, [role.name], scope); if (settings.get('UI_DisplayRoles')) { api.broadcast('user.roleUpdate', { @@ -279,7 +274,7 @@ API.v1.addRoute('roles.removeUserFromRole', { authRequired: true }, { _id: user._id, username: user.username, }, - scope: data.scope, + scope, }); } diff --git a/app/api/server/v1/settings.ts b/app/api/server/v1/settings.ts index b9233b1e09d13..ca0a8ac5178da 100644 --- a/app/api/server/v1/settings.ts +++ b/app/api/server/v1/settings.ts @@ -4,7 +4,7 @@ import _ from 'underscore'; import { Settings } from '../../../models/server/raw'; import { hasPermission } from '../../../authorization/server'; -import { API, ResultTypeEndpoints } from '../api'; +import { API, ResultFor } from '../api'; import { SettingsEvents, settings } from '../../../settings/server'; import { setValue } from '../../../settings/server/raw'; import { ISetting, ISettingColor, isSettingAction, isSettingColor } from '../../../../definition/ISetting'; @@ -126,7 +126,7 @@ API.v1.addRoute('settings/:_id', { authRequired: true }, { }, post: { twoFactorRequired: true, - async action(): Promise['post']> { + async action(): Promise> { if (!hasPermission(this.userId, 'edit-privileged-setting')) { return API.v1.unauthorized(); } diff --git a/app/api/server/v1/teams.ts b/app/api/server/v1/teams.ts index dab3ac1e0a82f..7cdb8120ecddc 100644 --- a/app/api/server/v1/teams.ts +++ b/app/api/server/v1/teams.ts @@ -18,6 +18,7 @@ import { isTeamsAddMembersProps } from '../../../../definition/rest/v1/teams/Tea import { isTeamsDeleteProps } from '../../../../definition/rest/v1/teams/TeamsDeleteProps'; import { isTeamsLeaveProps } from '../../../../definition/rest/v1/teams/TeamsLeaveProps'; import { isTeamsUpdateProps } from '../../../../definition/rest/v1/teams/TeamsUpdateProps'; +import { ITeam, TEAM_TYPE } from '../../../../definition/ITeam'; API.v1.addRoute('teams.list', { authRequired: true }, { async get() { @@ -59,11 +60,16 @@ API.v1.addRoute('teams.create', { authRequired: true }, { if (!hasPermission(this.userId, 'create-team')) { return API.v1.unauthorized(); } - const { name, type, members, room, owner } = this.bodyParams; - if (!name) { - return API.v1.failure('Body param "name" is required'); - } + check(this.bodyParams, Match.ObjectIncluding({ + name: String, + type: Match.OneOf(TEAM_TYPE.PRIVATE, TEAM_TYPE.PUBLIC), + members: Match.Maybe([String]), + room: Match.Maybe(Match.Any), + owner: Match.Maybe(String), + })); + + const { name, type, members, room, owner } = this.bodyParams; const team = await Team.create(this.userId, { team: { @@ -120,15 +126,34 @@ API.v1.addRoute('teams.convertToChannel', { authRequired: true }, { }, }); +const getTeamByIdOrName = async (params: { teamId: string } | { teamName: string }): Promise => { + if ('teamId' in params && params.teamId) { + return Team.getOneById(params.teamId); + } + + if ('teamName' in params && params.teamName) { + return Team.getOneByName(params.teamName); + } + + return null; +}; + API.v1.addRoute('teams.addRooms', { authRequired: true }, { async post() { - const { rooms, teamId, teamName } = this.bodyParams; - - if (!teamId && !teamName) { - return API.v1.failure('missing-teamId-or-teamName'); - } + check(this.bodyParams, Match.OneOf( + Match.ObjectIncluding({ + teamId: String, + }), + Match.ObjectIncluding({ + teamName: String, + }), + )); + + check(this.bodyParams, Match.ObjectIncluding({ + rooms: [String], + })); - const team = await (teamId ? Team.getOneById(teamId) : Team.getOneByName(teamName)); + const team = await getTeamByIdOrName(this.bodyParams); if (!team) { return API.v1.failure('team-does-not-exist'); } @@ -137,6 +162,8 @@ API.v1.addRoute('teams.addRooms', { authRequired: true }, { return API.v1.unauthorized('error-no-permission-team-channel'); } + const { rooms } = this.bodyParams; + const validRooms = await Team.addRooms(this.userId, rooms, team._id); return API.v1.success({ rooms: validRooms }); @@ -148,9 +175,8 @@ API.v1.addRoute('teams.removeRoom', { authRequired: true }, { if (!isTeamsRemoveRoomProps(this.bodyParams)) { return API.v1.failure('body-params-invalid', isTeamsRemoveRoomProps.errors?.map((error) => error.message).join('\n ')); } - const { roomId, teamId, teamName } = this.bodyParams; - const team = await (teamId ? Team.getOneById(teamId) : Team.getOneByName(teamName)); + const team = await getTeamByIdOrName(this.bodyParams); if (!team) { return API.v1.failure('team-does-not-exist'); } @@ -161,6 +187,8 @@ API.v1.addRoute('teams.removeRoom', { authRequired: true }, { const canRemoveAny = !!hasPermission(this.userId, 'view-all-team-channels', team.roomId); + const { roomId } = this.bodyParams; + const room = await Team.removeRoom(this.userId, roomId, team._id, canRemoveAny); return API.v1.success({ room }); @@ -169,6 +197,11 @@ API.v1.addRoute('teams.removeRoom', { authRequired: true }, { API.v1.addRoute('teams.updateRoom', { authRequired: true }, { async post() { + check(this.bodyParams, Match.ObjectIncluding({ + roomId: String, + isDefault: Boolean, + })); + const { roomId, isDefault } = this.bodyParams; const team = await Team.getOneByRoomId(roomId); @@ -189,15 +222,29 @@ API.v1.addRoute('teams.updateRoom', { authRequired: true }, { API.v1.addRoute('teams.listRooms', { authRequired: true }, { async get() { - const { teamId, teamName, filter, type } = this.queryParams; + check(this.queryParams, Match.OneOf( + Match.ObjectIncluding({ + teamId: String, + }), + Match.ObjectIncluding({ + teamName: String, + }), + )); + + check(this.queryParams, Match.ObjectIncluding({ + filter: Match.Maybe(String), + type: Match.Maybe(String), + })); + + const { filter, type } = this.queryParams; const { offset, count } = this.getPaginationItems(); - const team = await (teamId ? Team.getOneById(teamId) : Team.getOneByName(teamName)); + const team = await getTeamByIdOrName(this.queryParams); if (!team) { return API.v1.failure('team-does-not-exist'); } - const allowPrivateTeam = hasPermission(this.userId, 'view-all-teams', team.roomId); + const allowPrivateTeam: boolean = hasPermission(this.userId, 'view-all-teams', team.roomId); let getAllRooms = false; if (hasPermission(this.userId, 'view-all-team-channels', team.roomId)) { @@ -205,7 +252,7 @@ API.v1.addRoute('teams.listRooms', { authRequired: true }, { } const listFilter = { - name: filter, + name: filter ?? undefined, isDefault: type === 'autoJoin', getAllRooms, allowPrivateTeam, @@ -224,27 +271,36 @@ API.v1.addRoute('teams.listRooms', { authRequired: true }, { API.v1.addRoute('teams.listRoomsOfUser', { authRequired: true }, { async get() { - const { offset, count } = this.getPaginationItems(); - const { teamId, teamName, userId, canUserDelete = false } = this.queryParams; - + check(this.queryParams, Match.OneOf( + Match.ObjectIncluding({ + teamId: String, + }), + Match.ObjectIncluding({ + teamName: String, + }), + )); - if (!teamId && !teamName) { - return API.v1.failure('missing-teamId-or-teamName'); - } + check(this.queryParams, Match.ObjectIncluding({ + userId: String, + canUserDelete: Match.Maybe(Boolean), + })); - const team = await (teamId ? Team.getOneById(teamId) : Team.getOneByName(teamName!)); + const { offset, count } = this.getPaginationItems(); + const team = await getTeamByIdOrName(this.queryParams); if (!team) { return API.v1.failure('team-does-not-exist'); } const allowPrivateTeam = hasPermission(this.userId, 'view-all-teams', team.roomId); + const { userId, canUserDelete } = this.queryParams; + if (!(this.userId === userId || hasPermission(this.userId, 'view-all-team-channels', team.roomId))) { return API.v1.unauthorized(); } - const { records, total } = await Team.listRoomsOfUser(this.userId, team._id, userId, allowPrivateTeam, canUserDelete, { offset, count }); + const { records, total } = await Team.listRoomsOfUser(this.userId, team._id, userId, allowPrivateTeam, canUserDelete ?? false, { offset, count }); return API.v1.success({ rooms: records, @@ -259,23 +315,28 @@ API.v1.addRoute('teams.members', { authRequired: true }, { async get() { const { offset, count } = this.getPaginationItems(); + check(this.queryParams, Match.OneOf( + Match.ObjectIncluding({ + teamId: String, + }), + Match.ObjectIncluding({ + teamName: String, + }), + )); + check(this.queryParams, Match.ObjectIncluding({ - teamId: Match.Maybe(String), - teamName: Match.Maybe(String), status: Match.Maybe([String]), username: Match.Maybe(String), name: Match.Maybe(String), })); - const { teamId, teamName, status, username, name } = this.queryParams; - if (!teamId && !teamName) { - return API.v1.failure('missing-teamId-or-teamName'); - } + const { status, username, name } = this.queryParams; - const team = await (teamId ? Team.getOneById(teamId) : Team.getOneByName(teamName)); + const team = await getTeamByIdOrName(this.queryParams); if (!team) { return API.v1.failure('team-does-not-exist'); } + const canSeeAllMembers = hasPermission(this.userId, 'view-all-teams', team.roomId); const query = { @@ -431,16 +492,16 @@ API.v1.addRoute('teams.leave', { authRequired: true }, { API.v1.addRoute('teams.info', { authRequired: true }, { async get() { - const { teamId, teamName } = this.queryParams; - - if (!teamId && !teamName) { - return API.v1.failure('Provide either the "teamId" or "teamName"'); - } - - const teamInfo = await (teamId - ? Team.getInfoById(teamId) - : Team.getInfoByName(teamName)); - + check(this.queryParams, Match.OneOf( + Match.ObjectIncluding({ + teamId: String, + }), + Match.ObjectIncluding({ + teamName: String, + }), + )); + + const teamInfo = await getTeamByIdOrName(this.queryParams); if (!teamInfo) { return API.v1.failure('Team not found'); } @@ -498,6 +559,10 @@ API.v1.addRoute('teams.delete', { authRequired: true }, { API.v1.addRoute('teams.autocomplete', { authRequired: true }, { async get() { + check(this.queryParams, Match.ObjectIncluding({ + name: String, + })); + const { name } = this.queryParams; const teams = await Team.autocomplete(this.userId, name); diff --git a/app/models/server/raw/Sessions.ts b/app/models/server/raw/Sessions.ts index d79fd32c89133..dbd6080f18b6f 100644 --- a/app/models/server/raw/Sessions.ts +++ b/app/models/server/raw/Sessions.ts @@ -1,14 +1,14 @@ import { AggregationCursor, BulkWriteOperation, BulkWriteOpResultObject, Collection, IndexSpecification, UpdateWriteOpResult, FilterQuery } from 'mongodb'; -import { ISession as T } from '../../../../definition/ISession'; +import { ISession } from '../../../../definition/ISession'; import { BaseRaw, ModelOptionalId } from './BaseRaw'; type DestructuredDate = {year: number; month: number; day: number}; type DestructuredDateWithType = {year: number; month: number; day: number; type?: 'month' | 'week'}; type DestructuredRange = {start: DestructuredDate; end: DestructuredDate}; -type FullReturn = { year: number; month: number; day: number; data: T[] }; +type FullReturn = { year: number; month: number; day: number; data: ISession[] }; -const matchBasedOnDate = (start: DestructuredDate, end: DestructuredDate): FilterQuery => { +const matchBasedOnDate = (start: DestructuredDate, end: DestructuredDate): FilterQuery => { if (start.year === end.year && start.month === end.month) { return { year: start.year, @@ -112,16 +112,16 @@ const getProjectionByFullDate = (): { day: string; month: string; year: string } }); export const aggregates = { - dailySessionsOfYesterday(collection: Collection, { year, month, day }: DestructuredDate): AggregationCursor & { + dailySessionsOfYesterday(collection: Collection, { year, month, day }: DestructuredDate): AggregationCursor & { time: number; sessions: number; - devices: T['device'][]; + devices: ISession['device'][]; _computedAt: string; }> { - return collection.aggregate & { + return collection.aggregate & { time: number; sessions: number; - devices: T['device'][]; + devices: ISession['device'][]; _computedAt: string; }>([{ $match: { @@ -211,7 +211,7 @@ export const aggregates = { }], { allowDiskUse: true }); }, - async getUniqueUsersOfYesterday(collection: Collection, { year, month, day }: DestructuredDate): Promise { + async getUniqueUsersOfYesterday(collection: Collection, { year, month, day }: DestructuredDate): Promise { return collection.aggregate([{ $match: { year, @@ -273,7 +273,7 @@ export const aggregates = { }]).toArray(); }, - async getUniqueUsersOfLastMonthOrWeek(collection: Collection, { year, month, day, type = 'month' }: DestructuredDateWithType): Promise { + async getUniqueUsersOfLastMonthOrWeek(collection: Collection, { year, month, day, type = 'month' }: DestructuredDateWithType): Promise { return collection.aggregate([{ $match: { type: 'user_daily', @@ -343,7 +343,7 @@ export const aggregates = { }], { allowDiskUse: true }).toArray(); }, - getMatchOfLastMonthOrWeek({ year, month, day, type = 'month' }: DestructuredDateWithType): FilterQuery { + getMatchOfLastMonthOrWeek({ year, month, day, type = 'month' }: DestructuredDateWithType): FilterQuery { let startOfPeriod; if (type === 'month') { @@ -418,7 +418,7 @@ export const aggregates = { }; }, - async getUniqueDevicesOfLastMonthOrWeek(collection: Collection, { year, month, day, type = 'month' }: DestructuredDateWithType): Promise { + async getUniqueDevicesOfLastMonthOrWeek(collection: Collection, { year, month, day, type = 'month' }: DestructuredDateWithType): Promise { return collection.aggregate([{ $match: { type: 'user_daily', @@ -456,7 +456,7 @@ export const aggregates = { }], { allowDiskUse: true }).toArray(); }, - getUniqueDevicesOfYesterday(collection: Collection, { year, month, day }: DestructuredDate): Promise { + getUniqueDevicesOfYesterday(collection: Collection, { year, month, day }: DestructuredDate): Promise { return collection.aggregate([{ $match: { year, @@ -496,7 +496,7 @@ export const aggregates = { }]).toArray(); }, - getUniqueOSOfLastMonthOrWeek(collection: Collection, { year, month, day, type = 'month' }: DestructuredDateWithType): Promise { + getUniqueOSOfLastMonthOrWeek(collection: Collection, { year, month, day, type = 'month' }: DestructuredDateWithType): Promise { return collection.aggregate([{ $match: { type: 'user_daily', @@ -535,7 +535,7 @@ export const aggregates = { }], { allowDiskUse: true }).toArray(); }, - getUniqueOSOfYesterday(collection: Collection, { year, month, day }: DestructuredDate): Promise { + getUniqueOSOfYesterday(collection: Collection, { year, month, day }: DestructuredDate): Promise { return collection.aggregate([{ $match: { year, @@ -577,7 +577,7 @@ export const aggregates = { }, }; -export class SessionsRaw extends BaseRaw { +export class SessionsRaw extends BaseRaw { protected indexes: IndexSpecification[] = [ { key: { instanceId: 1, sessionId: 1, year: 1, month: 1, day: 1 } }, { key: { instanceId: 1, sessionId: 1, userId: 1 } }, @@ -590,19 +590,19 @@ export class SessionsRaw extends BaseRaw { { key: { _computedAt: 1 }, expireAfterSeconds: 60 * 60 * 24 * 45 }, ] - private secondaryCollection: Collection; + private secondaryCollection: Collection; constructor( - public readonly col: Collection, - public readonly colSecondary: Collection, - public readonly trash?: Collection, + public readonly col: Collection, + public readonly colSecondary: Collection, + public readonly trash?: Collection, ) { super(col, trash); this.secondaryCollection = colSecondary; } - async getActiveUsersBetweenDates({ start, end }: DestructuredRange): Promise { + async getActiveUsersBetweenDates({ start, end }: DestructuredRange): Promise { return this.col.aggregate([ { $match: { @@ -618,7 +618,7 @@ export class SessionsRaw extends BaseRaw { ]).toArray(); } - async findLastLoginByIp(ip: string): Promise { + async findLastLoginByIp(ip: string): Promise { return this.findOne({ ip, }, { @@ -627,7 +627,7 @@ export class SessionsRaw extends BaseRaw { }); } - async getActiveUsersOfPeriodByDayBetweenDates({ start, end }: DestructuredRange): Promise { + async getActiveUsersOfPeriodByDayBetweenDates({ start, end }: DestructuredRange): Promise { return this.col.aggregate([ { $match: { @@ -675,7 +675,7 @@ export class SessionsRaw extends BaseRaw { ]).toArray(); } - async getBusiestTimeWithinHoursPeriod({ start, end, groupSize }: DestructuredRange & {groupSize: number}): Promise { + async getBusiestTimeWithinHoursPeriod({ start, end, groupSize }: DestructuredRange & {groupSize: number}): Promise { const match = { $match: { type: 'computed-session', @@ -709,7 +709,7 @@ export class SessionsRaw extends BaseRaw { return this.col.aggregate([match, rangeProject, unwind, groups.listGroup, groups.countGroup, presentationProject, sort]).toArray(); } - async getTotalOfSessionsByDayBetweenDates({ start, end }: DestructuredRange): Promise { + async getTotalOfSessionsByDayBetweenDates({ start, end }: DestructuredRange): Promise { return this.col.aggregate([ { $match: { @@ -739,7 +739,7 @@ export class SessionsRaw extends BaseRaw { ]).toArray(); } - async getTotalOfSessionByHourAndDayBetweenDates({ start, end }: DestructuredRange): Promise { + async getTotalOfSessionByHourAndDayBetweenDates({ start, end }: DestructuredRange): Promise { const match = { $match: { type: 'computed-session', @@ -922,7 +922,7 @@ export class SessionsRaw extends BaseRaw { }; } - async createOrUpdate(data: T): Promise { + async createOrUpdate(data: ISession): Promise { const { year, month, day, sessionId, instanceId } = data; if (!year || !month || !day || !sessionId || !instanceId) { @@ -992,12 +992,12 @@ export class SessionsRaw extends BaseRaw { return this.updateMany(query, update); } - async createBatch(sessions: ModelOptionalId[]): Promise { + async createBatch(sessions: ModelOptionalId[]): Promise { if (!sessions || sessions.length === 0) { return; } - const ops: BulkWriteOperation[] = []; + const ops: BulkWriteOperation[] = []; sessions.forEach((doc) => { const { year, month, day, sessionId, instanceId } = doc; delete doc._id; diff --git a/app/utils/client/lib/RestApiClient.d.ts b/app/utils/client/lib/RestApiClient.d.ts new file mode 100644 index 0000000000000..452fb2a9c13d7 --- /dev/null +++ b/app/utils/client/lib/RestApiClient.d.ts @@ -0,0 +1,43 @@ +import { Serialized } from '../../../../definition/Serialized'; + +export declare const APIClient: { + delete(endpoint: string, params?: Serialized

    ): Promise>; + get(endpoint: string, params?: Serialized

    ): Promise>; + post(endpoint: string, params?: Serialized

    , body?: B): Promise>; + upload( + endpoint: string, + params?: Serialized

    , + formData?: B, + xhrOptions?: { + progress: (amount: number) => void; + error: (ev: ProgressEvent) => void; + } + ): { promise: Promise> }; + getCredentials(): { + 'X-User-Id': string; + 'X-Auth-Token': string; + }; + _jqueryCall( + method?: string, + endpoint?: string, + params?: any, + body?: any, + headers?: Record, + dataType?: string + ): any; + v1: { + delete(endpoint: string, params?: Serialized

    ): Promise>; + get(endpoint: string, params?: Serialized

    ): Promise>; + post(endpoint: string, params?: Serialized

    , body?: B): Promise>; + upload( + endpoint: string, + params?: Serialized

    , + formData?: B, + xhrOptions?: { + progress: (amount: number) => void; + error: (ev: ProgressEvent) => void; + } + ): { promise: Promise> }; + }; +}; diff --git a/app/utils/client/lib/RestApiClient.js b/app/utils/client/lib/RestApiClient.js index 9d857af0d580d..bcec20f9df76c 100644 --- a/app/utils/client/lib/RestApiClient.js +++ b/app/utils/client/lib/RestApiClient.js @@ -1,5 +1,6 @@ import { Meteor } from 'meteor/meteor'; import { Accounts } from 'meteor/accounts-base'; +import jQuery from 'jquery'; import { process2faReturn } from '../../../../client/lib/2fa/process2faReturn'; import { baseURI } from '../../../../client/lib/baseURI'; diff --git a/client/contexts/ServerContext/ServerContext.ts b/client/contexts/ServerContext/ServerContext.ts index 15b8c50f97f51..838500ef1864f 100644 --- a/client/contexts/ServerContext/ServerContext.ts +++ b/client/contexts/ServerContext/ServerContext.ts @@ -1,8 +1,15 @@ import { createContext, useCallback, useContext, useMemo } from 'react'; -import { IServerInfo } from '../../../definition/IServerInfo'; +import type { IServerInfo } from '../../../definition/IServerInfo'; import type { Serialized } from '../../../definition/Serialized'; -import type { PathFor, Params, Return, Method } from '../../../definition/rest'; +import type { + Method, + PathFor, + OperationParams, + MatchPathPattern, + OperationResult, + PathPattern, +} from '../../../definition/rest'; import { ServerMethodFunction, ServerMethodName, @@ -18,11 +25,11 @@ type ServerContextValue = { methodName: MethodName, ...args: ServerMethodParameters ) => Promise>; - callEndpoint: >( - method: M, - path: P, - params: Params[0], - ) => Promise>>; + callEndpoint: >( + method: TMethod, + path: TPath, + params: Serialized>>, + ) => Promise>>>; uploadToEndpoint: (endpoint: string, params: any, formData: any) => Promise; getStream: ( streamName: string, @@ -70,10 +77,16 @@ export const useMethod = ( ); }; -export const useEndpoint = >( - method: M, - path: P, -): ((params: Params[0]) => Promise>>) => { +type EndpointFunction = ( + params: void extends OperationParams + ? void + : Serialized>, +) => Promise>>; + +export const useEndpoint = >( + method: TMethod, + path: TPath, +): EndpointFunction> => { const { callEndpoint } = useContext(ServerContext); return useCallback((params) => callEndpoint(method, path, params), [callEndpoint, path, method]); diff --git a/client/hooks/useEndpointAction.ts b/client/hooks/useEndpointAction.ts index 0deb6cdd7d7c4..62990514185ca 100644 --- a/client/hooks/useEndpointAction.ts +++ b/client/hooks/useEndpointAction.ts @@ -1,16 +1,24 @@ import { useCallback } from 'react'; import { Serialized } from '../../definition/Serialized'; -import { Method, Params, PathFor, Return } from '../../definition/rest'; +import { + MatchPathPattern, + Method, + OperationParams, + OperationResult, + PathFor, +} from '../../definition/rest'; import { useEndpoint } from '../contexts/ServerContext'; import { useToastMessageDispatch } from '../contexts/ToastMessagesContext'; -export const useEndpointAction = >( - method: M, - path: P, - params: Params[0] = {}, +export const useEndpointAction = >( + method: TMethod, + path: TPath, + params: Serialized>> = {} as Serialized< + OperationParams> + >, successMessage?: string, -): ((extraParams?: Params[1]) => Promise>>) => { +): (() => Promise>>>) => { const sendData = useEndpoint(method, path); const dispatchToastMessage = useToastMessageDispatch(); diff --git a/client/hooks/useEndpointActionExperimental.ts b/client/hooks/useEndpointActionExperimental.ts index 0e82f6d428306..1b5420acae277 100644 --- a/client/hooks/useEndpointActionExperimental.ts +++ b/client/hooks/useEndpointActionExperimental.ts @@ -1,15 +1,26 @@ import { useCallback } from 'react'; import { Serialized } from '../../definition/Serialized'; -import { Method, Params, PathFor, Return } from '../../definition/rest'; +import { + MatchPathPattern, + Method, + OperationParams, + OperationResult, + PathFor, +} from '../../definition/rest'; import { useEndpoint } from '../contexts/ServerContext'; import { useToastMessageDispatch } from '../contexts/ToastMessagesContext'; -export const useEndpointActionExperimental = >( - method: M, - path: P, +export const useEndpointActionExperimental = < + TMethod extends Method, + TPath extends PathFor, +>( + method: TMethod, + path: TPath, successMessage?: string, -): ((params: Params[0]) => Promise>>) => { +): (( + params: Serialized>>, +) => Promise>>>) => { const sendData = useEndpoint(method, path); const dispatchToastMessage = useToastMessageDispatch(); diff --git a/client/hooks/useEndpointData.ts b/client/hooks/useEndpointData.ts index 1e8f5ebc880cd..38469217d1723 100644 --- a/client/hooks/useEndpointData.ts +++ b/client/hooks/useEndpointData.ts @@ -1,18 +1,26 @@ import { useCallback, useEffect } from 'react'; import { Serialized } from '../../definition/Serialized'; -import { Params, PathFor, Return } from '../../definition/rest'; +import { MatchPathPattern, OperationParams, OperationResult, PathFor } from '../../definition/rest'; import { useEndpoint } from '../contexts/ServerContext'; import { useToastMessageDispatch } from '../contexts/ToastMessagesContext'; import { AsyncState, useAsyncState } from './useAsyncState'; -const defaultParams = {}; - -export const useEndpointData =

    >( - endpoint: P, - params: Params<'GET', P>[0] = defaultParams as Params<'GET', P>[0], - initialValue?: Serialized> | (() => Serialized>), -): AsyncState>> & { reload: () => void } => { +export const useEndpointData = >( + endpoint: TPath, + params: void extends OperationParams<'GET', MatchPathPattern> + ? void + : Serialized< + OperationParams<'GET', MatchPathPattern> + > = undefined as void extends OperationParams<'GET', MatchPathPattern> + ? void + : Serialized>>, + initialValue?: + | Serialized>> + | (() => Serialized>>), +): AsyncState>>> & { + reload: () => void; +} => { const { resolve, reject, reset, ...state } = useAsyncState(initialValue); const dispatchToastMessage = useToastMessageDispatch(); const getData = useEndpoint('GET', endpoint); diff --git a/client/lib/userData.ts b/client/lib/userData.ts index 2b37d1957f5a1..76488ead80137 100644 --- a/client/lib/userData.ts +++ b/client/lib/userData.ts @@ -5,14 +5,39 @@ import { Users } from '../../app/models/client'; import { Notifications } from '../../app/notifications/client'; import { APIClient } from '../../app/utils/client'; import type { IUser, IUserDataEvent } from '../../definition/IUser'; +import { Serialized } from '../../definition/Serialized'; export const isSyncReady = new ReactiveVar(false); -type RawUserData = Omit & { - _updatedAt: string; -}; +type RawUserData = Serialized< + Pick< + IUser, + | '_id' + | 'type' + | 'name' + | 'username' + | 'emails' + | 'status' + | 'statusDefault' + | 'statusText' + | 'statusConnection' + | 'avatarOrigin' + | 'utcOffset' + | 'language' + | 'settings' + | 'roles' + | 'active' + | 'defaultRoom' + | 'customFields' + | 'statusLivechat' + | 'oauth' + | 'createdAt' + | '_updatedAt' + | 'avatarETag' + > +>; -const updateUser = (userData: IUser & { _updatedAt: Date }): void => { +const updateUser = (userData: IUser): void => { const user: IUser = Users.findOne({ _id: userData._id }); if (!user || !user._updatedAt || user._updatedAt.getTime() < userData._updatedAt.getTime()) { @@ -57,6 +82,7 @@ export const synchronizeUserData = async (uid: Meteor.User['_id']): Promise( }); }); -const callEndpoint = >( - method: M, - path: P, - params: Params[0], -): Promise>> => { +const callEndpoint = >( + method: TMethod, + path: TPath, + params: Serialized>>, +): Promise>>> => { const api = path[0] === '/' ? APIClient : APIClient.v1; const endpointPath = path[0] === '/' ? path.slice(1) : path; diff --git a/client/startup/banners.ts b/client/startup/banners.ts index 57e0d9c7daa9e..64ba23041accc 100644 --- a/client/startup/banners.ts +++ b/client/startup/banners.ts @@ -4,14 +4,15 @@ import { Tracker } from 'meteor/tracker'; import { Notifications } from '../../app/notifications/client'; import { APIClient } from '../../app/utils/client'; import { IBanner, BannerPlatform } from '../../definition/IBanner'; +import { Serialized } from '../../definition/Serialized'; import * as banners from '../lib/banners'; const fetchInitialBanners = async (): Promise => { - const response = (await APIClient.get('v1/banners', { - platform: BannerPlatform.Web, - })) as { + const response: Serialized<{ banners: IBanner[]; - }; + }> = await APIClient.get('v1/banners', { + platform: BannerPlatform.Web, + }); for (const banner of response.banners) { banners.open({ @@ -22,11 +23,11 @@ const fetchInitialBanners = async (): Promise => { }; const handleBanner = async (event: { bannerId: string }): Promise => { - const response = (await APIClient.get(`v1/banners/${event.bannerId}`, { - platform: BannerPlatform.Web, - })) as { + const response: Serialized<{ banners: IBanner[]; - }; + }> = await APIClient.get(`v1/banners/${event.bannerId}`, { + platform: BannerPlatform.Web, + }); if (!response.banners.length) { return banners.closeById(event.bannerId); diff --git a/client/views/admin/settings/groups/LDAPGroupPage.tsx b/client/views/admin/settings/groups/LDAPGroupPage.tsx index 94a4b59273a15..f466c56d3d17c 100644 --- a/client/views/admin/settings/groups/LDAPGroupPage.tsx +++ b/client/views/admin/settings/groups/LDAPGroupPage.tsx @@ -39,7 +39,7 @@ function LDAPGroupPage({ _id, ...group }: ISetting): JSX.Element { const handleTestConnectionButtonClick = async (): Promise => { try { - const { message } = await testConnection(undefined); + const { message } = await testConnection(); dispatchToastMessage({ type: 'success', message: t(message) }); } catch (error) { dispatchToastMessage({ type: 'error', message: error }); @@ -48,12 +48,12 @@ function LDAPGroupPage({ _id, ...group }: ISetting): JSX.Element { const handleSyncNowButtonClick = async (): Promise => { try { - await testConnection(undefined); + await testConnection(); const confirmSync = async (): Promise => { closeModal(); try { - const { message } = await syncNow(undefined); + const { message } = await syncNow(); dispatchToastMessage({ type: 'success', message: t(message) }); } catch (error) { dispatchToastMessage({ type: 'error', message: error }); @@ -79,7 +79,7 @@ function LDAPGroupPage({ _id, ...group }: ISetting): JSX.Element { const handleSearchTestButtonClick = async (): Promise => { try { - await testConnection(undefined); + await testConnection(); let username = ''; const handleChangeUsername = (event: FormEvent): void => { username = event.currentTarget.value; diff --git a/client/views/hooks/useDepartmentsByUnitsList.ts b/client/views/hooks/useDepartmentsByUnitsList.ts index e2a1de9352a96..7211afe283fc9 100644 --- a/client/views/hooks/useDepartmentsByUnitsList.ts +++ b/client/views/hooks/useDepartmentsByUnitsList.ts @@ -21,9 +21,7 @@ export const useDepartmentsByUnitsList = ( } => { const [itemsList, setItemsList] = useState(() => new RecordList()); const reload = useCallback(() => setItemsList(new RecordList()), []); - const endpoint = `livechat/departments.available-by-unit/${ - options.unitId || 'none' - }` as 'livechat/departments.by-unit/'; + const endpoint = `livechat/departments.available-by-unit/${options.unitId || 'none'}` as const; const getDepartments = useEndpoint('GET', endpoint); diff --git a/definition/Serialized.ts b/definition/Serialized.ts index 2871f401689e3..a6626dcdb9b84 100644 --- a/definition/Serialized.ts +++ b/definition/Serialized.ts @@ -1,9 +1,10 @@ -export type Serialized = T extends Date - ? (Exclude | string) - : T extends boolean | number | string | null | undefined - ? T - : T extends {} - ? { - [K in keyof T]: Serialized; - } - : null; +export type Serialized = + T extends Date + ? (Exclude | string) + : T extends boolean | number | string | null | undefined + ? T + : T extends {} + ? { + [K in keyof T]: Serialized; + } + : null; diff --git a/definition/externals/meteor/check.d.ts b/definition/externals/meteor/check.d.ts new file mode 100644 index 0000000000000..401b56c831fd3 --- /dev/null +++ b/definition/externals/meteor/check.d.ts @@ -0,0 +1,7 @@ +import 'meteor/check'; + +declare module 'meteor/check' { + namespace Match { + function Where(condition: (val: T) => val is U): Matcher; + } +} diff --git a/definition/rest/helpers/ReplacePlaceholders.ts b/definition/rest/helpers/ReplacePlaceholders.ts new file mode 100644 index 0000000000000..d8503b4b8471f --- /dev/null +++ b/definition/rest/helpers/ReplacePlaceholders.ts @@ -0,0 +1,8 @@ +export type ReplacePlaceholders = + string extends TPath + ? TPath + : TPath extends `${ infer Start }:${ infer _Param }/${ infer Rest }` + ? `${ Start }${ string }/${ ReplacePlaceholders }` + : TPath extends `${ infer Start }:${ infer _Param }` + ? `${ Start }${ string }` + : TPath; diff --git a/definition/rest/index.ts b/definition/rest/index.ts index b7bad4b5ffdae..8a2f9e3105a70 100644 --- a/definition/rest/index.ts +++ b/definition/rest/index.ts @@ -1,7 +1,8 @@ import type { EnterpriseEndpoints } from '../../ee/definition/rest'; -import type { ExtractKeys, ValueOf } from '../utils'; +import type { KeyOfEach } from '../utils'; import type { AppsEndpoints } from './apps'; -import { BannersEndpoints } from './v1/banners'; +import type { ReplacePlaceholders } from './helpers/ReplacePlaceholders'; +import type { BannersEndpoints } from './v1/banners'; import type { ChannelsEndpoints } from './v1/channels'; import type { ChatEndpoints } from './v1/chat'; import type { CloudEndpoints } from './v1/cloud'; @@ -11,20 +12,21 @@ import type { DnsEndpoints } from './v1/dns'; import type { EmojiCustomEndpoints } from './v1/emojiCustom'; import type { GroupsEndpoints } from './v1/groups'; import type { ImEndpoints } from './v1/im'; -import { InstancesEndpoints } from './v1/instances'; +import type { InstancesEndpoints } from './v1/instances'; import type { LDAPEndpoints } from './v1/ldap'; import type { LicensesEndpoints } from './v1/licenses'; import type { MiscEndpoints } from './v1/misc'; import type { OmnichannelEndpoints } from './v1/omnichannel'; -import { PermissionsEndpoints } from './v1/permissions'; -import { RolesEndpoints } from './v1/roles'; +import type { PermissionsEndpoints } from './v1/permissions'; +import type { RolesEndpoints } from './v1/roles'; import type { RoomsEndpoints } from './v1/rooms'; -import { SettingsEndpoints } from './v1/settings'; +import type { SettingsEndpoints } from './v1/settings'; import type { StatisticsEndpoints } from './v1/statistics'; import type { TeamsEndpoints } from './v1/teams'; import type { UsersEndpoints } from './v1/users'; -type CommunityEndpoints = BannersEndpoints & ChatEndpoints & +type CommunityEndpoints = BannersEndpoints & +ChatEndpoints & ChannelsEndpoints & CloudEndpoints & CustomUserStatusEndpoints & @@ -47,51 +49,76 @@ MiscEndpoints & PermissionsEndpoints & InstancesEndpoints; -export type Endpoints = CommunityEndpoints & EnterpriseEndpoints; +type Endpoints = CommunityEndpoints & EnterpriseEndpoints; -type Endpoint = UnionizeEndpoints; +type OperationsByPathPattern = TPathPattern extends any + ? OperationsByPathPatternAndMethod + : never; -type UnionizeEndpoints = ValueOf< -{ - [P in keyof EE]: UnionizeMethods; -} ->; +type OperationsByPathPatternAndMethod< + TPathPattern extends keyof Endpoints, + TMethod extends KeyOfEach = KeyOfEach +> = TMethod extends any + ? { + pathPattern: TPathPattern; + method: TMethod; + path: ReplacePlaceholders; + params: GetParams; + result: GetResult; + } + : never; -type ExtractOperations = ExtractKeys any>; +type Operations = OperationsByPathPattern; -type UnionizeMethods = ValueOf< -{ - [M in keyof OO as ExtractOperations]: ( - method: M, - path: OO extends { path: string } ? OO['path'] : P, - ...params: Parameters any>> - ) => ReturnType any>>; -} ->; +export type PathPattern = Operations['pathPattern']; -export type Method = Parameters[0]; -export type Path = Parameters[1]; +export type Method = Operations['method']; -export type MethodFor

    = P extends any - ? Parameters any>>[0] +export type Path = Operations['path']; + +export type MethodFor = TPath extends any + ? Extract['method'] : never; -export type PathFor = M extends any - ? Parameters any>>[1] + +export type PathFor = TMethod extends any + ? Extract['path'] : never; -type Operation> = M extends any - ? P extends any - ? Extract any> - : never +export type MatchPathPattern = TPath extends any + ? Extract['pathPattern'] : never; -type ExtractParams = Q extends [any, any] - ? [undefined?] - : Q extends [any, any, any, ...any[]] - ? [Q[2]] +export type JoinPathPattern = Extract< +PathPattern, +`${ TBasePath }/${ TSubPathPattern }` | TSubPathPattern +>; + +type GetParams = TOperation extends (...args: any) => any + ? Parameters[0] extends void ? void : Parameters[0] + : never + +type GetResult = TOperation extends (...args: any) => any + ? ReturnType + : never + +export type OperationParams = + TMethod extends keyof Endpoints[TPathPattern] + ? GetParams : never; -export type Params> = ExtractParams< -Parameters> ->; -export type Return> = ReturnType>; +export type OperationResult = + TMethod extends keyof Endpoints[TPathPattern] + ? GetResult + : never; + +export type UrlParams = string extends T + ? Record + : T extends `${ infer _Start }:${ infer Param }/${ infer Rest }` + ? { [k in Param | keyof UrlParams]: string } + : T extends `${ infer _Start }:${ infer Param }` + ? { [k in Param]: string } + : {}; + +export type MethodOf = TPathPattern extends any + ? keyof Endpoints[TPathPattern] + : never; diff --git a/definition/rest/v1/banners.ts b/definition/rest/v1/banners.ts index e448abb7b410f..7b667a4f26e9c 100644 --- a/definition/rest/v1/banners.ts +++ b/definition/rest/v1/banners.ts @@ -1,21 +1,21 @@ -import { IBanner } from '../../IBanner'; +import type { BannerPlatform, IBanner } from '../../IBanner'; export type BannersEndpoints = { /* @deprecated */ 'banners.getNew': { - GET: () => ({ + GET: (params: { platform: BannerPlatform; bid: IBanner['_id'] }) => ({ banners: IBanner[]; }); }; 'banners/:id': { - GET: (params: { platform: string }) => ({ + GET: (params: { platform: BannerPlatform }) => ({ banners: IBanner[]; }); }; 'banners': { - GET: () => ({ + GET: (params: { platform: BannerPlatform }) => ({ banners: IBanner[]; }); }; diff --git a/definition/rest/v1/omnichannel.ts b/definition/rest/v1/omnichannel.ts index bf098ad76b3b7..80d85f4ba4bf7 100644 --- a/definition/rest/v1/omnichannel.ts +++ b/definition/rest/v1/omnichannel.ts @@ -49,13 +49,17 @@ export type OmnichannelEndpoints = { }; }; 'livechat/department/:_id': { - path: `livechat/department/${ string }`; GET: () => { department: ILivechatDepartment; }; }; - 'livechat/departments.by-unit/': { - path: `livechat/departments.by-unit/${ string }`; + 'livechat/departments.by-unit/:id': { + GET: (params: { text: string; offset: number; count: number }) => { + departments: ILivechatDepartment[]; + total: number; + }; + }; + 'livechat/departments.available-by-unit/:id': { GET: (params: { text: string; offset: number; count: number }) => { departments: ILivechatDepartment[]; total: number; @@ -139,7 +143,6 @@ export type OmnichannelEndpoints = { DELETE: (params: { _id: IOmnichannelCannedResponse['_id'] }) => void; }; 'canned-responses/:_id': { - path: `canned-responses/${ string }`; GET: () => { cannedResponse: IOmnichannelCannedResponse; }; diff --git a/definition/rest/v1/roles.ts b/definition/rest/v1/roles.ts index 844d125083b3d..bccc9a37e1d10 100644 --- a/definition/rest/v1/roles.ts +++ b/definition/rest/v1/roles.ts @@ -109,6 +109,7 @@ type RoleRemoveUserFromRoleProps = { username: string; roleName: string; roomId?: string; + scope?: string; } const roleRemoveUserFromRolePropsSchema: JSONSchemaType = { @@ -124,6 +125,10 @@ const roleRemoveUserFromRolePropsSchema: JSONSchemaType PaginatedResult & { rooms: IRoom[] }; + GET: (params: PaginatedRequest & ({ teamId: string } | { teamName: string }) & { filter?: string; type?: string }) => PaginatedResult & { rooms: IRoom[] }; }; diff --git a/definition/utils.ts b/definition/utils.ts index 72339afa798a0..211bcd5234dea 100644 --- a/definition/utils.ts +++ b/definition/utils.ts @@ -9,3 +9,6 @@ export type UnionToIntersection = (T extends any ? (x: T) => void : never) ex : never; export type Awaited = T extends PromiseLike ? Awaited : T; + +// `T extends any` is a trick to apply a operator to each member of a union +export type KeyOfEach = T extends any ? keyof T : never; diff --git a/ee/definition/rest/v1/omnichannel/businessHours.ts b/ee/definition/rest/v1/omnichannel/businessHours.ts index 77b352c612599..1211ccdb9de05 100644 --- a/ee/definition/rest/v1/omnichannel/businessHours.ts +++ b/ee/definition/rest/v1/omnichannel/businessHours.ts @@ -2,6 +2,16 @@ import { ILivechatBusinessHour } from '../../../../../definition/ILivechatBusine export type OmnichannelBusinessHoursEndpoints = { 'livechat/business-hours.list': { - GET: () => ({ businessHours: ILivechatBusinessHour[] }); + GET: (params: { + name?: string; + offset: number; + count: number; + sort: Record; + }) => { + businessHours: ILivechatBusinessHour[]; + count: number; + offset: number; + total: number; + }; }; } diff --git a/server/sdk/types/ITeamService.ts b/server/sdk/types/ITeamService.ts index 697a67e406418..1d464759397ab 100644 --- a/server/sdk/types/ITeamService.ts +++ b/server/sdk/types/ITeamService.ts @@ -41,7 +41,7 @@ export interface ITeamInfo extends ITeam { } export interface IListRoomsFilter { - name: string; + name?: string; isDefault: boolean; getAllRooms: boolean; allowPrivateTeam: boolean; diff --git a/tests/end-to-end/api/21-banners.js b/tests/end-to-end/api/21-banners.js index 6d31095aced43..ede0cccc737a8 100644 --- a/tests/end-to-end/api/21-banners.js +++ b/tests/end-to-end/api/21-banners.js @@ -27,7 +27,6 @@ describe('banners', function() { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'Match error: Missing key \'platform\''); }) .end(done); }); @@ -41,8 +40,6 @@ describe('banners', function() { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'Platform is unknown. [error-unknown-platform]'); - expect(res.body).to.have.property('errorType', 'error-unknown-platform'); }) .end(done); }); @@ -56,8 +53,6 @@ describe('banners', function() { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'The required "platform" param is missing. [error-missing-param]'); - expect(res.body).to.have.property('errorType', 'error-missing-param'); }) .end(done); }); @@ -112,8 +107,6 @@ describe('banners', function() { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'The required "bannerId" param is missing. [error-missing-param]'); - expect(res.body).to.have.property('errorType', 'error-missing-param'); }) .end(done); }); From e0fa7018dd96f9a75cebd64681a3184ea73a7c65 Mon Sep 17 00:00:00 2001 From: Leonardo Ostjen Couto Date: Fri, 19 Nov 2021 19:29:12 -0300 Subject: [PATCH 091/137] [NEW] Permission for download/uploading files on mobile (#23686) * created new permission * removed unnecessary empty lines * fixed permission import on migration --- .../server/functions/upsertPermissions.ts | 2 ++ packages/rocketchat-i18n/i18n/en.i18n.json | 2 ++ packages/rocketchat-i18n/i18n/pt-BR.i18n.json | 2 ++ server/startup/migrations/index.ts | 1 + server/startup/migrations/v245.ts | 10 ++++++++++ 5 files changed, 17 insertions(+) create mode 100644 server/startup/migrations/v245.ts diff --git a/app/authorization/server/functions/upsertPermissions.ts b/app/authorization/server/functions/upsertPermissions.ts index ad4c06c224717..6c41b1b11af09 100644 --- a/app/authorization/server/functions/upsertPermissions.ts +++ b/app/authorization/server/functions/upsertPermissions.ts @@ -150,6 +150,8 @@ export const upsertPermissions = async (): Promise => { { _id: 'access-mailer', roles: ['admin'] }, { _id: 'pin-message', roles: ['owner', 'moderator', 'admin'] }, { _id: 'snippet-message', roles: ['owner', 'moderator', 'admin'] }, + { _id: 'mobile-upload-file', roles: ['user', 'admin'] }, + { _id: 'mobile-download-file', roles: ['user', 'admin'] }, ]; diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index a616292d9191e..84a53aec4bfb5 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -2979,6 +2979,8 @@ "Mobex_sms_gateway_restful_address_desc": "IP or Host of your Mobex REST API. E.g. `http://192.168.1.1:8080` or `https://www.example.com:8080`", "Mobex_sms_gateway_username": "Username", "Mobile": "Mobile", + "mobile-download-file": "Allow file download on mobile devices", + "mobile-upload-file": "Allow file upload on mobile devices", "Mobile_Push_Notifications_Default_Alert": "Push Notifications Default Alert", "Monday": "Monday", "Mongo_storageEngine": "Mongo Storage Engine", diff --git a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json index f34ce0b737cd2..10a52bf43458b 100644 --- a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json +++ b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json @@ -2974,6 +2974,8 @@ "Mobex_sms_gateway_restful_address_desc": "IP ou Host da API REST Mobex. Por exemplo, `http://192.168.1.1:8080` ou `https://www.example.com:8080`", "Mobex_sms_gateway_username": "Nome de Usuário", "Mobile": "Móvel", + "mobile-download-file": "Permitir download em dispositivos móveis", + "mobile-upload-file": "Permitir upload em dispositivos móveis", "Mobile_Push_Notifications_Default_Alert": "Alertas Padrão de Notificações Push", "Monday": "Segunda-feira", "Mongo_storageEngine": "Mongo Storage Engine", diff --git a/server/startup/migrations/index.ts b/server/startup/migrations/index.ts index dfeec238d17a9..ef9a9a912cd8c 100644 --- a/server/startup/migrations/index.ts +++ b/server/startup/migrations/index.ts @@ -68,4 +68,5 @@ import './v241'; import './v242'; import './v243'; import './v244'; +import './v245'; import './xrun'; diff --git a/server/startup/migrations/v245.ts b/server/startup/migrations/v245.ts new file mode 100644 index 0000000000000..a3efd7bcc279e --- /dev/null +++ b/server/startup/migrations/v245.ts @@ -0,0 +1,10 @@ +import { addMigration } from '../../lib/migrations'; +import { Permissions } from '../../../app/models/server/raw'; + +addMigration({ + version: 245, + up() { + Permissions.create('mobile-download-file', ['user', 'admin']); + Permissions.create('mobile-upload-file', ['user', 'admin']); + }, +}); From f1ce17dd71060a6faecde14e73f047aaad72afd0 Mon Sep 17 00:00:00 2001 From: Aaron Ogle Date: Fri, 19 Nov 2021 18:14:53 -0600 Subject: [PATCH 092/137] allow registering by REG_TOKEN environment variable (#23737) --- app/cloud/server/index.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/app/cloud/server/index.js b/app/cloud/server/index.js index 98ae3b710b963..eb239b095c680 100644 --- a/app/cloud/server/index.js +++ b/app/cloud/server/index.js @@ -6,9 +6,12 @@ import { getWorkspaceAccessToken } from './functions/getWorkspaceAccessToken'; import { getWorkspaceAccessTokenWithScope } from './functions/getWorkspaceAccessTokenWithScope'; import { getWorkspaceLicense } from './functions/getWorkspaceLicense'; import { getUserCloudAccessToken } from './functions/getUserCloudAccessToken'; +import { retrieveRegistrationStatus } from './functions/retrieveRegistrationStatus'; import { getWorkspaceKey } from './functions/getWorkspaceKey'; import { syncWorkspace } from './functions/syncWorkspace'; +import { connectWorkspace } from './functions/connectWorkspace'; import { settings } from '../../settings/server'; +import { SystemLogger } from '../../../server/lib/logger/system'; const licenseCronName = 'Cloud Workspace Sync'; @@ -34,6 +37,22 @@ Meteor.startup(function() { job: syncWorkspace, }); }); + + const { workspaceRegistered } = retrieveRegistrationStatus(); + + if (process.env.REG_TOKEN && process.env.REG_TOKEN !== '' && !workspaceRegistered) { + try { + SystemLogger.info('REG_TOKEN Provided. Attempting to register'); + + if (!connectWorkspace(process.env.REG_TOKEN)) { + throw new Error('Couldn\'t register with token. Please make sure token is valid or hasn\'t already been used'); + } + + console.log('Successfully registered with token provided by REG_TOKEN!'); + } catch (e) { + SystemLogger.error('An error occured registering with token.', e.message); + } + } }); export { getWorkspaceAccessToken, getWorkspaceAccessTokenWithScope, getWorkspaceLicense, getWorkspaceKey, getUserCloudAccessToken }; From 9771ee67f8652e1275b906df325233cf17066425 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 22 Nov 2021 08:23:20 -0600 Subject: [PATCH 093/137] Chore: Type omnichannel models (#23758) Co-authored-by: Guilherme Gazzo --- app/livechat/server/api/lib/departments.js | 2 +- app/livechat/server/api/lib/visitors.js | 2 +- ...ntActivity.js => LivechatAgentActivity.ts} | 29 ++++++++++---- app/models/server/raw/LivechatCustomField.js | 5 --- app/models/server/raw/LivechatCustomField.ts | 6 +++ ...hatDepartment.js => LivechatDepartment.ts} | 18 +++++---- app/models/server/raw/LivechatInquiry.js | 22 ----------- app/models/server/raw/LivechatInquiry.ts | 25 ++++++++++++ app/models/server/raw/LivechatTrigger.js | 5 --- app/models/server/raw/LivechatTrigger.ts | 6 +++ ...ivechatVisitors.js => LivechatVisitors.ts} | 36 +++++++++--------- definition/IInquiry.ts | 30 +++++++++++++++ definition/ILivechatAgentActivity.ts | 15 ++++++++ definition/ILivechatCustomField.ts | 13 +++++++ definition/ILivechatDepartment.ts | 8 ++-- definition/ILivechatDepartmentAgents.ts | 2 + definition/ILivechatDepartmentRecord.ts | 8 ++-- definition/ILivechatTrigger.ts | 30 +++++++++++++++ definition/ILivechatVisitor.ts | 35 +++++++++++++++++ definition/IMessage/IMessage.ts | 11 ++++++ definition/IRoom.ts | 6 +++ definition/rest/v1/omnichannel.ts | 33 ---------------- .../server/business-hour/Custom.ts | 8 ++-- .../rest/v1/omnichannel/cannedResponses.ts | 38 +++++++++++++++++++ ee/definition/rest/v1/omnichannel/index.ts | 3 +- server/modules/watchers/watchers.module.ts | 8 ++-- 26 files changed, 289 insertions(+), 115 deletions(-) rename app/models/server/raw/{LivechatAgentActivity.js => LivechatAgentActivity.ts} (76%) delete mode 100644 app/models/server/raw/LivechatCustomField.js create mode 100644 app/models/server/raw/LivechatCustomField.ts rename app/models/server/raw/{LivechatDepartment.js => LivechatDepartment.ts} (51%) delete mode 100644 app/models/server/raw/LivechatInquiry.js create mode 100644 app/models/server/raw/LivechatInquiry.ts delete mode 100644 app/models/server/raw/LivechatTrigger.js create mode 100644 app/models/server/raw/LivechatTrigger.ts rename app/models/server/raw/{LivechatVisitors.js => LivechatVisitors.ts} (51%) create mode 100644 definition/ILivechatAgentActivity.ts create mode 100644 definition/ILivechatCustomField.ts create mode 100644 definition/ILivechatTrigger.ts create mode 100644 definition/ILivechatVisitor.ts create mode 100644 ee/definition/rest/v1/omnichannel/cannedResponses.ts diff --git a/app/livechat/server/api/lib/departments.js b/app/livechat/server/api/lib/departments.js index 0a70d1b6fca44..1e70a709444f2 100644 --- a/app/livechat/server/api/lib/departments.js +++ b/app/livechat/server/api/lib/departments.js @@ -71,7 +71,7 @@ export async function findDepartmentsToAutocomplete({ uid, selector, onlyMyDepar let { conditions = {} } = selector; const options = { - fields: { + projection: { _id: 1, name: 1, }, diff --git a/app/livechat/server/api/lib/visitors.js b/app/livechat/server/api/lib/visitors.js index c0366bf1ac694..aec58e18db428 100644 --- a/app/livechat/server/api/lib/visitors.js +++ b/app/livechat/server/api/lib/visitors.js @@ -111,7 +111,7 @@ export async function findVisitorsToAutocomplete({ userId, selector }) { const { exceptions = [], conditions = {} } = selector; const options = { - fields: { + projection: { _id: 1, name: 1, username: 1, diff --git a/app/models/server/raw/LivechatAgentActivity.js b/app/models/server/raw/LivechatAgentActivity.ts similarity index 76% rename from app/models/server/raw/LivechatAgentActivity.js rename to app/models/server/raw/LivechatAgentActivity.ts index 9d48cf45a8328..7531dd30c3459 100644 --- a/app/models/server/raw/LivechatAgentActivity.js +++ b/app/models/server/raw/LivechatAgentActivity.ts @@ -1,9 +1,11 @@ import moment from 'moment'; +import { AggregationCursor } from 'mongodb'; import { BaseRaw } from './BaseRaw'; +import { ILivechatAgentActivity } from '../../../../definition/ILivechatAgentActivity'; -export class LivechatAgentActivityRaw extends BaseRaw { - findAllAverageAvailableServiceTime({ date, departmentId }) { +export class LivechatAgentActivityRaw extends BaseRaw { + findAllAverageAvailableServiceTime({ date, departmentId }: { date: Date; departmentId: string }): Promise { const match = { $match: { date } }; const lookup = { $lookup: { @@ -56,7 +58,7 @@ export class LivechatAgentActivityRaw extends BaseRaw { }, }, }; - const params = [match]; + const params = [match] as object[]; if (departmentId && departmentId !== 'undefined') { params.push(lookup); params.push(unwind); @@ -67,7 +69,19 @@ export class LivechatAgentActivityRaw extends BaseRaw { return this.col.aggregate(params).toArray(); } - findAvailableServiceTimeHistory({ start, end, fullReport, onlyCount = false, options = {} }) { + findAvailableServiceTimeHistory({ + start, + end, + fullReport, + onlyCount = false, + options = {}, + }: { + start: string; + end: string; + fullReport: boolean; + onlyCount: boolean; + options: any; + }): AggregationCursor { const match = { $match: { date: { @@ -101,13 +115,12 @@ export class LivechatAgentActivityRaw extends BaseRaw { _id: 0, username: '$_id.username', availableTimeInSeconds: 1, + ...fullReport && { serviceHistory: 1 }, }, }; - if (fullReport) { - project.$project.serviceHistory = 1; - } + const sort = { $sort: options.sort || { username: 1 } }; - const params = [match, lookup, unwind, group, project, sort]; + const params = [match, lookup, unwind, group, project, sort] as object[]; if (onlyCount) { params.push({ $count: 'total' }); return this.col.aggregate(params); diff --git a/app/models/server/raw/LivechatCustomField.js b/app/models/server/raw/LivechatCustomField.js deleted file mode 100644 index 2e3ee77e85a7b..0000000000000 --- a/app/models/server/raw/LivechatCustomField.js +++ /dev/null @@ -1,5 +0,0 @@ -import { BaseRaw } from './BaseRaw'; - -export class LivechatCustomFieldRaw extends BaseRaw { - -} diff --git a/app/models/server/raw/LivechatCustomField.ts b/app/models/server/raw/LivechatCustomField.ts new file mode 100644 index 0000000000000..6ca1ca5b0e238 --- /dev/null +++ b/app/models/server/raw/LivechatCustomField.ts @@ -0,0 +1,6 @@ +import { BaseRaw } from './BaseRaw'; +import { ILivechatCustomField } from '../../../../definition/ILivechatCustomField'; + +export class LivechatCustomFieldRaw extends BaseRaw { + +} diff --git a/app/models/server/raw/LivechatDepartment.js b/app/models/server/raw/LivechatDepartment.ts similarity index 51% rename from app/models/server/raw/LivechatDepartment.js rename to app/models/server/raw/LivechatDepartment.ts index 7915f57724c35..af4da4397da9f 100644 --- a/app/models/server/raw/LivechatDepartment.js +++ b/app/models/server/raw/LivechatDepartment.ts @@ -1,14 +1,16 @@ import { escapeRegExp } from '@rocket.chat/string-helpers'; +import { FindOneOptions, Cursor, FilterQuery, WriteOpResult } from 'mongodb'; import { BaseRaw } from './BaseRaw'; +import { ILivechatDepartmentRecord } from '../../../../definition/ILivechatDepartmentRecord'; -export class LivechatDepartmentRaw extends BaseRaw { - findInIds(departmentsIds, options) { +export class LivechatDepartmentRaw extends BaseRaw { + findInIds(departmentsIds: string[], options: FindOneOptions): Cursor { const query = { _id: { $in: departmentsIds } }; return this.find(query, options); } - findByNameRegexWithExceptionsAndConditions(searchTerm, exceptions = [], conditions = {}, options = {}) { + findByNameRegexWithExceptionsAndConditions(searchTerm: string, exceptions: string[] = [], conditions: FilterQuery = {}, options: FindOneOptions = {}): Cursor { if (!Array.isArray(exceptions)) { exceptions = [exceptions]; } @@ -26,17 +28,17 @@ export class LivechatDepartmentRaw extends BaseRaw { return this.find(query, options); } - findByBusinessHourId(businessHourId, options) { + findByBusinessHourId(businessHourId: string, options: FindOneOptions): Cursor { const query = { businessHourId }; return this.find(query, options); } - findEnabledByBusinessHourId(businessHourId, options) { + findEnabledByBusinessHourId(businessHourId: string, options: FindOneOptions): Cursor { const query = { businessHourId, enabled: true }; return this.find(query, options); } - addBusinessHourToDepartmentsByIds(ids = [], businessHourId) { + addBusinessHourToDepartmentsByIds(ids: string[] = [], businessHourId: string): Promise { const query = { _id: { $in: ids }, }; @@ -50,7 +52,7 @@ export class LivechatDepartmentRaw extends BaseRaw { return this.col.update(query, update, { multi: true }); } - removeBusinessHourFromDepartmentsByIdsAndBusinessHourId(ids = [], businessHourId) { + removeBusinessHourFromDepartmentsByIdsAndBusinessHourId(ids: string[] = [], businessHourId: string): Promise { const query = { _id: { $in: ids }, businessHourId, @@ -65,7 +67,7 @@ export class LivechatDepartmentRaw extends BaseRaw { return this.col.update(query, update, { multi: true }); } - removeBusinessHourFromDepartmentsByBusinessHourId(businessHourId) { + removeBusinessHourFromDepartmentsByBusinessHourId(businessHourId: string): Promise { const query = { businessHourId, }; diff --git a/app/models/server/raw/LivechatInquiry.js b/app/models/server/raw/LivechatInquiry.js deleted file mode 100644 index 5a3f4971786b5..0000000000000 --- a/app/models/server/raw/LivechatInquiry.js +++ /dev/null @@ -1,22 +0,0 @@ -import { BaseRaw } from './BaseRaw'; - -export class LivechatInquiryRaw extends BaseRaw { - findOneQueuedByRoomId(rid) { - const query = { - rid, - status: 'queued', - }; - return this.findOne(query); - } - - findOneByRoomId(rid, options) { - const query = { - rid, - }; - return this.findOne(query, options); - } - - getDistinctQueuedDepartments() { - return this.col.distinct('department', { status: 'queued' }); - } -} diff --git a/app/models/server/raw/LivechatInquiry.ts b/app/models/server/raw/LivechatInquiry.ts new file mode 100644 index 0000000000000..cae073044f89a --- /dev/null +++ b/app/models/server/raw/LivechatInquiry.ts @@ -0,0 +1,25 @@ +import { FindOneOptions, MongoDistinctPreferences } from 'mongodb'; + +import { BaseRaw } from './BaseRaw'; +import { ILivechatInquiryRecord, LivechatInquiryStatus } from '../../../../definition/IInquiry'; + +export class LivechatInquiryRaw extends BaseRaw { + findOneQueuedByRoomId(rid: string): Promise { + const query = { + rid, + status: LivechatInquiryStatus.QUEUED, + }; + return this.findOne(query) as unknown as (Promise<(ILivechatInquiryRecord & { status: LivechatInquiryStatus.QUEUED }) | null>); + } + + findOneByRoomId(rid: string, options: FindOneOptions): Promise { + const query = { + rid, + }; + return this.findOne(query, options); + } + + getDistinctQueuedDepartments(options: MongoDistinctPreferences): Promise { + return this.col.distinct('department', { status: LivechatInquiryStatus.QUEUED }, options); + } +} diff --git a/app/models/server/raw/LivechatTrigger.js b/app/models/server/raw/LivechatTrigger.js deleted file mode 100644 index af9bfbdcce749..0000000000000 --- a/app/models/server/raw/LivechatTrigger.js +++ /dev/null @@ -1,5 +0,0 @@ -import { BaseRaw } from './BaseRaw'; - -export class LivechatTriggerRaw extends BaseRaw { - -} diff --git a/app/models/server/raw/LivechatTrigger.ts b/app/models/server/raw/LivechatTrigger.ts new file mode 100644 index 0000000000000..71035b1db1112 --- /dev/null +++ b/app/models/server/raw/LivechatTrigger.ts @@ -0,0 +1,6 @@ +import { BaseRaw } from './BaseRaw'; +import { ILivechatTrigger } from '../../../../definition/ILivechatTrigger'; + +export class LivechatTriggerRaw extends BaseRaw { + +} diff --git a/app/models/server/raw/LivechatVisitors.js b/app/models/server/raw/LivechatVisitors.ts similarity index 51% rename from app/models/server/raw/LivechatVisitors.js rename to app/models/server/raw/LivechatVisitors.ts index dabd7124625cc..ff0f0ba5319fc 100644 --- a/app/models/server/raw/LivechatVisitors.js +++ b/app/models/server/raw/LivechatVisitors.ts @@ -1,9 +1,11 @@ import { escapeRegExp } from '@rocket.chat/string-helpers'; +import { AggregationCursor, Cursor, FilterQuery, FindOneOptions } from 'mongodb'; import { BaseRaw } from './BaseRaw'; +import { ILivechatVisitor } from '../../../../definition/ILivechatVisitor'; -export class LivechatVisitorsRaw extends BaseRaw { - getVisitorsBetweenDate({ start, end, department }) { +export class LivechatVisitorsRaw extends BaseRaw { + getVisitorsBetweenDate({ start, end, department }: { start: Date; end: Date; department: string }): Cursor { const query = { _updatedAt: { $gte: new Date(start), @@ -12,10 +14,12 @@ export class LivechatVisitorsRaw extends BaseRaw { ...department && department !== 'undefined' && { department }, }; - return this.find(query, { fields: { _id: 1 } }); + return this.find(query, { projection: { _id: 1 } }); } - findByNameRegexWithExceptionsAndConditions(searchTerm, exceptions = [], conditions = {}, options = {}) { + findByNameRegexWithExceptionsAndConditions

    (searchTerm: string, exceptions: string[] = [], conditions: FilterQuery = {}, options: FindOneOptions

    = {}): AggregationCursor

    { if (!Array.isArray(exceptions)) { exceptions = [exceptions]; } @@ -32,24 +36,22 @@ export class LivechatVisitorsRaw extends BaseRaw { }, }; - const { fields, sort, offset, count } = options; + const { projection, sort, skip, limit } = options; const project = { - $project: { + $project: { // TODO: move this logic to client + // eslint-disable-next-line @typescript-eslint/camelcase custom_name: { $concat: ['$username', ' - ', '$name'] }, - ...fields, + ...projection, }, }; const order = { $sort: sort || { name: 1 } }; - const params = [match, project, order]; - - if (offset) { - params.push({ $skip: offset }); - } - - if (count) { - params.push({ $limit: count }); - } + const params: Record[] = [ + match, + order, + skip && { $skip: skip }, + limit && { $limit: limit }, + project].filter(Boolean) as Record[]; return this.col.aggregate(params); } @@ -58,7 +60,7 @@ export class LivechatVisitorsRaw extends BaseRaw { * Find visitors by their email or phone or username or name * @return [{object}] List of Visitors from db */ - findVisitorsByEmailOrPhoneOrNameOrUsername(_emailOrPhoneOrNameOrUsername, options) { + findVisitorsByEmailOrPhoneOrNameOrUsername(_emailOrPhoneOrNameOrUsername: string, options: FindOneOptions): Cursor { const filter = new RegExp(_emailOrPhoneOrNameOrUsername, 'i'); const query = { $or: [{ diff --git a/definition/IInquiry.ts b/definition/IInquiry.ts index 8cc531ef1f49d..d4d259467ae78 100644 --- a/definition/IInquiry.ts +++ b/definition/IInquiry.ts @@ -1,5 +1,35 @@ +import { IRocketChatRecord } from './IRocketChatRecord'; + export interface IInquiry { _id: string; _updatedAt?: Date; department?: string; } + +export enum LivechatInquiryStatus { + QUEUED = 'queued', + TAKEN = 'taken', + READY = 'ready' +} + +export interface IVisitor { + _id: string; + username: string; + token: string; + status: string; +} + +export interface ILivechatInquiryRecord extends IRocketChatRecord { + rid: string; + name: string; + ts: Date; + message: string; + status: LivechatInquiryStatus; + v: IVisitor; + t: 'l'; + queueOrder: number; + estimatedWaitingTimeQueue: number; + estimatedServiceTimeAt: string; + department: string; + estimatedInactivityCloseTimeAt: Date; +} diff --git a/definition/ILivechatAgentActivity.ts b/definition/ILivechatAgentActivity.ts new file mode 100644 index 0000000000000..29abc17c529ec --- /dev/null +++ b/definition/ILivechatAgentActivity.ts @@ -0,0 +1,15 @@ +import { IRocketChatRecord } from './IRocketChatRecord'; + +export interface ILivechatAgentActivity extends IRocketChatRecord { + agentId: string; + date: number; + lastStartedAt: Date; + availableTime: number; + serviceHistory: IServiceHistory[]; + lastStoppedAt?: Date; +} + +export interface IServiceHistory { + startedAt: Date; + stoppedAt: Date; +} diff --git a/definition/ILivechatCustomField.ts b/definition/ILivechatCustomField.ts new file mode 100644 index 0000000000000..b3ba5c6197209 --- /dev/null +++ b/definition/ILivechatCustomField.ts @@ -0,0 +1,13 @@ +import { IRocketChatRecord } from './IRocketChatRecord'; + +export interface ILivechatCustomField extends IRocketChatRecord { + label: string; + scope: 'visitor' | 'room'; + visibility: string; + type?: string; + regexp?: string; + required?: boolean; + defaultValue?: string; + options?: string; + public?: boolean; +} diff --git a/definition/ILivechatDepartment.ts b/definition/ILivechatDepartment.ts index 22c352007e8b8..eb74209c60881 100644 --- a/definition/ILivechatDepartment.ts +++ b/definition/ILivechatDepartment.ts @@ -2,14 +2,16 @@ export interface ILivechatDepartment { _id: string; name: string; enabled: boolean; - description: string; + description?: string; showOnRegistration: boolean; showOnOfflineForm: boolean; - requestTagBeforeClosingChat: boolean; + requestTagBeforeClosingChat?: boolean; email: string; - chatClosingTags: string[]; + chatClosingTags?: string[]; offlineMessageChannelName: string; numAgents: number; _updatedAt?: Date; businessHourId?: string; + // extra optional fields + [k: string]: any; } diff --git a/definition/ILivechatDepartmentAgents.ts b/definition/ILivechatDepartmentAgents.ts index 3c221e1325f89..e33c80ff9245e 100644 --- a/definition/ILivechatDepartmentAgents.ts +++ b/definition/ILivechatDepartmentAgents.ts @@ -4,4 +4,6 @@ export interface ILivechatDepartmentAgents { departmentEnabled: boolean; agentId: string; username: string; + count: number; + order: number; } diff --git a/definition/ILivechatDepartmentRecord.ts b/definition/ILivechatDepartmentRecord.ts index 9b2ff1d5100be..028bec46e5433 100644 --- a/definition/ILivechatDepartmentRecord.ts +++ b/definition/ILivechatDepartmentRecord.ts @@ -5,13 +5,15 @@ export interface ILivechatDepartmentRecord extends IRocketChatRecord { _id: string; name: string; enabled: boolean; - description: string; + description?: string; showOnRegistration: boolean; showOnOfflineForm: boolean; - requestTagBeforeClosingChat: boolean; + requestTagBeforeClosingChat?: boolean; email: string; - chatClosingTags: string[]; + chatClosingTags?: string[]; offlineMessageChannelName: string; numAgents: number; businessHourId?: string; + // extra optional fields + [k: string]: any; } diff --git a/definition/ILivechatTrigger.ts b/definition/ILivechatTrigger.ts new file mode 100644 index 0000000000000..d577bcfee4f4b --- /dev/null +++ b/definition/ILivechatTrigger.ts @@ -0,0 +1,30 @@ +import { IRocketChatRecord } from './IRocketChatRecord'; + +export enum ILivechatTriggerType { + TIME_ON_SITE = 'time-on-site', + PAGE_URL = 'page-url', + CHAT_OPENED_BY_VISITOR = 'chat-opened-by-visitor' +} + +export interface ILivechatTriggerCondition { + name: ILivechatTriggerType; + value?: string | number; +} + +export interface ILivechatTriggerAction { + name: 'send-message'; + params?: { + sender: 'queue' | 'custom'; + msg: string; + name: string; + }; +} + +export interface ILivechatTrigger extends IRocketChatRecord { + name: string; + description: string; + enabled: boolean; + runOnce: boolean; + conditions: ILivechatTriggerCondition[]; + actions: ILivechatTriggerAction[]; +} diff --git a/definition/ILivechatVisitor.ts b/definition/ILivechatVisitor.ts new file mode 100644 index 0000000000000..d3552a627c1e0 --- /dev/null +++ b/definition/ILivechatVisitor.ts @@ -0,0 +1,35 @@ +import { IRocketChatRecord } from './IRocketChatRecord'; + +export interface IVisitorPhone { + phoneNumber: string; +} + +export interface IVisitorLastChat { + _id: string; + ts: string; +} + +export interface ILivechatVisitorConnectionData { + httpHeaders: { + [k: string]: string; + }; + clientAddress: string; +} + +export interface IVisitorEmail { + address: string; +} + +export interface ILivechatVisitor extends IRocketChatRecord { + username: string; + ts: Date; + token: string; + department?: string; + name?: string; + phone?: (IVisitorPhone)[] | null; + lastChat?: IVisitorLastChat; + userAgent?: string; + ip?: string; + host?: string; + visitorEmails?: IVisitorEmail[]; +} diff --git a/definition/IMessage/IMessage.ts b/definition/IMessage/IMessage.ts index 8fa8b66c348df..2bfbace17dc7f 100644 --- a/definition/IMessage/IMessage.ts +++ b/definition/IMessage/IMessage.ts @@ -48,6 +48,7 @@ export interface IMessage extends IRocketChatRecord { channels?: Array; u: Pick; blocks?: MessageSurfaceLayout; + alias?: string; md?: ReturnType; _hidden?: boolean; @@ -73,3 +74,13 @@ export interface IMessage extends IRocketChatRecord { files?: FileProp[]; attachments?: MessageAttachment[]; } + +export type IMessageInbox = IMessage & { + // email inbox fields + email?: { + references?: string[]; + messageId?: string; + }; +} + +export const isIMessageInbox = (message: IMessage): message is IMessageInbox => 'email' in message; diff --git a/definition/IRoom.ts b/definition/IRoom.ts index 04e94c1cb4041..ffb8766c39785 100644 --- a/definition/IRoom.ts +++ b/definition/IRoom.ts @@ -112,6 +112,8 @@ export interface IOmnichannelRoom extends Omit room.t === 'l'; diff --git a/definition/rest/v1/omnichannel.ts b/definition/rest/v1/omnichannel.ts index 80d85f4ba4bf7..a2d43915ab09b 100644 --- a/definition/rest/v1/omnichannel.ts +++ b/definition/rest/v1/omnichannel.ts @@ -1,10 +1,8 @@ import { ILivechatDepartment } from '../../ILivechatDepartment'; import { ILivechatMonitor } from '../../ILivechatMonitor'; import { ILivechatTag } from '../../ILivechatTag'; -import { IOmnichannelCannedResponse } from '../../IOmnichannelCannedResponse'; import { IOmnichannelRoom, IRoom } from '../../IRoom'; import { ISetting } from '../../ISetting'; -import { IUser } from '../../IUser'; export type OmnichannelEndpoints = { 'livechat/appearance': { @@ -116,35 +114,4 @@ export type OmnichannelEndpoints = { total: number; }; }; - 'canned-responses': { - GET: (params: { - shortcut?: string; - text?: string; - scope?: string; - createdBy?: IUser['username']; - tags?: any; - departmentId?: ILivechatDepartment['_id']; - offset?: number; - count?: number; - }) => { - cannedResponses: IOmnichannelCannedResponse[]; - count?: number; - offset?: number; - total: number; - }; - POST: (params: { - _id?: IOmnichannelCannedResponse['_id']; - shortcut: string; - text: string; - scope: string; - tags?: any; - departmentId?: ILivechatDepartment['_id']; - }) => void; - DELETE: (params: { _id: IOmnichannelCannedResponse['_id'] }) => void; - }; - 'canned-responses/:_id': { - GET: () => { - cannedResponse: IOmnichannelCannedResponse; - }; - }; }; diff --git a/ee/app/livechat-enterprise/server/business-hour/Custom.ts b/ee/app/livechat-enterprise/server/business-hour/Custom.ts index b5ae8b18a2cc0..ebd121712cfed 100644 --- a/ee/app/livechat-enterprise/server/business-hour/Custom.ts +++ b/ee/app/livechat-enterprise/server/business-hour/Custom.ts @@ -48,8 +48,8 @@ class CustomBusinessHour extends AbstractBusinessHourType implements IBusinessHo const businessHourToReturn = { ...businessHourData, departmentsToApplyBusinessHour }; delete businessHourData.departments; const businessHourId = await this.baseSaveBusinessHour(businessHourData); - const currentDepartments = (await this.DepartmentsRepository.findByBusinessHourId(businessHourId, { fields: { _id: 1 } }).toArray()).map((dept: any) => dept._id); - const toRemove = [...currentDepartments.filter((dept: string) => !departments.includes(dept))]; + const currentDepartments = (await this.DepartmentsRepository.findByBusinessHourId(businessHourId, { projection: { _id: 1 } }).toArray()).map((dept) => dept._id); + const toRemove = [...currentDepartments.filter((dept) => !departments.includes(dept))]; const toAdd = [...departments.filter((dept: string) => !currentDepartments.includes(dept))]; await this.removeBusinessHourFromDepartmentsIfNeeded(businessHourId, toRemove); await this.addBusinessHourToDepartmentsIfNeeded(businessHourId, toAdd); @@ -69,8 +69,8 @@ class CustomBusinessHour extends AbstractBusinessHourType implements IBusinessHo } private async removeBusinessHourFromAgents(businessHourId: string): Promise { - const departmentIds = (await this.DepartmentsRepository.findByBusinessHourId(businessHourId, { fields: { _id: 1 } }).toArray()).map((dept: any) => dept._id); - const agentIds = (await this.DepartmentsAgentsRepository.findByDepartmentIds(departmentIds, { fields: { agentId: 1 } }).toArray()).map((dept: any) => dept.agentId); + const departmentIds = (await this.DepartmentsRepository.findByBusinessHourId(businessHourId, { projection: { _id: 1 } }).toArray()).map((dept) => dept._id); + const agentIds = (await this.DepartmentsAgentsRepository.findByDepartmentIds(departmentIds, { projection: { agentId: 1 } }).toArray()).map((dept) => dept.agentId); this.UsersRepository.removeBusinessHourByAgentIds(agentIds, businessHourId); } diff --git a/ee/definition/rest/v1/omnichannel/cannedResponses.ts b/ee/definition/rest/v1/omnichannel/cannedResponses.ts new file mode 100644 index 0000000000000..9098604c12d69 --- /dev/null +++ b/ee/definition/rest/v1/omnichannel/cannedResponses.ts @@ -0,0 +1,38 @@ +import { ILivechatDepartment } from '../../../../../definition/ILivechatDepartment'; +import { IOmnichannelCannedResponse } from '../../../../../definition/IOmnichannelCannedResponse'; +import { IUser } from '../../../../../definition/IUser'; + +export type OmnichannelCannedResponsesEndpoints = { + 'canned-responses': { + GET: (params: { + shortcut?: string; + text?: string; + scope?: string; + createdBy?: IUser['username']; + tags?: any; + departmentId?: ILivechatDepartment['_id']; + offset?: number; + count?: number; + }) => { + cannedResponses: IOmnichannelCannedResponse[]; + count?: number; + offset?: number; + total: number; + }; + POST: (params: { + _id?: IOmnichannelCannedResponse['_id']; + shortcut: string; + text: string; + scope: string; + tags?: any; + departmentId?: ILivechatDepartment['_id']; + }) => void; + DELETE: (params: { _id: IOmnichannelCannedResponse['_id'] }) => void; + }; + 'canned-responses/:_id': { + path: `canned-responses/${ string }`; + GET: () => { + cannedResponse: IOmnichannelCannedResponse; + }; + }; +}; diff --git a/ee/definition/rest/v1/omnichannel/index.ts b/ee/definition/rest/v1/omnichannel/index.ts index 36aecf6e9623f..000e315633392 100644 --- a/ee/definition/rest/v1/omnichannel/index.ts +++ b/ee/definition/rest/v1/omnichannel/index.ts @@ -1,4 +1,5 @@ import type { OmnichannelBusinessHoursEndpoints } from './businessHours'; import type { OmnichannelBusinessUnitsEndpoints } from './businessUnits'; +import { OmnichannelCannedResponsesEndpoints } from './cannedResponses'; -export type OmnichannelEndpoints = OmnichannelBusinessHoursEndpoints & OmnichannelBusinessUnitsEndpoints; +export type OmnichannelEndpoints = OmnichannelBusinessHoursEndpoints & OmnichannelBusinessUnitsEndpoints & OmnichannelCannedResponsesEndpoints; diff --git a/server/modules/watchers/watchers.module.ts b/server/modules/watchers/watchers.module.ts index 9ba8b2ada9918..3c9012bb92333 100644 --- a/server/modules/watchers/watchers.module.ts +++ b/server/modules/watchers/watchers.module.ts @@ -16,7 +16,7 @@ import { LivechatInquiryRaw } from '../../../app/models/server/raw/LivechatInqui import { IBaseData } from '../../../definition/IBaseData'; import { IPermission } from '../../../definition/IPermission'; import { ISetting, SettingValue } from '../../../definition/ISetting'; -import { IInquiry } from '../../../definition/IInquiry'; +import { ILivechatInquiryRecord } from '../../../definition/IInquiry'; import { UsersSessionsRaw } from '../../../app/models/server/raw/UsersSessions'; import { IUserSession } from '../../../definition/IUserSession'; import { subscriptionFields, roomFields } from './publishFields'; @@ -209,15 +209,15 @@ export function initWatchers(models: IModelsParam, broadcast: BroadcastCallback, }); } - watch(LivechatInquiry, async ({ clientAction, id, data, diff }) => { + watch(LivechatInquiry, async ({ clientAction, id, data, diff }) => { switch (clientAction) { case 'inserted': case 'updated': - data = data ?? await LivechatInquiry.findOneById(id); + data = data ?? await LivechatInquiry.findOneById(id) ?? undefined; break; case 'removed': - data = await LivechatInquiry.trashFindOneById(id); + data = await LivechatInquiry.trashFindOneById(id) ?? undefined; break; } From 6d7752fd211303e98c2e929096549089ada26eeb Mon Sep 17 00:00:00 2001 From: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> Date: Mon, 22 Nov 2021 20:44:35 +0530 Subject: [PATCH 094/137] [NEW] Audio and Video calling in Livechat using Jitsi and WebRTC (#23004) * [NEW] Livechat WebRTC call settings (#22559) * [NEW] Livechat WebRTC call settings * [FIX] Omnichannel Call Settings - Change Livechat call to Video and Audio Call as it will apply to other omnichannels in future - Change Audio and Video Setting alert to description to be conformant with the other settings - Remove unrelated changes(base.css) that got induced unknowingly - Refactor to remove translation for "Jitsi" and "WebRTC" and remove unnecessary dependency on t - Refactor to add return type of handleClick - Add/remove related i18n labels * [FIX] Livechat videoCall api and method * [FIX] Add migrations for webRTC enabled settings and omnichannel call provider * [FIX] 'Jitsi' typo * [NEW] Join call action button (#22689) * [NEW] WebRTC Call Session * [IMPROVEMENT] Use API endpoint instead of method, fix handleClick dependency warning * [FIX] Return updated callStatus, use translation for join call message, remove system logger, make callStatus optional in room interface * [NEW] Join and End Call Action Message Button for Agent * [FIX] Use translation for Call ALready Ended toastr, convert actionLink to tsx * [IMPROVE] Remove redundant callStatus from message collection, Display Call Ended message with call duration * [REF] Use translation for call ended message, Store danger field in db with other action fields, store actionAlignment field in db * [NEW] update call status (#22854) * add code for update call status * remove fourth param Co-authored-by: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> Co-authored-by: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> * [NEW] P2P WebRTC Connection Establishment (#22847) * [NEW] WebRTC P2P Connection with Basic Call UI * [FIX] Set Stream on a stable connection * [FIX] userId typecheck error * [REFACTOR] - Restore type of userId to string by removing `| undefined` - Add translation for visitor does not exist toastr - Set visitorId from room object fetched instead of fetching from livechat widget as query param - Type Checking * [FIX] Running startCall 2 times for agent * [FIX] Call declined Page * [NEW] Control Buttons - mic, cam, expand and end call (#22928) * [NEW] Control Buttons - mic, cam, expand and end call * [REFACTOR] Add an empty file on EOF en18.i18n that was mistakenly removed * [FIX] UI responsiveness (#22934) * [NEW] Control Buttons - mic, cam, expand and end call * [REFACTOR] Add an empty file on EOF en18.i18n that was mistakenly removed * [FIX] UI Responsiveness * [REF] Use const and ternary op * [FIX] Handle decline message action link (#22936) * [NEW] Control Buttons - mic, cam, expand and end call * [REFACTOR] Add an empty file on EOF en18.i18n that was mistakenly removed * [FIX] UI Responsiveness * [REF] Use const and ternary op * [FIX] Action Link not updating when call declined * [FIX] WebRTC_call_declined_message Co-authored-by: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> * [REF] Use a single IF statement to handle status Co-authored-by: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> * [FIX] Call button visible even when chat is queued (#22943) * [FIX] add callstatus attribute in room object (#22959) * [IMPROVE] Attach jitsi link to message object for Livechat (#22690) * [Improve] Attach jitsi link to message object for Livechat (cherry picked from commit c888961da3313de06eaeb0700b7ce0b6371ef469) * Update WebRTCClass.js * [NEW] Webrtc meet page layout (#22932) * [NEW] Control Buttons - mic, cam, expand and end call * webrtc meet page desgin * [REFACTOR] Add an empty file on EOF en18.i18n that was mistakenly removed * [FIX] UI Responsiveness * [FIX] Action Link not updating when call declined * webrtc meet page desgin * [FIX] Remote user avatar screen and video switching * fix-alert * fix 2 aletrs * improve codebase * make ui responsive * fix issue * Add call timer component + minor refactoring * some css changes Co-authored-by: Dhruv Jain Co-authored-by: murtaza98 Co-authored-by: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> * Add migration * Changing files to tsx * Update default value for setting -Jitsi_Open_New_Window * Update invalid-livechat-config issue * Fix typescript errors * Fix build errors caused by new room prop - webRtcCallStartTime * Simplify call duration calculation logic * Add definition PUT method for REST on client Co-authored-by: Dhruv Jain <51796498+djcruz93@users.noreply.github.com> Co-authored-by: Deepak Agarwal Co-authored-by: Dhruv Jain --- app/livechat/lib/messageTypes.js | 18 + app/livechat/server/api/lib/livechat.js | 18 +- app/livechat/server/api/v1/videoCall.js | 110 ++++- app/livechat/server/api/v1/visitor.js | 24 ++ app/livechat/server/config.ts | 25 +- app/livechat/server/lib/Livechat.js | 8 +- app/livechat/server/methods/getInitialData.js | 2 +- app/models/server/models/Rooms.js | 29 ++ app/models/server/raw/Subscriptions.ts | 13 +- app/notifications/client/lib/Notifications.js | 10 +- app/utils/client/lib/RestApiClient.d.ts | 1 + app/utils/client/lib/RestApiClient.js | 13 + app/videobridge/client/tabBar.tsx | 3 +- .../server/methods/jitsiSetTimeout.js | 11 + app/videobridge/server/settings.ts | 2 +- app/webrtc/client/WebRTCClass.js | 115 ++--- app/webrtc/client/actionLink.tsx | 27 ++ app/webrtc/client/index.js | 2 + app/webrtc/client/tabBar.tsx | 26 ++ app/webrtc/server/settings.ts | 10 + client/components/Message/Actions/Action.tsx | 14 +- client/components/Message/Actions/Actions.tsx | 23 +- client/startup/routes.ts | 23 + client/views/meet/CallPage.tsx | 396 ++++++++++++++++++ client/views/meet/MeetPage.tsx | 159 +++++++ client/views/meet/OngoingCallDuration.tsx | 21 + client/views/meet/styles.css | 41 ++ .../channels/hooks/useTeamsChannelList.ts | 19 +- definition/IRoom.ts | 6 + definition/ISubscription.ts | 2 +- packages/rocketchat-i18n/i18n/en.i18n.json | 19 +- .../notifications/notifications.module.ts | 42 +- server/startup/migrations/index.ts | 1 + server/startup/migrations/v246.ts | 25 ++ 34 files changed, 1144 insertions(+), 114 deletions(-) create mode 100644 app/webrtc/client/actionLink.tsx create mode 100644 app/webrtc/client/tabBar.tsx create mode 100644 client/views/meet/CallPage.tsx create mode 100644 client/views/meet/MeetPage.tsx create mode 100644 client/views/meet/OngoingCallDuration.tsx create mode 100644 client/views/meet/styles.css create mode 100644 server/startup/migrations/v246.ts diff --git a/app/livechat/lib/messageTypes.js b/app/livechat/lib/messageTypes.js index bde52192cbe9d..fb6fa4c10160f 100644 --- a/app/livechat/lib/messageTypes.js +++ b/app/livechat/lib/messageTypes.js @@ -1,4 +1,6 @@ +import formatDistance from 'date-fns/formatDistance'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; +import moment from 'moment'; import { MessageTypes } from '../../ui-utils'; @@ -81,6 +83,22 @@ MessageTypes.registerType({ message: 'New_videocall_request', }); +MessageTypes.registerType({ + id: 'livechat_webrtc_video_call', + render(message) { + if (message.msg === 'ended' && message.webRtcCallEndTs && message.ts) { + return TAPi18n.__('WebRTC_call_ended_message', { + callDuration: formatDistance(new Date(message.webRtcCallEndTs), new Date(message.ts)), + endTime: moment(message.webRtcCallEndTs).format('h:mm A'), + }); + } + if (message.msg === 'declined' && message.webRtcCallEndTs) { + return TAPi18n.__('WebRTC_call_declined_message'); + } + return message.msg; + }, +}); + MessageTypes.registerType({ id: 'omnichannel_placed_chat_on_hold', system: true, diff --git a/app/livechat/server/api/lib/livechat.js b/app/livechat/server/api/lib/livechat.js index 5720112b54638..b374e5139a99a 100644 --- a/app/livechat/server/api/lib/livechat.js +++ b/app/livechat/server/api/lib/livechat.js @@ -1,5 +1,6 @@ import { Meteor } from 'meteor/meteor'; import { Random } from 'meteor/random'; +import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { LivechatRooms, LivechatVisitors, LivechatDepartment, LivechatTrigger } from '../../../../models/server'; import { EmojiCustom } from '../../../../models/server/raw'; @@ -56,6 +57,7 @@ export function findOpenRoom(token, departmentId) { departmentId: 1, servedBy: 1, open: 1, + callStatus: 1, }, }; @@ -101,7 +103,7 @@ export async function settings() { nameFieldRegistrationForm: initSettings.Livechat_name_field_registration_form, emailFieldRegistrationForm: initSettings.Livechat_email_field_registration_form, displayOfflineForm: initSettings.Livechat_display_offline_form, - videoCall: initSettings.Livechat_videocall_enabled === true && initSettings.Jitsi_Enabled === true, + videoCall: initSettings.Omnichannel_call_provider === 'Jitsi' && initSettings.Jitsi_Enabled === true, fileUpload: initSettings.Livechat_fileupload_enabled && initSettings.FileUpload_Enabled, language: initSettings.Language, transcript: initSettings.Livechat_enable_transcript, @@ -117,10 +119,16 @@ export async function settings() { color: initSettings.Livechat_title_color, offlineTitle: initSettings.Livechat_offline_title, offlineColor: initSettings.Livechat_offline_title_color, - actionLinks: [ - { icon: 'icon-videocam', i18nLabel: 'Accept', method_id: 'createLivechatCall', params: '' }, - { icon: 'icon-cancel', i18nLabel: 'Decline', method_id: 'denyLivechatCall', params: '' }, - ], + actionLinks: { + webrtc: [ + { actionLinksAlignment: 'flex-start', i18nLabel: 'Join_call', label: TAPi18n.__('Join_call'), method_id: 'joinLivechatWebRTCCall' }, + { i18nLabel: 'End_call', label: TAPi18n.__('End_call'), method_id: 'endLivechatWebRTCCall', danger: true }, + ], + jitsi: [ + { icon: 'icon-videocam', i18nLabel: 'Accept', method_id: 'createLivechatCall' }, + { icon: 'icon-cancel', i18nLabel: 'Decline', method_id: 'denyLivechatCall' }, + ], + }, }, messages: { offlineMessage: initSettings.Livechat_offline_message, diff --git a/app/livechat/server/api/v1/videoCall.js b/app/livechat/server/api/v1/videoCall.js index 7e30b10eadd73..6aef0c49537e2 100644 --- a/app/livechat/server/api/v1/videoCall.js +++ b/app/livechat/server/api/v1/videoCall.js @@ -1,12 +1,15 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; import { Random } from 'meteor/random'; +import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; -import { Messages } from '../../../../models'; -import { settings as rcSettings } from '../../../../settings'; +import { Messages, Rooms } from '../../../../models'; +import { settings as rcSettings } from '../../../../settings/server'; import { API } from '../../../../api/server'; import { findGuest, getRoom, settings } from '../lib/livechat'; import { OmnichannelSourceType } from '../../../../../definition/IRoom'; +import { hasPermission, canSendMessage } from '../../../../authorization'; +import { Livechat } from '../../lib/Livechat'; API.v1.addRoute('livechat/video.call/:token', { get() { @@ -36,12 +39,12 @@ API.v1.addRoute('livechat/video.call/:token', { }; const { room } = getRoom({ guest, rid, roomInfo }); const config = Promise.await(settings()); - if (!config.theme || !config.theme.actionLinks) { + if (!config.theme || !config.theme.actionLinks || !config.theme.actionLinks.jitsi) { throw new Meteor.Error('invalid-livechat-config'); } Messages.createWithTypeRoomIdMessageAndUser('livechat_video_call', room._id, '', guest, { - actionLinks: config.theme.actionLinks, + actionLinks: config.theme.actionLinks.jitsi, }); let rname; if (rcSettings.get('Jitsi_URL_Room_Hash')) { @@ -63,3 +66,102 @@ API.v1.addRoute('livechat/video.call/:token', { } }, }); + +API.v1.addRoute('livechat/webrtc.call', { authRequired: true }, { + get() { + try { + check(this.queryParams, { + rid: Match.Maybe(String), + }); + + if (!hasPermission(this.userId, 'view-l-room')) { + return API.v1.unauthorized(); + } + + const room = canSendMessage(this.queryParams.rid, { + uid: this.userId, + username: this.user.username, + type: this.user.type, + }); + if (!room) { + throw new Meteor.Error('invalid-room'); + } + + const webrtcCallingAllowed = (rcSettings.get('WebRTC_Enabled') === true) && (rcSettings.get('Omnichannel_call_provider') === 'WebRTC'); + if (!webrtcCallingAllowed) { + throw new Meteor.Error('webRTC calling not enabled'); + } + + const config = Promise.await(settings()); + if (!config.theme || !config.theme.actionLinks || !config.theme.actionLinks.webrtc) { + throw new Meteor.Error('invalid-livechat-config'); + } + + let { callStatus } = room; + + if (!callStatus || callStatus === 'ended' || callStatus === 'declined') { + callStatus = 'ringing'; + Promise.await(Rooms.setCallStatusAndCallStartTime(room._id, callStatus)); + Promise.await(Messages.createWithTypeRoomIdMessageAndUser( + 'livechat_webrtc_video_call', + room._id, + TAPi18n.__('Join_my_room_to_start_the_video_call'), + this.user, + { + actionLinks: config.theme.actionLinks.webrtc, + }, + )); + } + const videoCall = { + rid: room._id, + provider: 'webrtc', + callStatus, + }; + return API.v1.success({ videoCall }); + } catch (e) { + return API.v1.failure(e); + } + }, +}); + +API.v1.addRoute('livechat/webrtc.call/:callId', { authRequired: true }, { + put() { + try { + check(this.urlParams, { + callId: String, + }); + + check(this.bodyParams, { + rid: Match.Maybe(String), + status: Match.Maybe(String), + }); + + const { callId } = this.urlParams; + const { rid, status } = this.bodyParams; + + if (!hasPermission(this.userId, 'view-l-room')) { + return API.v1.unauthorized(); + } + + const room = canSendMessage(rid, { + uid: this.userId, + username: this.user.username, + type: this.user.type, + }); + if (!room) { + throw new Meteor.Error('invalid-room'); + } + + const call = Promise.await(Messages.findOneById(callId)); + if (!call || call.t !== 'livechat_webrtc_video_call') { + throw new Meteor.Error('invalid-callId'); + } + + Livechat.updateCallStatus(callId, rid, status, this.user); + + return API.v1.success({ status }); + } catch (e) { + return API.v1.failure(e); + } + }, +}); diff --git a/app/livechat/server/api/v1/visitor.js b/app/livechat/server/api/v1/visitor.js index 98007540876c5..dc4e012839baa 100644 --- a/app/livechat/server/api/v1/visitor.js +++ b/app/livechat/server/api/v1/visitor.js @@ -128,6 +128,30 @@ API.v1.addRoute('livechat/visitor/:token/room', { authRequired: true }, { }, }); +API.v1.addRoute('livechat/visitor.callStatus', { + post() { + try { + check(this.bodyParams, { + token: String, + callStatus: String, + rid: String, + callId: String, + }); + + const { token, callStatus, rid, callId } = this.bodyParams; + const guest = findGuest(token); + if (!guest) { + throw new Meteor.Error('invalid-token'); + } + const status = callStatus; + Livechat.updateCallStatus(callId, rid, status, guest); + return API.v1.success({ token, callStatus }); + } catch (e) { + return API.v1.failure(e); + } + }, +}); + API.v1.addRoute('livechat/visitor.status', { post() { try { diff --git a/app/livechat/server/config.ts b/app/livechat/server/config.ts index f0a19e3246e65..d8b2d124c15aa 100644 --- a/app/livechat/server/config.ts +++ b/app/livechat/server/config.ts @@ -375,16 +375,6 @@ Meteor.startup(function() { enableQuery: omnichannelEnabledQuery, }); - this.add('Livechat_videocall_enabled', false, { - type: 'boolean', - group: 'Omnichannel', - section: 'Livechat', - public: true, - i18nLabel: 'Videocall_enabled', - i18nDescription: 'Beta_feature_Depends_on_Video_Conference_to_be_enabled', - enableQuery: [{ _id: 'Jitsi_Enabled', value: true }, omnichannelEnabledQuery], - }); - this.add('Livechat_fileupload_enabled', true, { type: 'boolean', group: 'Omnichannel', @@ -616,5 +606,20 @@ Meteor.startup(function() { i18nDescription: 'Time_in_seconds', enableQuery: omnichannelEnabledQuery, }); + + this.add('Omnichannel_call_provider', 'none', { + type: 'select', + public: true, + group: 'Omnichannel', + section: 'Video_and_Audio_Call', + values: [ + { key: 'none', i18nLabel: 'None' }, + { key: 'Jitsi', i18nLabel: 'Jitsi' }, + { key: 'WebRTC', i18nLabel: 'WebRTC' }, + ], + i18nDescription: 'Feature_depends_on_selected_call_provider_to_be_enabled_from_administration_settings', + i18nLabel: 'Call_provider', + enableQuery: omnichannelEnabledQuery, + }); }); }); diff --git a/app/livechat/server/lib/Livechat.js b/app/livechat/server/lib/Livechat.js index 6ccd549152874..78d54cd5f1e61 100644 --- a/app/livechat/server/lib/Livechat.js +++ b/app/livechat/server/lib/Livechat.js @@ -514,7 +514,7 @@ export const Livechat = { 'Livechat_offline_success_message', 'Livechat_offline_form_unavailable', 'Livechat_display_offline_form', - 'Livechat_videocall_enabled', + 'Omnichannel_call_provider', 'Jitsi_Enabled', 'Language', 'Livechat_enable_transcript', @@ -1278,6 +1278,12 @@ export const Livechat = { }; LivechatVisitors.updateById(contactId, updateUser); }, + updateCallStatus(callId, rid, status, user) { + Rooms.setCallStatus(rid, status); + if (status === 'ended' || status === 'declined') { + return updateMessage({ _id: callId, msg: status, actionLinks: [], webRtcCallEndTs: new Date() }, user); + } + }, }; settings.watch('Livechat_history_monitor_type', (value) => { diff --git a/app/livechat/server/methods/getInitialData.js b/app/livechat/server/methods/getInitialData.js index 9b7a2f22a7055..bac76ce8d49a6 100644 --- a/app/livechat/server/methods/getInitialData.js +++ b/app/livechat/server/methods/getInitialData.js @@ -75,7 +75,7 @@ Meteor.methods({ info.offlineUnavailableMessage = initSettings.Livechat_offline_form_unavailable; info.displayOfflineForm = initSettings.Livechat_display_offline_form; info.language = initSettings.Language; - info.videoCall = initSettings.Livechat_videocall_enabled === true && initSettings.Jitsi_Enabled === true; + info.videoCall = initSettings.Omnichannel_call_provider === 'Jitsi' && initSettings.Jitsi_Enabled === true; info.fileUpload = initSettings.Livechat_fileupload_enabled && initSettings.FileUpload_Enabled; info.transcript = initSettings.Livechat_enable_transcript; info.transcriptMessage = initSettings.Livechat_transcript_message; diff --git a/app/models/server/models/Rooms.js b/app/models/server/models/Rooms.js index 460bed352f07f..86076aaa43689 100644 --- a/app/models/server/models/Rooms.js +++ b/app/models/server/models/Rooms.js @@ -58,6 +58,35 @@ export class Rooms extends Base { return this.update(query, update); } + setCallStatus(_id, status) { + const query = { + _id, + }; + + const update = { + $set: { + callStatus: status, + }, + }; + + return this.update(query, update); + } + + setCallStatusAndCallStartTime(_id, status) { + const query = { + _id, + }; + + const update = { + $set: { + callStatus: status, + webRtcCallStartTime: new Date(), + }, + }; + + return this.update(query, update); + } + findByTokenpass(tokens) { const query = { 'tokenpass.tokens.token': { diff --git a/app/models/server/raw/Subscriptions.ts b/app/models/server/raw/Subscriptions.ts index 4a02f61857694..af75749ab8284 100644 --- a/app/models/server/raw/Subscriptions.ts +++ b/app/models/server/raw/Subscriptions.ts @@ -46,7 +46,18 @@ export class SubscriptionsRaw extends BaseRaw { return this.find(query, options); } - countByRoomIdAndUserId(rid: string, uid: string): Promise { + findByLivechatRoomIdAndNotUserId(roomId: string, userId: string, options: FindOneOptions = {}): Cursor { + const query = { + rid: roomId, + 'servedBy._id': { + $ne: userId, + }, + }; + + return this.find(query, options); + } + + countByRoomIdAndUserId(rid: string, uid: string | undefined): Promise { const query = { rid, 'u._id': uid, diff --git a/app/notifications/client/lib/Notifications.js b/app/notifications/client/lib/Notifications.js index 15cd4374fe96b..bf601bb7878d3 100644 --- a/app/notifications/client/lib/Notifications.js +++ b/app/notifications/client/lib/Notifications.js @@ -78,9 +78,9 @@ class Notifications { return this.streamRoom.on(`${ room }/${ eventName }`, callback); } - async onUser(eventName, callback) { - await this.streamUser.on(`${ Meteor.userId() }/${ eventName }`, callback); - return () => this.unUser(eventName, callback); + async onUser(eventName, callback, visitorId = null) { + await this.streamUser.on(`${ Meteor.userId() || visitorId }/${ eventName }`, callback); + return () => this.unUser(eventName, callback, visitorId); } unAll(callback) { @@ -95,8 +95,8 @@ class Notifications { return this.streamRoom.removeListener(`${ room }/${ eventName }`, callback); } - unUser(eventName, callback) { - return this.streamUser.removeListener(`${ Meteor.userId() }/${ eventName }`, callback); + unUser(eventName, callback, visitorId = null) { + return this.streamUser.removeListener(`${ Meteor.userId() || visitorId }/${ eventName }`, callback); } } diff --git a/app/utils/client/lib/RestApiClient.d.ts b/app/utils/client/lib/RestApiClient.d.ts index 452fb2a9c13d7..ecf1f22354f8d 100644 --- a/app/utils/client/lib/RestApiClient.d.ts +++ b/app/utils/client/lib/RestApiClient.d.ts @@ -30,6 +30,7 @@ export declare const APIClient: { delete(endpoint: string, params?: Serialized

    ): Promise>; get(endpoint: string, params?: Serialized

    ): Promise>; post(endpoint: string, params?: Serialized

    , body?: B): Promise>; + put(endpoint: string, params?: Serialized

    , body?: B): Promise>; upload( endpoint: string, params?: Serialized

    , diff --git a/app/utils/client/lib/RestApiClient.js b/app/utils/client/lib/RestApiClient.js index bcec20f9df76c..38038e0142472 100644 --- a/app/utils/client/lib/RestApiClient.js +++ b/app/utils/client/lib/RestApiClient.js @@ -23,6 +23,15 @@ export const APIClient = { return APIClient._jqueryCall('POST', endpoint, params, body); }, + put(endpoint, params, body) { + if (!body) { + body = params; + params = {}; + } + + return APIClient._jqueryCall('PUT', endpoint, params, body); + }, + upload(endpoint, params, formData, xhrOptions) { if (!formData) { formData = params; @@ -169,5 +178,9 @@ export const APIClient = { upload(endpoint, params, formData) { return APIClient.upload(`v1/${ endpoint }`, params, formData); }, + + put(endpoint, params, body) { + return APIClient.put(`v1/${ endpoint }`, params, body); + }, }, }; diff --git a/app/videobridge/client/tabBar.tsx b/app/videobridge/client/tabBar.tsx index d67fc0b88a204..b8ddf4ae6eb07 100644 --- a/app/videobridge/client/tabBar.tsx +++ b/app/videobridge/client/tabBar.tsx @@ -53,12 +53,13 @@ addAction('video', ({ room }) => { const enabledChannel = useSetting('Jitsi_Enable_Channels'); const enabledTeams = useSetting('Jitsi_Enable_Teams'); + const enabledLiveChat = useSetting('Omnichannel_call_provider') === 'Jitsi'; const groups = useStableArray([ 'direct', 'direct_multiple', 'group', - 'live', + enabledLiveChat && 'live', enabledTeams && 'team', enabledChannel && 'channel', ].filter(Boolean) as ToolboxActionConfig['groups']); diff --git a/app/videobridge/server/methods/jitsiSetTimeout.js b/app/videobridge/server/methods/jitsiSetTimeout.js index bfb109efd3220..096304923a340 100644 --- a/app/videobridge/server/methods/jitsiSetTimeout.js +++ b/app/videobridge/server/methods/jitsiSetTimeout.js @@ -7,6 +7,13 @@ import { metrics } from '../../../metrics/server'; import * as CONSTANTS from '../../constants'; import { canSendMessage } from '../../../authorization/server'; import { SystemLogger } from '../../../../server/lib/logger/system'; +import { settings } from '../../../settings'; + +// TODO: Access Token missing. This is just a partial solution, it doesn't handle access token generation logic as present in this file - client/views/room/contextualBar/Call/Jitsi/CallJitsWithData.js +const resolveJitsiCallUrl = (room) => { + const rname = settings.get('Jitsi_URL_Room_Hash') ? settings.get('uniqueID') + room._id : encodeURIComponent(room.t === 'd' ? room.usernames.join(' x ') : room.name); + return `${ settings.get('Jitsi_SSL') ? 'https://' : 'http://' }${ settings.get('Jitsi_Domain') }/${ settings.get('Jitsi_URL_Room_Prefix') }${ rname }${ settings.get('Jitsi_URL_Room_Suffix') }`; +}; Meteor.methods({ 'jitsi:updateTimeout': (rid, joiningNow = true) => { @@ -43,6 +50,10 @@ Meteor.methods({ actionLinks: [ { icon: 'icon-videocam', label: TAPi18n.__('Click_to_join'), i18nLabel: 'Click_to_join', method_id: 'joinJitsiCall', params: '' }, ], + customFields: { + ...room.customFields && { ...room.customFields }, + ...room.t === 'l' && { jitsiCallUrl: resolveJitsiCallUrl(room) }, // Note: this is just a temporary solution for the jitsi calls to work in Livechat. In future we wish to create specific events for specific to livechat calls (eg: start, accept, decline, end, etc) and this url info will be passed via there + }, }); message.msg = TAPi18n.__('Started_a_video_call'); callbacks.run('afterSaveMessage', message, { ...room, jitsiTimeout: currentTime + CONSTANTS.TIMEOUT }); diff --git a/app/videobridge/server/settings.ts b/app/videobridge/server/settings.ts index 09b14ddc9b656..b5cbdace307b2 100644 --- a/app/videobridge/server/settings.ts +++ b/app/videobridge/server/settings.ts @@ -139,7 +139,7 @@ settingsRegistry.addGroup('Video Conference', function() { public: true, }); - this.add('Jitsi_Open_New_Window', false, { + this.add('Jitsi_Open_New_Window', true, { type: 'boolean', enableQuery: { _id: 'Jitsi_Enabled', diff --git a/app/webrtc/client/WebRTCClass.js b/app/webrtc/client/WebRTCClass.js index 1d8485b245d45..66e7a23950b2f 100644 --- a/app/webrtc/client/WebRTCClass.js +++ b/app/webrtc/client/WebRTCClass.js @@ -115,7 +115,7 @@ class WebRTCClass { @param room {String} */ - constructor(selfId, room) { + constructor(selfId, room, autoAccept = false) { this.config = { iceServers: [], }; @@ -145,15 +145,15 @@ class WebRTCClass { this.remoteItems = new ReactiveVar([]); this.remoteItemsById = new ReactiveVar({}); this.callInProgress = new ReactiveVar(false); - this.audioEnabled = new ReactiveVar(true); - this.videoEnabled = new ReactiveVar(true); + this.audioEnabled = new ReactiveVar(false); + this.videoEnabled = new ReactiveVar(false); this.overlayEnabled = new ReactiveVar(false); this.screenShareEnabled = new ReactiveVar(false); this.localUrl = new ReactiveVar(); this.active = false; this.remoteMonitoring = false; this.monitor = false; - this.autoAccept = false; + this.autoAccept = autoAccept; this.navigator = undefined; const userAgent = navigator.userAgent.toLocaleLowerCase(); @@ -169,7 +169,7 @@ class WebRTCClass { this.screenShareAvailable = ['chrome', 'firefox', 'electron'].includes(this.navigator); this.media = { - video: false, + video: true, audio: true, }; this.transport = new this.TransportClass(this); @@ -498,11 +498,12 @@ class WebRTCClass { } const onSuccess = (stream) => { this.localStream = stream; + !this.audioEnabled.get() && this.disableAudio(); + !this.videoEnabled.get() && this.disableVideo(); this.localUrl.set(stream); - this.videoEnabled.set(this.media.video === true); - this.audioEnabled.set(this.media.audio === true); const { peerConnections } = this; Object.entries(peerConnections).forEach(([, peerConnection]) => peerConnection.addStream(stream)); + document.querySelector('video#localVideo').srcObject = stream; callback(null, this.localStream); }; const onError = (error) => { @@ -537,19 +538,10 @@ class WebRTCClass { setAudioEnabled(enabled = true) { if (this.localStream != null) { - if (enabled === true && this.media.audio !== true) { - delete this.localStream; - this.media.audio = true; - this.getLocalUserMedia(() => { - this.stopAllPeerConnections(); - this.joinCall(); - }); - } else { - this.localStream.getAudioTracks().forEach(function(audio) { - audio.enabled = enabled; - }); - this.audioEnabled.set(enabled); - } + this.localStream.getAudioTracks().forEach(function(audio) { + audio.enabled = enabled; + }); + this.audioEnabled.set(enabled); } } @@ -561,21 +553,19 @@ class WebRTCClass { this.setAudioEnabled(true); } + toggleAudio() { + if (this.audioEnabled.get()) { + return this.disableAudio(); + } + return this.enableAudio(); + } + setVideoEnabled(enabled = true) { if (this.localStream != null) { - if (enabled === true && this.media.video !== true) { - delete this.localStream; - this.media.video = true; - this.getLocalUserMedia(() => { - this.stopAllPeerConnections(); - this.joinCall(); - }); - } else { - this.localStream.getVideoTracks().forEach(function(video) { - video.enabled = enabled; - }); - this.videoEnabled.set(enabled); - } + this.localStream.getVideoTracks().forEach(function(video) { + video.enabled = enabled; + }); + this.videoEnabled.set(enabled); } } @@ -610,6 +600,13 @@ class WebRTCClass { this.setVideoEnabled(true); } + toggleVideo() { + if (this.videoEnabled.get()) { + return this.disableVideo(); + } + return this.enableVideo(); + } + stop() { this.active = false; this.monitor = false; @@ -663,7 +660,6 @@ class WebRTCClass { onRemoteCall(data) { if (this.autoAccept === true) { - goToRoomById(data.room); Meteor.defer(() => { this.joinCall({ to: data.from, @@ -735,12 +731,6 @@ class WebRTCClass { */ joinCall(data = {}, ...args) { - if (data.media && data.media.audio) { - this.media.audio = data.media.audio; - } - if (data.media && data.media.video) { - this.media.video = data.media.video; - } data.media = this.media; this.log('joinCall', [data, ...args]); this.getLocalUserMedia(() => { @@ -873,6 +863,7 @@ class WebRTCClass { if (peerConnection.iceConnectionState !== 'closed' && peerConnection.iceConnectionState !== 'failed' && peerConnection.iceConnectionState !== 'disconnected' && peerConnection.iceConnectionState !== 'completed') { peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate)); } + document.querySelector('video#remoteVideo').srcObject = this.remoteItems.get()[0]?.url; } @@ -916,27 +907,41 @@ const WebRTC = new class { this.instancesByRoomId = {}; } - getInstanceByRoomId(rid) { - const subscription = ChatSubscription.findOne({ rid }); - if (!subscription) { - return; - } + getInstanceByRoomId(rid, visitorId = null) { let enabled = false; - switch (subscription.t) { - case 'd': - enabled = settings.get('WebRTC_Enable_Direct'); - break; - case 'p': - enabled = settings.get('WebRTC_Enable_Private'); - break; - case 'c': - enabled = settings.get('WebRTC_Enable_Channel'); + if (!visitorId) { + const subscription = ChatSubscription.findOne({ rid }); + if (!subscription) { + return; + } + switch (subscription.t) { + case 'd': + enabled = settings.get('WebRTC_Enable_Direct'); + break; + case 'p': + enabled = settings.get('WebRTC_Enable_Private'); + break; + case 'c': + enabled = settings.get('WebRTC_Enable_Channel'); + break; + case 'l': + enabled = settings.get('Omnichannel_call_provider') === 'WebRTC'; + } + } else { + enabled = settings.get('Omnichannel_call_provider') === 'WebRTC'; } + enabled = enabled && settings.get('WebRTC_Enabled'); if (enabled === false) { return; } if (this.instancesByRoomId[rid] == null) { - this.instancesByRoomId[rid] = new WebRTCClass(Meteor.userId(), rid); + let uid = Meteor.userId(); + let autoAccept = false; + if (visitorId) { + uid = visitorId; + autoAccept = true; + } + this.instancesByRoomId[rid] = new WebRTCClass(uid, rid, autoAccept); } return this.instancesByRoomId[rid]; } diff --git a/app/webrtc/client/actionLink.tsx b/app/webrtc/client/actionLink.tsx new file mode 100644 index 0000000000000..9d31b54b6cefb --- /dev/null +++ b/app/webrtc/client/actionLink.tsx @@ -0,0 +1,27 @@ +import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; +import toastr from 'toastr'; + +import { actionLinks } from '../../action-links/client'; +import { APIClient } from '../../utils/client'; +import { Rooms } from '../../models/client'; +import { IMessage } from '../../../definition/IMessage'; +import { Notifications } from '../../notifications/client'; + +actionLinks.register('joinLivechatWebRTCCall', (message: IMessage) => { + const { callStatus, _id } = Rooms.findOne({ _id: message.rid }); + if (callStatus === 'declined' || callStatus === 'ended') { + toastr.info(TAPi18n.__('Call_Already_Ended')); + return; + } + window.open(`/meet/${ _id }`, _id); +}); + +actionLinks.register('endLivechatWebRTCCall', async (message: IMessage) => { + const { callStatus, _id } = Rooms.findOne({ _id: message.rid }); + if (callStatus === 'declined' || callStatus === 'ended') { + toastr.info(TAPi18n.__('Call_Already_Ended')); + return; + } + await APIClient.v1.put(`livechat/webrtc.call/${ message._id }`, {}, { rid: _id, status: 'ended' }); + Notifications.notifyRoom(_id, 'webrtc', 'callStatus', { callStatus: 'ended' }); +}); diff --git a/app/webrtc/client/index.js b/app/webrtc/client/index.js index 305227a2f105e..32ad3e5304231 100644 --- a/app/webrtc/client/index.js +++ b/app/webrtc/client/index.js @@ -1,3 +1,5 @@ import './adapter'; +import './tabBar'; +import './actionLink'; export * from './WebRTCClass'; diff --git a/app/webrtc/client/tabBar.tsx b/app/webrtc/client/tabBar.tsx new file mode 100644 index 0000000000000..1ae17abd4bf12 --- /dev/null +++ b/app/webrtc/client/tabBar.tsx @@ -0,0 +1,26 @@ +import { useMemo, useCallback } from 'react'; + +import { useSetting } from '../../../client/contexts/SettingsContext'; +import { addAction } from '../../../client/views/room/lib/Toolbox'; +import { APIClient } from '../../utils/client'; + +addAction('webRTCVideo', ({ room }) => { + const enabled = useSetting('WebRTC_Enabled') && (useSetting('Omnichannel_call_provider') === 'WebRTC') && room.servedBy; + + const handleClick = useCallback(async (): Promise => { + if (!room.callStatus || room.callStatus === 'declined' || room.callStatus === 'ended') { + await APIClient.v1.get('livechat/webrtc.call', { rid: room._id }); + } + window.open(`/meet/${ room._id }`, room._id); + }, [room._id, room.callStatus]); + + return useMemo(() => (enabled ? { + groups: ['live'], + id: 'webRTCVideo', + title: 'WebRTC_Call', + icon: 'phone', + action: handleClick, + full: true, + order: 4, + } : null), [enabled, handleClick]); +}); diff --git a/app/webrtc/server/settings.ts b/app/webrtc/server/settings.ts index b0cad64d4579c..d94a0c8fde7c0 100644 --- a/app/webrtc/server/settings.ts +++ b/app/webrtc/server/settings.ts @@ -1,24 +1,34 @@ import { settingsRegistry } from '../../settings/server'; settingsRegistry.addGroup('WebRTC', function() { + this.add('WebRTC_Enabled', false, { + type: 'boolean', + group: 'WebRTC', + public: true, + i18nLabel: 'Enabled', + }); this.add('WebRTC_Enable_Channel', false, { type: 'boolean', group: 'WebRTC', public: true, + enableQuery: { _id: 'WebRTC_Enabled', value: true }, }); this.add('WebRTC_Enable_Private', false, { type: 'boolean', group: 'WebRTC', public: true, + enableQuery: { _id: 'WebRTC_Enabled', value: true }, }); this.add('WebRTC_Enable_Direct', false, { type: 'boolean', group: 'WebRTC', public: true, + enableQuery: { _id: 'WebRTC_Enabled', value: true }, }); return this.add('WebRTC_Servers', 'stun:stun.l.google.com:19302, stun:23.21.150.121, team%40rocket.chat:demo@turn:numb.viagenie.ca:3478', { type: 'string', group: 'WebRTC', public: true, + enableQuery: { _id: 'WebRTC_Enabled', value: true }, }); }); diff --git a/client/components/Message/Actions/Action.tsx b/client/components/Message/Actions/Action.tsx index d15aec9f17d02..fa635c4bd167e 100644 --- a/client/components/Message/Actions/Action.tsx +++ b/client/components/Message/Actions/Action.tsx @@ -12,6 +12,7 @@ type ActionOptions = { i18nLabel?: TranslationKey; label?: string; runAction?: RunAction; + danger?: boolean; }; const resolveLegacyIcon = (legacyIcon: string | undefined): string | undefined => { @@ -22,13 +23,22 @@ const resolveLegacyIcon = (legacyIcon: string | undefined): string | undefined = return legacyIcon && legacyIcon.replace(/^icon-/, ''); }; -const Action: FC = ({ id, icon, i18nLabel, label, mid, runAction }) => { +const Action: FC = ({ id, icon, i18nLabel, label, mid, runAction, danger }) => { const t = useTranslation(); const resolvedIcon = resolveLegacyIcon(icon); return ( - diff --git a/client/components/Message/Actions/Actions.tsx b/client/components/Message/Actions/Actions.tsx index 64fb6dbf6a2e0..a6e59d496055d 100644 --- a/client/components/Message/Actions/Actions.tsx +++ b/client/components/Message/Actions/Actions.tsx @@ -14,19 +14,24 @@ type ActionOptions = { i18nLabel?: TranslationKey; label?: string; runAction?: RunAction; + actionLinksAlignment?: string; }; const Actions: FC<{ actions: Array; runAction: RunAction; mid: string }> = ({ actions, runAction, -}) => ( - - - {actions.map((action) => ( - - ))} - - -); +}) => { + const alignment = actions[0]?.actionLinksAlignment || 'center'; + + return ( + + + {actions.map((action) => ( + + ))} + + + ); +}; export default Actions; diff --git a/client/startup/routes.ts b/client/startup/routes.ts index 2c17a3bb003d6..69096f1a1aac9 100644 --- a/client/startup/routes.ts +++ b/client/startup/routes.ts @@ -1,10 +1,13 @@ import { FlowRouter } from 'meteor/kadira:flow-router'; import { Meteor } from 'meteor/meteor'; +import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { Session } from 'meteor/session'; import { Tracker } from 'meteor/tracker'; import { lazy } from 'react'; +import toastr from 'toastr'; import { KonchatNotification } from '../../app/ui/client'; +import { APIClient } from '../../app/utils/client'; import { IUser } from '../../definition/IUser'; import { appLayout } from '../lib/appLayout'; import { createTemplateForComponent } from '../lib/portals/createTemplateForComponent'; @@ -14,6 +17,7 @@ import { handleError } from '../lib/utils/handleError'; const SetupWizardRoute = lazy(() => import('../views/setupWizard/SetupWizardRoute')); const MailerUnsubscriptionPage = lazy(() => import('../views/mailer/MailerUnsubscriptionPage')); const NotFoundPage = lazy(() => import('../views/notFound/NotFoundPage')); +const MeetPage = lazy(() => import('../views/meet/MeetPage')); FlowRouter.wait(); @@ -50,6 +54,25 @@ FlowRouter.route('/login', { }, }); +FlowRouter.route('/meet/:rid', { + name: 'meet', + + async action(_params, queryParams) { + if (queryParams?.token !== undefined) { + // visitor login + const visitor = await APIClient.v1.get(`/livechat/visitor/${queryParams?.token}`); + if (visitor?.visitor) { + return appLayout.render({ component: MeetPage }); + } + return toastr.error(TAPi18n.__('Visitor_does_not_exist')); + } + if (!Meteor.userId()) { + FlowRouter.go('home'); + } + appLayout.render({ component: MeetPage }); + }, +}); + FlowRouter.route('/home', { name: 'home', diff --git a/client/views/meet/CallPage.tsx b/client/views/meet/CallPage.tsx new file mode 100644 index 0000000000000..4f5283c6def59 --- /dev/null +++ b/client/views/meet/CallPage.tsx @@ -0,0 +1,396 @@ +import { Box, Flex, ButtonGroup, Button, Icon } from '@rocket.chat/fuselage'; +import moment from 'moment'; +import React, { FC, useEffect, useState } from 'react'; + +import { Notifications } from '../../../app/notifications/client'; +import { WebRTC } from '../../../app/webrtc/client'; +import { WEB_RTC_EVENTS } from '../../../app/webrtc/index'; +import UserAvatar from '../../components/avatar/UserAvatar'; +import { useTranslation } from '../../contexts/TranslationContext'; +import OngoingCallDuration from './OngoingCallDuration'; +import './styles.css'; + +type CallPageProps = { + roomId: any; + visitorToken: any; + visitorId: any; + status: any; + setStatus: any; + layout: any; + visitorName: any; + agentName: any; + callStartTime: any; +}; + +const CallPage: FC = ({ + roomId, + visitorToken, + visitorId, + status, + setStatus, + layout, + visitorName, + agentName, + callStartTime, +}) => { + const [isAgentActive, setIsAgentActive] = useState(false); + const [isMicOn, setIsMicOn] = useState(false); + const [isCameraOn, setIsCameraOn] = useState(false); + const [isRemoteMobileDevice, setIsRemoteMobileDevice] = useState(false); + const [callInIframe, setCallInIframe] = useState(false); + const [isRemoteCameraOn, setIsRemoteCameraOn] = useState(false); + const [isLocalMobileDevice, setIsLocalMobileDevice] = useState(false); + + let iconSize = 'x21'; + let buttonSize = 'x40'; + const avatarSize = 'x48'; + if (layout === 'embedded') { + iconSize = 'x19'; + buttonSize = 'x35'; + } + + const t = useTranslation(); + useEffect(() => { + if (visitorToken) { + const webrtcInstance = WebRTC.getInstanceByRoomId(roomId, visitorId); + const isMobileDevice = (): boolean => { + if (layout === 'embedded') { + setCallInIframe(true); + } + if (window.innerWidth <= 450 && window.innerHeight >= 629 && window.innerHeight <= 900) { + setIsLocalMobileDevice(true); + webrtcInstance.media = { + audio: true, + video: { + width: { ideal: 440 }, + height: { ideal: 580 }, + }, + }; + return true; + } + return false; + }; + Notifications.onUser( + WEB_RTC_EVENTS.WEB_RTC, + (type: any, data: any) => { + if (data.room == null) { + return; + } + webrtcInstance.onUserStream(type, data); + }, + visitorId, + ); + Notifications.onRoom(roomId, 'webrtc', (type: any, data: any) => { + if (type === 'callStatus' && data.callStatus === 'ended') { + webrtcInstance.stop(); + setStatus(data.callStatus); + } else if (type === 'getDeviceType') { + Notifications.notifyRoom(roomId, 'webrtc', 'deviceType', { + isMobileDevice: isMobileDevice(), + }); + } else if (type === 'cameraStatus') { + setIsRemoteCameraOn(data.isCameraOn); + } + }); + Notifications.notifyRoom(roomId, 'webrtc', 'deviceType', { + isMobileDevice: isMobileDevice(), + }); + Notifications.notifyRoom(roomId, 'webrtc', 'callStatus', { callStatus: 'inProgress' }); + } else if (!isAgentActive) { + const webrtcInstance = WebRTC.getInstanceByRoomId(roomId); + if (status === 'inProgress') { + Notifications.notifyRoom(roomId, 'webrtc', 'getDeviceType'); + webrtcInstance.startCall({ + audio: true, + video: { + width: { ideal: 1920 }, + height: { ideal: 1080 }, + }, + }); + } + Notifications.onRoom(roomId, 'webrtc', (type: any, data: any) => { + if (type === 'callStatus') { + switch (data.callStatus) { + case 'ended': + webrtcInstance.stop(); + break; + case 'inProgress': + webrtcInstance.startCall({ + audio: true, + video: { + width: { ideal: 1920 }, + height: { ideal: 1080 }, + }, + }); + } + setStatus(data.callStatus); + } else if (type === 'deviceType' && data.isMobileDevice) { + setIsRemoteMobileDevice(true); + } else if (type === 'cameraStatus') { + setIsRemoteCameraOn(data.isCameraOn); + } + }); + setIsAgentActive(true); + } + }, [isAgentActive, status, setStatus, visitorId, roomId, visitorToken, layout]); + + const toggleButton = (control: any): any => { + if (control === 'mic') { + WebRTC.getInstanceByRoomId(roomId, visitorToken).toggleAudio(); + return setIsMicOn(!isMicOn); + } + WebRTC.getInstanceByRoomId(roomId, visitorToken).toggleVideo(); + setIsCameraOn(!isCameraOn); + Notifications.notifyRoom(roomId, 'webrtc', 'cameraStatus', { isCameraOn: !isCameraOn }); + }; + + const closeWindow = (): void => { + if (layout === 'embedded') { + return (parent as any)?.handleIframeClose(); + } + return window.close(); + }; + + const getCallDuration = (callStartTime: any): any => + moment.duration(moment(new Date()).diff(moment(callStartTime))).asSeconds(); + + const showCallPage = (localAvatar: any, remoteAvatar: any): any => ( + + + + + + + + + + {layout === 'embedded' && ( + + )} + + + + + + + + + + {remoteAvatar} + + + + + ); + + return ( + <> + {status === 'ringing' && ( + + + + + + + + + {'Calling...'} + + + {visitorName} + + + + + )} + {status === 'declined' && ( + + {t('Call_declined')} + + )} + {status === 'inProgress' && ( + + {visitorToken + ? showCallPage(visitorName, agentName) + : showCallPage(agentName, visitorName)} + + )} + + ); +}; + +export default CallPage; diff --git a/client/views/meet/MeetPage.tsx b/client/views/meet/MeetPage.tsx new file mode 100644 index 0000000000000..f0e570d3bf9a7 --- /dev/null +++ b/client/views/meet/MeetPage.tsx @@ -0,0 +1,159 @@ +import { Button, Box, Icon, Flex } from '@rocket.chat/fuselage'; +import { Meteor } from 'meteor/meteor'; +import React, { useEffect, useState, useCallback, FC } from 'react'; + +import { APIClient } from '../../../app/utils/client'; +import UserAvatar from '../../components/avatar/UserAvatar'; +import { useRouteParameter, useQueryStringParameter } from '../../contexts/RouterContext'; +import NotFoundPage from '../notFound/NotFoundPage'; +import PageLoading from '../root/PageLoading'; +import CallPage from './CallPage'; +import './styles.css'; + +const MeetPage: FC = () => { + const [isRoomMember, setIsRoomMember] = useState(false); + const [status, setStatus] = useState(null); + const [visitorId, setVisitorId] = useState(null); + const roomId = useRouteParameter('rid'); + const visitorToken = useQueryStringParameter('token'); + const layout = useQueryStringParameter('layout'); + const [visitorName, setVisitorName] = useState(''); + const [agentName, setAgentName] = useState(''); + const [callStartTime, setCallStartTime] = useState(undefined); + + const isMobileDevice = (): boolean => window.innerWidth <= 450; + const closeCallTab = (): void => window.close(); + + const setupCallForVisitor = useCallback(async () => { + const room = await APIClient.v1.get(`/livechat/room?token=${visitorToken}&rid=${roomId}`); + if (room?.room?.v?.token === visitorToken) { + setVisitorId(room.room.v._id); + setVisitorName(room.room.fname); + room?.room?.responseBy?.username + ? setAgentName(room.room.responseBy.username) + : setAgentName(room.room.servedBy.username); + setStatus(room?.room?.callStatus || 'ended'); + setCallStartTime(room.room.webRtcCallStartTime); + return setIsRoomMember(true); + } + }, [visitorToken, roomId]); + + const setupCallForAgent = useCallback(async () => { + const room = await APIClient.v1.get(`/rooms.info?roomId=${roomId}`); + if (room?.room?.servedBy?._id === Meteor.userId()) { + setVisitorName(room.room.fname); + room?.room?.responseBy?.username + ? setAgentName(room.room.responseBy.username) + : setAgentName(room.room.servedBy.username); + setStatus(room?.room?.callStatus || 'ended'); + setCallStartTime(room.room.webRtcCallStartTime); + return setIsRoomMember(true); + } + }, [roomId]); + + useEffect(() => { + if (visitorToken) { + setupCallForVisitor(); + return; + } + setupCallForAgent(); + }, [setupCallForAgent, setupCallForVisitor, visitorToken]); + if (status === null) { + return ; + } + if (!isRoomMember) { + return ; + } + if (status === 'ended') { + return ( + + + + + + + +

    {'Call Ended!'}

    +

    + {visitorToken ? agentName : visitorName} +

    + + + + + + + ); + } + + return ( + + ); +}; + +export default MeetPage; diff --git a/client/views/meet/OngoingCallDuration.tsx b/client/views/meet/OngoingCallDuration.tsx new file mode 100644 index 0000000000000..6bcf61036bae5 --- /dev/null +++ b/client/views/meet/OngoingCallDuration.tsx @@ -0,0 +1,21 @@ +import { Box } from '@rocket.chat/fuselage'; +import React, { FC, useEffect, useState } from 'react'; + +type OngoingCallDurationProps = { + counter: number; +}; + +const OngoingCallDuration: FC = ({ counter: defaultCounter = 0 }) => { + const [counter, setCounter] = useState(defaultCounter); + useEffect(() => { + setTimeout(() => setCounter(counter + 1), 1000); + }, [counter]); + + return ( + + {new Date(counter * 1000).toISOString().substr(11, 8)} + + ); +}; + +export default OngoingCallDuration; diff --git a/client/views/meet/styles.css b/client/views/meet/styles.css new file mode 100644 index 0000000000000..799fd59faec80 --- /dev/null +++ b/client/views/meet/styles.css @@ -0,0 +1,41 @@ +.Off { + color: #ffffff !important; + border-color: #2f343d !important; + background-color: #2f343d !important; +} + +.On { + color: #000000 !important; + border-color: #ffffff !important; + background-color: #ffffff !important; +} + +.Self_Video { + display: flex; + + width: 15%; + height: 17.5%; + + justify-content: center; +} + +@media (max-width: 900px) and (min-height: 500px) { + .Self_Video { + width: 30%; + height: 20%; + } +} + +@media (max-width: 900px) and (max-height: 500px) { + .Self_Video { + width: 30%; + height: 35%; + } +} + +@media (min-width: 901px) and (max-width: 1300px) and (max-height: 500px) { + .Self_Video { + width: 20%; + height: 40%; + } +} diff --git a/client/views/teams/contextualBar/channels/hooks/useTeamsChannelList.ts b/client/views/teams/contextualBar/channels/hooks/useTeamsChannelList.ts index c310c75b73ad7..b6b01f96953c9 100644 --- a/client/views/teams/contextualBar/channels/hooks/useTeamsChannelList.ts +++ b/client/views/teams/contextualBar/channels/hooks/useTeamsChannelList.ts @@ -41,14 +41,17 @@ export const useTeamsChannelList = ( }); return { - items: rooms.map(({ _updatedAt, lastMessage, lm, ts, jitsiTimeout, ...room }) => ({ - jitsiTimeout: new Date(jitsiTimeout), - ...(lm && { lm: new Date(lm) }), - ...(ts && { ts: new Date(ts) }), - _updatedAt: new Date(_updatedAt), - ...(lastMessage && { lastMessage: mapMessageFromApi(lastMessage) }), - ...room, - })), + items: rooms.map( + ({ _updatedAt, lastMessage, lm, ts, jitsiTimeout, webRtcCallStartTime, ...room }) => ({ + jitsiTimeout: new Date(jitsiTimeout), + ...(lm && { lm: new Date(lm) }), + ...(ts && { ts: new Date(ts) }), + _updatedAt: new Date(_updatedAt), + ...(lastMessage && { lastMessage: mapMessageFromApi(lastMessage) }), + ...(webRtcCallStartTime && { webRtcCallStartTime: new Date(webRtcCallStartTime) }), + ...room, + }), + ), itemCount: total, }; }, diff --git a/definition/IRoom.ts b/definition/IRoom.ts index ffb8766c39785..f070c35f14d7e 100644 --- a/definition/IRoom.ts +++ b/definition/IRoom.ts @@ -3,6 +3,7 @@ import { IMessage } from './IMessage'; import { IUser, Username } from './IUser'; type RoomType = 'c' | 'd' | 'p' | 'l'; +type CallStatus = 'ringing' | 'ended' | 'declined' | 'ongoing'; export type RoomID = string; export type ChannelName = string; @@ -32,6 +33,11 @@ export interface IRoom extends IRocketChatRecord { lm?: Date; usersCount: number; jitsiTimeout: Date; + callStatus?: CallStatus; + webRtcCallStartTime?: Date; + servedBy?: { + _id: string; + }; streamingOptions?: { id?: string; diff --git a/definition/ISubscription.ts b/definition/ISubscription.ts index 1761791b2fed1..cceb56c563837 100644 --- a/definition/ISubscription.ts +++ b/definition/ISubscription.ts @@ -7,6 +7,7 @@ type RoomID = string; export interface ISubscription extends IRocketChatRecord { u: Pick; + v?: Pick; rid: RoomID; open: boolean; ts: Date; @@ -57,7 +58,6 @@ export interface ISubscription extends IRocketChatRecord { ignored?: unknown; department?: unknown; - v?: unknown; } export interface IOmnichannelSubscription extends ISubscription { diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 84a53aec4bfb5..32c282b5728a7 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -701,6 +701,9 @@ "By_author": "By __author__", "cache_cleared": "Cache cleared", "Call": "Call", + "Call_declined": "Call Declined!", + "Call_provider": "Call Provider", + "Call_Already_Ended": "Call Already Ended", "call-management": "Call Management", "call-management_description": "Permission to start a meeting", "Caller": "Caller", @@ -1609,6 +1612,8 @@ "Encryption_key_saved_successfully": "Your encryption key was saved successfully.", "EncryptionKey_Change_Disabled": "You can't set a password for your encryption key because your private key is not present on this client. In order to set a new password you need load your private key using your existing password or use a client where the key is already loaded.", "End": "End", + "End_call": "End call", + "Expand_view": "Expand view", "End_OTR": "End OTR", "Engagement_Dashboard": "Engagement Dashboard", "Enter": "Enter", @@ -1836,6 +1841,7 @@ "Favorite": "Favorite", "Favorite_Rooms": "Enable Favorite Rooms", "Favorites": "Favorites", + "Feature_depends_on_selected_call_provider_to_be_enabled_from_administration_settings": "This feature depends on the above selected call provider to be enabled from the administration settings.", "Feature_Depends_on_Livechat_Visitor_navigation_as_a_message_to_be_enabled": "This feature depends on \"Send Visitor Navigation History as a Message\" to be enabled.", "Features": "Features", "Features_Enabled": "Features Enabled", @@ -2339,12 +2345,14 @@ "Jitsi_Limit_Token_To_Room": "Limit token to Jitsi Room", "Job_Title": "Job Title", "join": "Join", + "Join_call": "Join Call", "Join_audio_call": "Join audio call", "Join_Chat": "Join Chat", "Join_default_channels": "Join default channels", "Join_the_Community": "Join the Community", "Join_the_given_channel": "Join the given channel", "Join_video_call": "Join video call", + "Join_my_room_to_start_the_video_call": "Join my room to start the video call", "join-without-join-code": "Join Without Join Code", "join-without-join-code_description": "Permission to bypass the join code in channels with join code enabled", "Joined": "Joined", @@ -2669,6 +2677,7 @@ "Livechat_Triggers": "Livechat Triggers", "Livechat_user_sent_chat_transcript_to_visitor": "__agent__ sent the chat transcript to __guest__", "Livechat_Users": "Omnichannel Users", + "Livechat_Calls": "Livechat Calls", "Livechat_visitor_email_and_transcript_email_do_not_match": "Visitor's email and transcript's email do not match", "Livechat_visitor_transcript_request": "__guest__ requested the chat transcript", "LiveStream & Broadcasting": "LiveStream & Broadcasting", @@ -3011,6 +3020,7 @@ "Mute_Group_Mentions": "Mute @all and @here mentions", "Mute_someone_in_room": "Mute someone in the room", "Mute_user": "Mute user", + "Mute_microphone": "Mute Microphone", "mute-user": "Mute User", "mute-user_description": "Permission to mute other users in the same channel", "Muted": "Muted", @@ -4287,6 +4297,8 @@ "Tuesday": "Tuesday", "Turn_OFF": "Turn OFF", "Turn_ON": "Turn ON", + "Turn_on_video": "Turn on video", + "Turn_off_video": "Turn off video", "Two Factor Authentication": "Two Factor Authentication", "Two-factor_authentication": "Two-factor authentication via TOTP", "Two-factor_authentication_disabled": "Two-factor authentication disabled", @@ -4348,6 +4360,7 @@ "Unread_Rooms_Mode": "Unread Rooms Mode", "Unread_Tray_Icon_Alert": "Unread Tray Icon Alert", "Unstar_Message": "Remove Star", + "Unmute_microphone": "Unmute Microphone", "Update": "Update", "Update_EnableChecker": "Enable the Update Checker", "Update_EnableChecker_Description": "Checks automatically for new updates / important messages from the Rocket.Chat developers and receives notifications when available. The notification appears once per new version as a clickable banner and as a message from the Rocket.Cat bot, both visible only for administrators.", @@ -4529,7 +4542,7 @@ "Video_Conference": "Video Conference", "Video_message": "Video message", "Videocall_declined": "Video Call Declined.", - "Videocall_enabled": "Video Call Enabled", + "Video_and_Audio_Call": "Video and Audio Call", "Videos": "Videos", "View_All": "View All Members", "View_channels": "View Channels", @@ -4608,6 +4621,7 @@ "Visitor_message": "Visitor Messages", "Visitor_Name": "Visitor Name", "Visitor_Name_Placeholder": "Please enter a visitor name...", + "Visitor_does_not_exist": "Visitor does not exist!", "Visitor_Navigation": "Visitor Navigation", "Visitor_page_URL": "Visitor page URL", "Visitor_time_on_site": "Visitor time on site", @@ -4635,6 +4649,7 @@ "Webhook_Details": "WebHook Details", "Webhook_URL": "Webhook URL", "Webhooks": "Webhooks", + "WebRTC_Call": "WebRTC Call", "WebRTC_direct_audio_call_from_%s": "Direct audio call from %s", "WebRTC_direct_video_call_from_%s": "Direct video call from %s", "WebRTC_Enable_Channel": "Enable for Public Channels", @@ -4645,6 +4660,8 @@ "WebRTC_monitor_call_from_%s": "Monitor call from %s", "WebRTC_Servers": "STUN/TURN Servers", "WebRTC_Servers_Description": "A list of STUN and TURN servers separated by comma.
    Username, password and port are allowed in the format `username:password@stun:host:port` or `username:password@turn:host:port`.", + "WebRTC_call_ended_message": " Call ended at __endTime__ - Lasted __callDuration__", + "WebRTC_call_declined_message": " Call Declined by Contact.", "Website": "Website", "Wednesday": "Wednesday", "Weekly_Active_Users": "Weekly Active Users", diff --git a/server/modules/notifications/notifications.module.ts b/server/modules/notifications/notifications.module.ts index bd057b9304f2e..ba11ab8db5a08 100644 --- a/server/modules/notifications/notifications.module.ts +++ b/server/modules/notifications/notifications.module.ts @@ -169,7 +169,11 @@ export class NotificationsModule { this.streamRoom.allowRead(async function(eventName, extraData): Promise { - const [rid] = eventName.split('/'); + const [rid, e] = eventName.split('/'); + + if (e === 'webrtc') { + return true; + } // typing from livechat widget if (extraData?.token) { @@ -214,7 +218,7 @@ export class NotificationsModule { return user[key] === username; } catch (e) { - SystemLogger.error(e); + SystemLogger.error('Error: ', e); return false; } } @@ -251,21 +255,41 @@ export class NotificationsModule { this.streamRoomUsers.allowRead('none'); this.streamRoomUsers.allowWrite(async function(eventName, ...args) { - if (!this.userId) { - return false; - } - const [roomId, e] = eventName.split('/'); - if (await Subscriptions.countByRoomIdAndUserId(roomId, this.userId) > 0) { + if (!this.userId) { + const room = await Rooms.findOneById(roomId, { projection: { t: 1, 'servedBy._id': 1 } }); + if (room && room.t === 'l' && e === 'webrtc' && room.servedBy) { + notifyUser(room.servedBy._id, e, ...args); + return false; + } + } else if (await Subscriptions.countByRoomIdAndUserId(roomId, this.userId) > 0) { + const livechatSubscriptions: ISubscription[] = await Subscriptions.findByLivechatRoomIdAndNotUserId(roomId, this.userId, { projection: { 'v._id': 1, _id: 0 } }).toArray(); + if (livechatSubscriptions && e === 'webrtc') { + livechatSubscriptions.forEach((subscription) => subscription.v && notifyUser(subscription.v._id, e, ...args)); + return false; + } const subscriptions: ISubscription[] = await Subscriptions.findByRoomIdAndNotUserId(roomId, this.userId, { projection: { 'u._id': 1, _id: 0 } }).toArray(); subscriptions.forEach((subscription) => notifyUser(subscription.u._id, e, ...args)); } return false; }); - this.streamUser.allowWrite('logged'); + this.streamUser.allowWrite(async function(eventName) { + const [userId, e] = eventName.split('/'); + + if (e === 'webrtc') { + return true; + } + + return (this.userId != null) && (this.userId === userId); + }); this.streamUser.allowRead(async function(eventName) { - const [userId] = eventName.split('/'); + const [userId, e] = eventName.split('/'); + + if (e === 'webrtc') { + return true; + } + return (this.userId != null) && this.userId === userId; }); diff --git a/server/startup/migrations/index.ts b/server/startup/migrations/index.ts index ef9a9a912cd8c..e8159aad4db8f 100644 --- a/server/startup/migrations/index.ts +++ b/server/startup/migrations/index.ts @@ -69,4 +69,5 @@ import './v242'; import './v243'; import './v244'; import './v245'; +import './v246'; import './xrun'; diff --git a/server/startup/migrations/v246.ts b/server/startup/migrations/v246.ts new file mode 100644 index 0000000000000..103ab198f2262 --- /dev/null +++ b/server/startup/migrations/v246.ts @@ -0,0 +1,25 @@ +import { addMigration } from '../../lib/migrations'; +import { Settings } from '../../../app/models/server'; +import { settings } from '../../../app/settings/server'; + +addMigration({ + version: 246, + up() { + const livechatVideoCallEnabled = settings.get('Livechat_videocall_enabled'); + if (livechatVideoCallEnabled) { + Settings.upsert({ _id: 'Omnichannel_call_provider' }, { + $set: { value: 'Jitsi' }, + }); + } + Settings.removeById('Livechat_videocall_enabled'); + + const webRTCEnableChannel = settings.get('WebRTC_Enable_Channel'); + const webRTCEnableDirect = settings.get('WebRTC_Enable_Direct'); + const webRTCEnablePrivate = settings.get('WebRTC_Enable_Private'); + if (webRTCEnableChannel || webRTCEnableDirect || webRTCEnablePrivate) { + Settings.upsert({ _id: 'WebRTC_Enabled' }, { + $set: { value: true }, + }); + } + }, +}); From 4c9c2e657fdbf8f42ac88e91200e4068df1da67e Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Mon, 22 Nov 2021 13:24:59 -0300 Subject: [PATCH 095/137] [IMPROVE] Engagement Dashboard (#23547) Co-authored-by: matheusbsilva137 --- .eslintignore | 4 +- .storybook/.eslintrc.js | 1 + .storybook/.prettierrc | 1 + .storybook/decorators.tsx | 64 +- .storybook/hooks/index.ts | 1 + .../{hooks.ts => hooks/useAutoToggle.ts} | 0 .storybook/main.js | 43 +- .storybook/mocks/meteor.js | 27 +- .../providers/QueryClientProviderMock.tsx | 20 + .../mocks/providers/ServerProviderMock.tsx | 96 +++ .../{providers.tsx => providers/index.tsx} | 27 +- .storybook/preview.ts | 3 +- app/api/server/v1/banners.ts | 1 - app/api/server/v1/teams.ts | 85 +- app/models/server/raw/Analytics.ts | 16 +- app/models/server/raw/Rooms.js | 6 +- app/models/server/raw/Sessions.ts | 60 +- app/statistics/server/lib/SAUMonitor.js | 2 +- app/utils/client/lib/RestApiClient.d.ts | 2 +- client/.eslintrc.js | 9 + client/lib/createRouteGroup.ts | 131 ++- client/lib/createSidebarItems.ts | 3 + client/lib/download.ts | 2 +- client/lib/queryClient.ts | 3 + client/sidebar/header/UserDropdown.js | 1 + client/views/admin/index.ts | 5 +- client/views/root/AppRoot.tsx | 12 +- definition/IUser.ts | 8 - .../externals/meteor/kadira-flow-router.d.ts | 18 +- definition/rest/.eslintrc.js | 1 + definition/rest/.prettierrc | 1 + .../rest/helpers/ReplacePlaceholders.ts | 15 +- definition/rest/index.ts | 100 ++- definition/rest/v1/banners.ts | 12 +- definition/rest/v1/dm.ts | 8 +- definition/rest/v1/emojiCustom.ts | 4 +- definition/rest/v1/im.ts | 8 +- definition/rest/v1/instances.ts | 23 +- definition/rest/v1/ldap.ts | 4 +- definition/rest/v1/permissions.ts | 8 +- definition/rest/v1/roles.ts | 38 +- definition/rest/v1/settings.ts | 36 +- .../v1/teams/TeamsAddMembersProps.spec.ts | 38 +- .../v1/teams/TeamsAddMembersProps.test.ts | 71 -- .../rest/v1/teams/TeamsAddMembersProps.ts | 6 +- .../teams/TeamsConvertToChannelProps.spec.ts | 7 +- .../teams/TeamsConvertToChannelProps.test.ts | 39 - .../v1/teams/TeamsConvertToChannelProps.ts | 9 +- .../rest/v1/teams/TeamsDeleteProps.spec.ts | 12 +- .../rest/v1/teams/TeamsDeleteProps.test.ts | 64 -- definition/rest/v1/teams/TeamsDeleteProps.ts | 4 +- .../rest/v1/teams/TeamsLeaveProps.test.ts | 64 -- definition/rest/v1/teams/TeamsLeaveProps.ts | 1 - .../v1/teams/TeamsRemoveMemberProps.spec.ts | 40 +- .../v1/teams/TeamsRemoveMemberProps.test.ts | 61 -- .../rest/v1/teams/TeamsRemoveMemberProps.ts | 5 +- .../v1/teams/TeamsRemoveRoomProps.spec.ts | 4 +- .../v1/teams/TeamsRemoveRoomProps.test.ts | 27 - .../rest/v1/teams/TeamsRemoveRoomProps.ts | 4 +- .../v1/teams/TeamsUpdateMemberProps.spec.ts | 24 +- .../v1/teams/TeamsUpdateMemberProps.test.ts | 59 -- .../rest/v1/teams/TeamsUpdateMemberProps.ts | 4 +- .../rest/v1/teams/TeamsUpdateProps.spec.ts | 216 +++-- .../rest/v1/teams/TeamsUpdateProps.test.ts | 161 ---- definition/rest/v1/teams/TeamsUpdateProps.ts | 21 +- definition/rest/v1/teams/index.ts | 86 +- .../ChannelsTab/ChannelsTab.stories.js | 14 - .../components/ChannelsTab/TableSection.js | 163 ---- .../client/components/ChannelsTab/index.js | 7 - .../components/EngagementDashboardPage.js | 42 - .../EngagementDashboardPage.stories.js | 11 - .../components/EngagementDashboardRoute.js | 26 - .../MessagesTab/MessagesPerChannelSection.js | 236 ----- .../MessagesTab/MessagesSentSection.js | 186 ---- .../MessagesTab/MessagesTab.stories.js | 14 - .../client/components/Section.js | 22 - .../components/UsersTab/ActiveUsersSection.js | 259 ------ .../UsersTab/BusiestChatTimesSection.js | 261 ------ .../components/UsersTab/NewUsersSection.js | 234 ----- .../UsersTab/UsersByTimeOfTheDaySection.js | 211 ----- .../components/UsersTab/UsersTab.Stories.js | 14 - .../client/components/UsersTab/index.js | 31 - .../client/components/data/Histogram.js | 85 -- .../components/data/Histogram.stories.js | 16 - .../client/components/data/LegendSymbol.js | 19 - .../components/data/LegendSymbol.stories.js | 25 - .../client/components/data/colors.js | 2 - ee/app/engagement-dashboard/client/index.js | 1 - ee/app/engagement-dashboard/client/routes.js | 33 - .../server/api/channels.js | 26 - .../server/api/helpers/date.js | 14 - .../server/api/messages.js | 41 - .../engagement-dashboard/server/api/users.js | 67 -- ee/app/engagement-dashboard/server/index.js | 15 - .../server/lib/channels.js | 27 - .../engagement-dashboard/server/lib/date.js | 11 - .../server/listeners/index.js | 2 - .../server/listeners/messages.js | 5 - .../server/listeners/users.js | 4 - ee/app/license/client/index.ts | 22 +- ee/app/license/server/bundles.ts | 15 +- ee/app/license/server/license.ts | 63 +- ee/client/.eslintrc.js | 138 +-- ee/client/.prettierrc | 13 +- ee/client/audit/AuditPage.stories.js | 2 +- ee/client/index.ts | 2 +- ee/client/lib/fetchFeatures.ts | 9 + ee/client/lib/getFromRestApi.ts | 24 + ee/client/lib/onToggledFeature.ts | 42 + .../omnichannel/BusinessHoursTable.stories.js | 2 +- .../BusinessHoursMultiple.stories.js | 2 +- .../BusinessHoursTimeZone.stories.js | 2 +- ee/client/startup/engagementDashboard.ts | 33 + ee/client/startup/index.ts | 1 + .../EngagementDashboardPage.stories.tsx | 13 + .../EngagementDashboardPage.tsx | 66 ++ .../EngagementDashboardRoute.tsx | 43 + .../admin/engagementDashboard/Section.tsx | 30 + .../channels/ChannelsTab.stories.tsx | 14 + .../channels/ChannelsTab.tsx | 148 ++++ .../channels/useChannelsList.ts | 38 + .../data/DownloadDataButton.tsx | 60 ++ .../data/LegendSymbol.stories.tsx | 34 + .../engagementDashboard/data/LegendSymbol.tsx | 25 + .../data/PeriodSelector.tsx | 34 + .../admin/engagementDashboard/data/colors.ts | 14 + .../admin/engagementDashboard/data/periods.ts | 76 ++ .../data/usePeriodLabel.ts | 10 + .../data/usePeriodSelectorState.ts | 25 + .../messages/MessagesPerChannelSection.tsx | 254 ++++++ .../messages/MessagesSentSection.tsx | 177 ++++ .../messages/MessagesTab.stories.tsx | 14 + .../messages/MessagesTab.tsx} | 16 +- .../messages/useMessageOrigins.ts | 31 + .../messages/useMessagesSent.ts | 31 + .../messages/useTopFivePopularChannels.ts | 33 + .../users/ActiveUsersSection.tsx | 291 +++++++ .../users/BusiestChatTimesSection.tsx | 59 ++ .../users/ContentForDays.tsx | 139 +++ .../users/ContentForHours.tsx | 140 +++ .../users/NewUsersSection.tsx | 224 +++++ .../users/UsersByTimeOfTheDaySection.tsx | 198 +++++ .../users/UsersTab.stories.tsx | 14 + .../engagementDashboard/users/UsersTab.tsx | 37 + .../users/useActiveUsers.ts | 32 + .../users/useHourlyChatActivity.ts | 36 + .../engagementDashboard/users/useNewUsers.ts | 31 + .../users/useUsersByTimeOfTheDay.ts | 33 + .../users/useWeeklyChatActivity.ts | 36 + .../SeatsCapUsage/SeatsCapUsage.stories.tsx | 2 +- ee/definition/.eslintrc.js | 1 + ee/definition/.prettierrc | 1 + ee/definition/rest/v1/engagementDashboard.ts | 65 +- .../rest/v1/omnichannel/businessHours.ts | 2 +- .../rest/v1/omnichannel/businessUnits.ts | 23 +- .../rest/v1/omnichannel/cannedResponses.ts | 1 - ee/definition/rest/v1/omnichannel/index.ts | 4 +- ee/server/api/engagementDashboard/channels.ts | 33 + .../api/engagementDashboard/index.ts} | 0 ee/server/api/engagementDashboard/messages.ts | 56 ++ ee/server/api/engagementDashboard/users.ts | 94 ++ ee/server/index.ts | 1 - ee/server/lib/engagementDashboard/channels.ts | 54 ++ ee/server/lib/engagementDashboard/date.ts | 34 + .../lib/engagementDashboard/messages.ts} | 49 +- ee/server/lib/engagementDashboard/startup.ts | 28 + .../lib/engagementDashboard/users.ts} | 142 +-- ee/server/startup/engagementDashboard.ts | 21 + ee/server/startup/index.ts | 1 + package-lock.json | 810 ++++++++++-------- package.json | 20 +- server/sdk/types/ITeamService.ts | 10 +- 172 files changed, 4500 insertions(+), 3838 deletions(-) create mode 120000 .storybook/.eslintrc.js create mode 120000 .storybook/.prettierrc create mode 100644 .storybook/hooks/index.ts rename .storybook/{hooks.ts => hooks/useAutoToggle.ts} (100%) create mode 100644 .storybook/mocks/providers/QueryClientProviderMock.tsx create mode 100644 .storybook/mocks/providers/ServerProviderMock.tsx rename .storybook/mocks/{providers.tsx => providers/index.tsx} (74%) create mode 100644 client/lib/queryClient.ts create mode 120000 definition/rest/.eslintrc.js create mode 120000 definition/rest/.prettierrc delete mode 100644 definition/rest/v1/teams/TeamsAddMembersProps.test.ts delete mode 100644 definition/rest/v1/teams/TeamsConvertToChannelProps.test.ts delete mode 100644 definition/rest/v1/teams/TeamsDeleteProps.test.ts delete mode 100644 definition/rest/v1/teams/TeamsLeaveProps.test.ts delete mode 100644 definition/rest/v1/teams/TeamsRemoveMemberProps.test.ts delete mode 100644 definition/rest/v1/teams/TeamsRemoveRoomProps.test.ts delete mode 100644 definition/rest/v1/teams/TeamsUpdateMemberProps.test.ts delete mode 100644 definition/rest/v1/teams/TeamsUpdateProps.test.ts delete mode 100644 ee/app/engagement-dashboard/client/components/ChannelsTab/ChannelsTab.stories.js delete mode 100644 ee/app/engagement-dashboard/client/components/ChannelsTab/TableSection.js delete mode 100644 ee/app/engagement-dashboard/client/components/ChannelsTab/index.js delete mode 100644 ee/app/engagement-dashboard/client/components/EngagementDashboardPage.js delete mode 100644 ee/app/engagement-dashboard/client/components/EngagementDashboardPage.stories.js delete mode 100644 ee/app/engagement-dashboard/client/components/EngagementDashboardRoute.js delete mode 100644 ee/app/engagement-dashboard/client/components/MessagesTab/MessagesPerChannelSection.js delete mode 100644 ee/app/engagement-dashboard/client/components/MessagesTab/MessagesSentSection.js delete mode 100644 ee/app/engagement-dashboard/client/components/MessagesTab/MessagesTab.stories.js delete mode 100644 ee/app/engagement-dashboard/client/components/Section.js delete mode 100644 ee/app/engagement-dashboard/client/components/UsersTab/ActiveUsersSection.js delete mode 100644 ee/app/engagement-dashboard/client/components/UsersTab/BusiestChatTimesSection.js delete mode 100644 ee/app/engagement-dashboard/client/components/UsersTab/NewUsersSection.js delete mode 100644 ee/app/engagement-dashboard/client/components/UsersTab/UsersByTimeOfTheDaySection.js delete mode 100644 ee/app/engagement-dashboard/client/components/UsersTab/UsersTab.Stories.js delete mode 100644 ee/app/engagement-dashboard/client/components/UsersTab/index.js delete mode 100644 ee/app/engagement-dashboard/client/components/data/Histogram.js delete mode 100644 ee/app/engagement-dashboard/client/components/data/Histogram.stories.js delete mode 100644 ee/app/engagement-dashboard/client/components/data/LegendSymbol.js delete mode 100644 ee/app/engagement-dashboard/client/components/data/LegendSymbol.stories.js delete mode 100644 ee/app/engagement-dashboard/client/components/data/colors.js delete mode 100644 ee/app/engagement-dashboard/client/index.js delete mode 100644 ee/app/engagement-dashboard/client/routes.js delete mode 100644 ee/app/engagement-dashboard/server/api/channels.js delete mode 100644 ee/app/engagement-dashboard/server/api/helpers/date.js delete mode 100644 ee/app/engagement-dashboard/server/api/messages.js delete mode 100644 ee/app/engagement-dashboard/server/api/users.js delete mode 100644 ee/app/engagement-dashboard/server/index.js delete mode 100644 ee/app/engagement-dashboard/server/lib/channels.js delete mode 100644 ee/app/engagement-dashboard/server/lib/date.js delete mode 100644 ee/app/engagement-dashboard/server/listeners/index.js delete mode 100644 ee/app/engagement-dashboard/server/listeners/messages.js delete mode 100644 ee/app/engagement-dashboard/server/listeners/users.js mode change 100644 => 120000 ee/client/.eslintrc.js mode change 100644 => 120000 ee/client/.prettierrc create mode 100644 ee/client/lib/fetchFeatures.ts create mode 100644 ee/client/lib/getFromRestApi.ts create mode 100644 ee/client/lib/onToggledFeature.ts create mode 100644 ee/client/startup/engagementDashboard.ts create mode 100644 ee/client/startup/index.ts create mode 100644 ee/client/views/admin/engagementDashboard/EngagementDashboardPage.stories.tsx create mode 100644 ee/client/views/admin/engagementDashboard/EngagementDashboardPage.tsx create mode 100644 ee/client/views/admin/engagementDashboard/EngagementDashboardRoute.tsx create mode 100644 ee/client/views/admin/engagementDashboard/Section.tsx create mode 100644 ee/client/views/admin/engagementDashboard/channels/ChannelsTab.stories.tsx create mode 100644 ee/client/views/admin/engagementDashboard/channels/ChannelsTab.tsx create mode 100644 ee/client/views/admin/engagementDashboard/channels/useChannelsList.ts create mode 100644 ee/client/views/admin/engagementDashboard/data/DownloadDataButton.tsx create mode 100644 ee/client/views/admin/engagementDashboard/data/LegendSymbol.stories.tsx create mode 100644 ee/client/views/admin/engagementDashboard/data/LegendSymbol.tsx create mode 100644 ee/client/views/admin/engagementDashboard/data/PeriodSelector.tsx create mode 100644 ee/client/views/admin/engagementDashboard/data/colors.ts create mode 100644 ee/client/views/admin/engagementDashboard/data/periods.ts create mode 100644 ee/client/views/admin/engagementDashboard/data/usePeriodLabel.ts create mode 100644 ee/client/views/admin/engagementDashboard/data/usePeriodSelectorState.ts create mode 100644 ee/client/views/admin/engagementDashboard/messages/MessagesPerChannelSection.tsx create mode 100644 ee/client/views/admin/engagementDashboard/messages/MessagesSentSection.tsx create mode 100644 ee/client/views/admin/engagementDashboard/messages/MessagesTab.stories.tsx rename ee/{app/engagement-dashboard/client/components/MessagesTab/index.js => client/views/admin/engagementDashboard/messages/MessagesTab.tsx} (54%) create mode 100644 ee/client/views/admin/engagementDashboard/messages/useMessageOrigins.ts create mode 100644 ee/client/views/admin/engagementDashboard/messages/useMessagesSent.ts create mode 100644 ee/client/views/admin/engagementDashboard/messages/useTopFivePopularChannels.ts create mode 100644 ee/client/views/admin/engagementDashboard/users/ActiveUsersSection.tsx create mode 100644 ee/client/views/admin/engagementDashboard/users/BusiestChatTimesSection.tsx create mode 100644 ee/client/views/admin/engagementDashboard/users/ContentForDays.tsx create mode 100644 ee/client/views/admin/engagementDashboard/users/ContentForHours.tsx create mode 100644 ee/client/views/admin/engagementDashboard/users/NewUsersSection.tsx create mode 100644 ee/client/views/admin/engagementDashboard/users/UsersByTimeOfTheDaySection.tsx create mode 100644 ee/client/views/admin/engagementDashboard/users/UsersTab.stories.tsx create mode 100644 ee/client/views/admin/engagementDashboard/users/UsersTab.tsx create mode 100644 ee/client/views/admin/engagementDashboard/users/useActiveUsers.ts create mode 100644 ee/client/views/admin/engagementDashboard/users/useHourlyChatActivity.ts create mode 100644 ee/client/views/admin/engagementDashboard/users/useNewUsers.ts create mode 100644 ee/client/views/admin/engagementDashboard/users/useUsersByTimeOfTheDay.ts create mode 100644 ee/client/views/admin/engagementDashboard/users/useWeeklyChatActivity.ts create mode 120000 ee/definition/.eslintrc.js create mode 120000 ee/definition/.prettierrc create mode 100644 ee/server/api/engagementDashboard/channels.ts rename ee/{app/engagement-dashboard/server/api/index.js => server/api/engagementDashboard/index.ts} (100%) create mode 100644 ee/server/api/engagementDashboard/messages.ts create mode 100644 ee/server/api/engagementDashboard/users.ts create mode 100644 ee/server/lib/engagementDashboard/channels.ts create mode 100644 ee/server/lib/engagementDashboard/date.ts rename ee/{app/engagement-dashboard/server/lib/messages.js => server/lib/engagementDashboard/messages.ts} (68%) create mode 100644 ee/server/lib/engagementDashboard/startup.ts rename ee/{app/engagement-dashboard/server/lib/users.js => server/lib/engagementDashboard/users.ts} (52%) create mode 100644 ee/server/startup/engagementDashboard.ts diff --git a/.eslintignore b/.eslintignore index f37309f64ef25..38a10ea159b18 100644 --- a/.eslintignore +++ b/.eslintignore @@ -11,13 +11,13 @@ public/packages/rocketchat_videobridge/client/public/external_api.js packages/tap-i18n/lib/tap_i18next/tap_i18next-1.7.3.js private/moment-locales/ public/livechat/ -!.scripts public/pdf.worker.min.js public/workers/**/* imports/client/**/* -!/.storybook/ ee/server/services/dist/** !/.mocharc.js !/.mocharc.*.js +!/.scripts/ +!/.storybook/ !/client/.eslintrc.js !/ee/client/.eslintrc.js diff --git a/.storybook/.eslintrc.js b/.storybook/.eslintrc.js new file mode 120000 index 0000000000000..8589dc8c53248 --- /dev/null +++ b/.storybook/.eslintrc.js @@ -0,0 +1 @@ +../client/.eslintrc.js \ No newline at end of file diff --git a/.storybook/.prettierrc b/.storybook/.prettierrc new file mode 120000 index 0000000000000..4031483e531f1 --- /dev/null +++ b/.storybook/.prettierrc @@ -0,0 +1 @@ +../client/.prettierrc \ No newline at end of file diff --git a/.storybook/decorators.tsx b/.storybook/decorators.tsx index 01b7b4f93cb78..9314684c128f3 100644 --- a/.storybook/decorators.tsx +++ b/.storybook/decorators.tsx @@ -1,6 +1,8 @@ import React, { ReactElement } from 'react'; import { MeteorProviderMock } from './mocks/providers'; +import QueryClientProviderMock from './mocks/providers/QueryClientProviderMock'; +import ServerProviderMock from './mocks/providers/ServerProviderMock'; export const rocketChatDecorator = (storyFn: () => ReactElement): ReactElement => { const linkElement = document.getElementById('theme-styles') || document.createElement('link'); @@ -18,34 +20,44 @@ export const rocketChatDecorator = (storyFn: () => ReactElement): ReactElement = /* eslint-disable-next-line */ const { default: icons } = require('!!raw-loader!../private/public/icons.svg'); - return - -
    -
    - {storyFn()} -
    - ; + return ( + + + + +
    +
    {storyFn()}
    + + + + ); }; -export const fullHeightDecorator = (storyFn: () => ReactElement): ReactElement => -
    +export const fullHeightDecorator = (storyFn: () => ReactElement): ReactElement => ( +
    {storyFn()} -
    ; +
    +); -export const centeredDecorator = (storyFn: () => ReactElement): ReactElement => -
    +export const centeredDecorator = (storyFn: () => ReactElement): ReactElement => ( +
    {storyFn()} -
    ; +
    +); diff --git a/.storybook/hooks/index.ts b/.storybook/hooks/index.ts new file mode 100644 index 0000000000000..ca0d1db71f7f5 --- /dev/null +++ b/.storybook/hooks/index.ts @@ -0,0 +1 @@ +export * from './useAutoToggle'; diff --git a/.storybook/hooks.ts b/.storybook/hooks/useAutoToggle.ts similarity index 100% rename from .storybook/hooks.ts rename to .storybook/hooks/useAutoToggle.ts diff --git a/.storybook/main.js b/.storybook/main.js index 7ac1da4c927f6..58531e40b22b2 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -3,18 +3,15 @@ const { resolve, relative, join } = require('path'); const webpack = require('webpack'); module.exports = { - typescript: { - reactDocgen: 'none', - }, stories: [ '../app/**/*.stories.{js,tsx}', '../client/**/*.stories.{js,tsx}', - '../ee/**/*.stories.{js,tsx}', - ], - addons: [ - '@storybook/addon-essentials', - '@storybook/addon-postcss', + ...(process.env.EE === 'true' ? ['../ee/**/*.stories.{js,tsx}'] : []), ], + addons: ['@storybook/addon-essentials', '@storybook/addon-postcss'], + typescript: { + reactDocgen: 'none', + }, webpackFinal: async (config) => { const cssRule = config.module.rules.find(({ test }) => test.test('index.css')); @@ -22,16 +19,21 @@ module.exports = { ...cssRule.use[2].options, postcssOptions: { plugins: [ - require('postcss-custom-properties')({ preserve: true }), - require('postcss-media-minmax')(), - require('postcss-nested')(), - require('autoprefixer')(), - require('postcss-url')({ url: ({ absolutePath, relativePath, url }) => { - const absoluteDir = absolutePath.slice(0, -relativePath.length); - const relativeDir = relative(absoluteDir, resolve(__dirname, '../public')); - const newPath = join(relativeDir, url); - return newPath; - } }), + ['postcss-custom-properties', { preserve: true }], + 'postcss-media-minmax', + 'postcss-nested', + 'autoprefixer', + [ + 'postcss-url', + { + url: ({ absolutePath, relativePath, url }) => { + const absoluteDir = absolutePath.slice(0, -relativePath.length); + const relativeDir = relative(absoluteDir, resolve(__dirname, '../public')); + const newPath = join(relativeDir, url); + return newPath; + }, + }, + ], ], }, }; @@ -59,10 +61,7 @@ module.exports = { }); config.plugins.push( - new webpack.NormalModuleReplacementPlugin( - /^meteor/, - require.resolve('./mocks/meteor.js'), - ), + new webpack.NormalModuleReplacementPlugin(/^meteor/, require.resolve('./mocks/meteor.js')), new webpack.NormalModuleReplacementPlugin( /(app)\/*.*\/(server)\/*/, require.resolve('./mocks/empty.ts'), diff --git a/.storybook/mocks/meteor.js b/.storybook/mocks/meteor.js index 1b9d0e4f968cc..ef22c95f67a7e 100644 --- a/.storybook/mocks/meteor.js +++ b/.storybook/mocks/meteor.js @@ -45,7 +45,9 @@ export const ReactiveVar = (val) => { let currentVal = val; return { get: () => currentVal, - set: (val) => { currentVal = val; }, + set: (val) => { + currentVal = val; + }, }; }; @@ -55,16 +57,19 @@ export const ReactiveDict = () => ({ all: () => {}, }); -export const Template = Object.assign(() => ({ - onCreated: () => {}, - onRendered: () => {}, - onDestroyed: () => {}, - helpers: () => {}, - events: () => {}, -}), { - registerHelper: () => {}, - __checkName: () => {}, -}); +export const Template = Object.assign( + () => ({ + onCreated: () => {}, + onRendered: () => {}, + onDestroyed: () => {}, + helpers: () => {}, + events: () => {}, + }), + { + registerHelper: () => {}, + __checkName: () => {}, + }, +); export const Blaze = { Template, diff --git a/.storybook/mocks/providers/QueryClientProviderMock.tsx b/.storybook/mocks/providers/QueryClientProviderMock.tsx new file mode 100644 index 0000000000000..d44ea0e9d0798 --- /dev/null +++ b/.storybook/mocks/providers/QueryClientProviderMock.tsx @@ -0,0 +1,20 @@ +import React, { FC } from 'react'; +import { QueryCache, QueryClient, QueryClientProvider } from 'react-query'; + +const queryCache = new QueryCache(); + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + cacheTime: Infinity, + }, + }, + queryCache, +}); + +const QueryClientProviderMock: FC = ({ children }) => ( + {children} +); + +export default QueryClientProviderMock; diff --git a/.storybook/mocks/providers/ServerProviderMock.tsx b/.storybook/mocks/providers/ServerProviderMock.tsx new file mode 100644 index 0000000000000..7cc9a273b0651 --- /dev/null +++ b/.storybook/mocks/providers/ServerProviderMock.tsx @@ -0,0 +1,96 @@ +import { action } from '@storybook/addon-actions'; +import React, { ContextType, FC } from 'react'; + +import { + ServerContext, + ServerMethodName, + ServerMethodParameters, + ServerMethodReturn, +} from '../../../client/contexts/ServerContext'; +import { Serialized } from '../../../definition/Serialized'; +import { + MatchPathPattern, + Method, + OperationParams, + OperationResult, + PathFor, +} from '../../../definition/rest'; + +const logAction = action('ServerProvider'); + +const randomDelay = (): Promise => + new Promise((resolve) => setTimeout(resolve, Math.random() * 1000)); + +const absoluteUrl = (path: string): string => new URL(path, '/').toString(); + +const callMethod = ( + methodName: MethodName, + ...args: ServerMethodParameters +): Promise> => + Promise.resolve(logAction('callMethod', methodName, ...args)) + .then(randomDelay) + .then(() => undefined as any); + +const callEndpoint = >( + method: TMethod, + path: TPath, + params: Serialized>>, +): Promise>>> => + Promise.resolve(logAction('callEndpoint', method, path, params)) + .then(randomDelay) + .then(() => undefined as any); + +const uploadToEndpoint = (endpoint: string, params: any, formData: any): Promise => + Promise.resolve(logAction('uploadToEndpoint', endpoint, params, formData)).then(randomDelay); + +const getStream = ( + streamName: string, + options: {} = {}, +): ((eventName: string, callback: (data: T) => void) => () => void) => { + logAction('getStream', streamName, options); + + return (eventName, callback): (() => void) => { + const subId = Math.random().toString(16).slice(2); + logAction('getStream.subscribe', streamName, eventName, subId); + + randomDelay().then(() => callback(undefined as any)); + + return (): void => { + logAction('getStream.unsubscribe', streamName, eventName, subId); + }; + }; +}; + +const ServerProviderMock: FC>> = ({ + children, + ...overrides +}) => ( + +); + +export default ServerProviderMock; diff --git a/.storybook/mocks/providers.tsx b/.storybook/mocks/providers/index.tsx similarity index 74% rename from .storybook/mocks/providers.tsx rename to .storybook/mocks/providers/index.tsx index 31a66433c753d..9cecb32ff8b94 100644 --- a/.storybook/mocks/providers.tsx +++ b/.storybook/mocks/providers/index.tsx @@ -1,8 +1,10 @@ import i18next from 'i18next'; import React, { PropsWithChildren, ReactElement } from 'react'; -import { TranslationContext, TranslationContextValue } from '../../client/contexts/TranslationContext'; -import ServerProvider from '../../client/providers/ServerProvider'; +import { + TranslationContext, + TranslationContextValue, +} from '../../../client/contexts/TranslationContext'; let contextValue: TranslationContextValue; @@ -16,7 +18,7 @@ const getContextValue = (): TranslationContextValue => { defaultNS: 'project', resources: { en: { - project: require('../../packages/rocketchat-i18n/i18n/en.i18n.json'), + project: require('../../../packages/rocketchat-i18n/i18n/en.i18n.json'), }, }, interpolation: { @@ -45,11 +47,13 @@ const getContextValue = (): TranslationContextValue => { translate.has = (key: string): boolean => !!key && i18next.exists(key); contextValue = { - languages: [{ - name: 'English', - en: 'English', - key: 'en', - }], + languages: [ + { + name: 'English', + en: 'English', + key: 'en', + }, + ], language: 'en', translate, loadLanguage: async (): Promise => undefined, @@ -62,10 +66,7 @@ function TranslationProviderMock({ children }: PropsWithChildren<{}>): ReactElem return ; } +// eslint-disable-next-line react/no-multi-comp export function MeteorProviderMock({ children }: PropsWithChildren<{}>): ReactElement { - return - - {children} - - ; + return {children}; } diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 39ebd205bdb11..ab9bd5a3220d1 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -18,7 +18,6 @@ addParameters({ page: DocsPage, }, options: { - storySort: ([, a], [, b]): number => - a.kind.localeCompare(b.kind), + storySort: ([, a], [, b]): number => a.kind.localeCompare(b.kind), }, }); diff --git a/app/api/server/v1/banners.ts b/app/api/server/v1/banners.ts index 790dbd138afaa..4e740cdf2c901 100644 --- a/app/api/server/v1/banners.ts +++ b/app/api/server/v1/banners.ts @@ -116,7 +116,6 @@ API.v1.addRoute('banners/:id', { authRequired: true }, { // TODO: move to users/ check(this.urlParams, Match.ObjectIncluding({ id: Match.Where((id: unknown): id is string => typeof id === 'string' && Boolean(id.trim())), })); - check(this.queryParams, Match.ObjectIncluding({ platform: Match.OneOf(...Object.values(BannerPlatform)), })); diff --git a/app/api/server/v1/teams.ts b/app/api/server/v1/teams.ts index 7cdb8120ecddc..4f3a655aa7d43 100644 --- a/app/api/server/v1/teams.ts +++ b/app/api/server/v1/teams.ts @@ -9,7 +9,6 @@ import { hasAtLeastOnePermission, hasPermission } from '../../../authorization/s import { Users } from '../../../models/server'; import { removeUserFromRoom } from '../../../lib/server/functions/removeUserFromRoom'; import { IUser } from '../../../../definition/IUser'; -import { isTeamPropsWithTeamName, isTeamPropsWithTeamId } from '../../../../definition/rest/v1/teams'; import { isTeamsConvertToChannelProps } from '../../../../definition/rest/v1/teams/TeamsConvertToChannelProps'; import { isTeamsRemoveRoomProps } from '../../../../definition/rest/v1/teams/TeamsRemoveRoomProps'; import { isTeamsUpdateMemberProps } from '../../../../definition/rest/v1/teams/TeamsUpdateMemberProps'; @@ -42,7 +41,7 @@ API.v1.addRoute('teams.listAll', { authRequired: true }, { return API.v1.unauthorized(); } - const { offset, count } = this.getPaginationItems() as { offset: number; count: number }; + const { offset, count } = this.getPaginationItems(); const { records, total } = await Team.listAll({ offset, count }); @@ -85,20 +84,27 @@ API.v1.addRoute('teams.create', { authRequired: true }, { }, }); +const getTeamByIdOrName = async (params: { teamId: string } | { teamName: string }): Promise => { + if ('teamId' in params && params.teamId) { + return Team.getOneById(params.teamId); + } + + if ('teamName' in params && params.teamName) { + return Team.getOneByName(params.teamName); + } + + return null; +}; + API.v1.addRoute('teams.convertToChannel', { authRequired: true }, { async post() { if (!isTeamsConvertToChannelProps(this.bodyParams)) { return API.v1.failure('invalid-body-params', isTeamsConvertToChannelProps.errors?.map((e) => e.message).join('\n ')); } - const { bodyParams } = this; - - const { roomsToRemove = [] } = bodyParams; + const { roomsToRemove = [] } = this.bodyParams; - const team = await ( - (isTeamPropsWithTeamId(bodyParams) && Team.getOneById(bodyParams.teamId)) - || (isTeamPropsWithTeamName(bodyParams) && Team.getOneByName(bodyParams.teamName)) - ); + const team = await getTeamByIdOrName(this.bodyParams); if (!team) { return API.v1.failure('team-does-not-exist'); @@ -126,18 +132,6 @@ API.v1.addRoute('teams.convertToChannel', { authRequired: true }, { }, }); -const getTeamByIdOrName = async (params: { teamId: string } | { teamName: string }): Promise => { - if ('teamId' in params && params.teamId) { - return Team.getOneById(params.teamId); - } - - if ('teamName' in params && params.teamName) { - return Team.getOneByName(params.teamName); - } - - return null; -}; - API.v1.addRoute('teams.addRooms', { authRequired: true }, { async post() { check(this.bodyParams, Match.OneOf( @@ -365,11 +359,7 @@ API.v1.addRoute('teams.addMembers', { authRequired: true }, { const { bodyParams } = this; const { members } = bodyParams; - const team = await ( - (isTeamPropsWithTeamId(bodyParams) && Team.getOneById(bodyParams.teamId)) - || (isTeamPropsWithTeamName(bodyParams) && Team.getOneByName(bodyParams.teamName)) - ); - + const team = await getTeamByIdOrName(this.bodyParams); if (!team) { return API.v1.failure('team-does-not-exist'); } @@ -393,10 +383,7 @@ API.v1.addRoute('teams.updateMember', { authRequired: true }, { const { bodyParams } = this; const { member } = bodyParams; - const team = await ( - (isTeamPropsWithTeamId(bodyParams) && Team.getOneById(bodyParams.teamId)) - || (isTeamPropsWithTeamName(bodyParams) && Team.getOneByName(bodyParams.teamName)) - ); + const team = await getTeamByIdOrName(this.bodyParams); if (!team) { return API.v1.failure('team-does-not-exist'); } @@ -420,11 +407,7 @@ API.v1.addRoute('teams.removeMember', { authRequired: true }, { const { bodyParams } = this; const { userId, rooms } = bodyParams; - const team = await ( - (isTeamPropsWithTeamId(bodyParams) && Team.getOneById(bodyParams.teamId)) - || (isTeamPropsWithTeamName(bodyParams) && Team.getOneByName(bodyParams.teamName)) - ); - + const team = await getTeamByIdOrName(this.bodyParams); if (!team) { return API.v1.failure('team-does-not-exist'); } @@ -461,15 +444,9 @@ API.v1.addRoute('teams.leave', { authRequired: true }, { return API.v1.failure('invalid-params', isTeamsLeaveProps.errors?.map((e) => e.message).join('\n ')); } - const { bodyParams } = this; - - const { rooms = [] } = bodyParams; - - const team = await ( - (isTeamPropsWithTeamId(bodyParams) && Team.getOneById(bodyParams.teamId)) - || (isTeamPropsWithTeamName(bodyParams) && Team.getOneByName(bodyParams.teamName)) - ); + const { rooms = [] } = this.bodyParams; + const team = await getTeamByIdOrName(this.bodyParams); if (!team) { return API.v1.failure('team-does-not-exist'); } @@ -512,20 +489,15 @@ API.v1.addRoute('teams.info', { authRequired: true }, { API.v1.addRoute('teams.delete', { authRequired: true }, { async post() { - const { bodyParams } = this; const { roomsToRemove = [] } = this.bodyParams; - if (!isTeamsDeleteProps(bodyParams)) { + if (!isTeamsDeleteProps(this.bodyParams)) { return API.v1.failure('invalid-params', isTeamsDeleteProps.errors?.map((e) => e.message).join('\n ')); } - const team = await ( - (isTeamPropsWithTeamId(bodyParams) && Team.getOneById(bodyParams.teamId)) - || (isTeamPropsWithTeamName(bodyParams) && Team.getOneByName(bodyParams.teamName)) - ); - + const team = await getTeamByIdOrName(this.bodyParams); if (!team) { - return API.v1.failure('Team not found.'); + return API.v1.failure('team-does-not-exist'); } if (!hasPermission(this.userId, 'delete-team', team.roomId)) { @@ -573,18 +545,13 @@ API.v1.addRoute('teams.autocomplete', { authRequired: true }, { API.v1.addRoute('teams.update', { authRequired: true }, { async post() { - const { bodyParams } = this; - if (!isTeamsUpdateProps(bodyParams)) { + if (!isTeamsUpdateProps(this.bodyParams)) { return API.v1.failure('invalid-params', isTeamsUpdateProps.errors?.map((e) => e.message).join('\n ')); } - const { data } = bodyParams; - - const team = await ( - (isTeamPropsWithTeamId(bodyParams) && Team.getOneById(bodyParams.teamId)) - || (isTeamPropsWithTeamName(bodyParams) && Team.getOneByName(bodyParams.teamName)) - ); + const { data } = this.bodyParams; + const team = await getTeamByIdOrName(this.bodyParams); if (!team) { return API.v1.failure('team-does-not-exist'); } diff --git a/app/models/server/raw/Analytics.ts b/app/models/server/raw/Analytics.ts index 681a8be0ef633..4c2b1fa46cc6c 100644 --- a/app/models/server/raw/Analytics.ts +++ b/app/models/server/raw/Analytics.ts @@ -75,7 +75,7 @@ export class AnalyticsRaw extends BaseRaw { } getMessagesOrigin({ start, end }: { start: IAnalytic['date']; end: IAnalytic['date'] }): AggregationCursor<{ - t: 'message'; + t: IRoom['t']; messages: number; }> { const params = [ @@ -99,24 +99,16 @@ export class AnalyticsRaw extends BaseRaw { }, }, ]; - return this.col.aggregate<{ - t: 'message'; - messages: number; - }>(params); + return this.col.aggregate(params); } getMostPopularChannelsByMessagesSentQuantity({ start, end, options = {} }: { start: IAnalytic['date']; end: IAnalytic['date']; options?: { sort?: SortOptionObject; count?: number } }): AggregationCursor<{ - t: 'messages'; + t: IRoom['t']; name: string; messages: number; usernames: string[]; }> { - return this.col.aggregate<{ - t: 'messages'; - name: string; - messages: number; - usernames: string[]; - }>([ + return this.col.aggregate([ { $match: { type: 'messages', diff --git a/app/models/server/raw/Rooms.js b/app/models/server/raw/Rooms.js index 6dfc2212d8ed8..a4d83368a8b43 100644 --- a/app/models/server/raw/Rooms.js +++ b/app/models/server/raw/Rooms.js @@ -367,18 +367,20 @@ export class RoomsRaw extends BaseRaw { const firstParams = [lookup, messagesProject, messagesUnwind, messagesGroup, lastWeekMessagesUnwind, lastWeekMessagesGroup, presentationProject]; const sort = { $sort: options.sort || { messages: -1 } }; const params = [...firstParams, sort]; + if (onlyCount) { params.push({ $count: 'total' }); - return this.col.aggregate(params); } + if (options.offset) { params.push({ $skip: options.offset }); } + if (options.count) { params.push({ $limit: options.count }); } - return this.col.aggregate(params).toArray(); + return this.col.aggregate(params); } findOneByName(name, options = {}) { diff --git a/app/models/server/raw/Sessions.ts b/app/models/server/raw/Sessions.ts index dbd6080f18b6f..2d24a98691a88 100644 --- a/app/models/server/raw/Sessions.ts +++ b/app/models/server/raw/Sessions.ts @@ -1,7 +1,8 @@ import { AggregationCursor, BulkWriteOperation, BulkWriteOpResultObject, Collection, IndexSpecification, UpdateWriteOpResult, FilterQuery } from 'mongodb'; -import { ISession } from '../../../../definition/ISession'; +import type { ISession } from '../../../../definition/ISession'; import { BaseRaw, ModelOptionalId } from './BaseRaw'; +import type { IUser } from '../../../../definition/IUser'; type DestructuredDate = {year: number; month: number; day: number}; type DestructuredDateWithType = {year: number; month: number; day: number; type?: 'month' | 'week'}; @@ -627,8 +628,20 @@ export class SessionsRaw extends BaseRaw { }); } - async getActiveUsersOfPeriodByDayBetweenDates({ start, end }: DestructuredRange): Promise { - return this.col.aggregate([ + async getActiveUsersOfPeriodByDayBetweenDates({ start, end }: DestructuredRange): Promise<{ + day: number; + month: number; + year: number; + usersList: IUser['_id'][]; + users: number; + }[]> { + return this.col.aggregate<{ + day: number; + month: number; + year: number; + usersList: IUser['_id'][]; + users: number; + }>([ { $match: { ...matchBasedOnDate(start, end), @@ -675,7 +688,10 @@ export class SessionsRaw extends BaseRaw { ]).toArray(); } - async getBusiestTimeWithinHoursPeriod({ start, end, groupSize }: DestructuredRange & {groupSize: number}): Promise { + async getBusiestTimeWithinHoursPeriod({ start, end, groupSize }: DestructuredRange & { groupSize: number }): Promise<{ + hour: number; + users: number; + }[]> { const match = { $match: { type: 'computed-session', @@ -706,11 +722,24 @@ export class SessionsRaw extends BaseRaw { hour: -1, }, }; - return this.col.aggregate([match, rangeProject, unwind, groups.listGroup, groups.countGroup, presentationProject, sort]).toArray(); + return this.col.aggregate<{ + hour: number; + users: number; + }>([match, rangeProject, unwind, groups.listGroup, groups.countGroup, presentationProject, sort]).toArray(); } - async getTotalOfSessionsByDayBetweenDates({ start, end }: DestructuredRange): Promise { - return this.col.aggregate([ + async getTotalOfSessionsByDayBetweenDates({ start, end }: DestructuredRange): Promise<{ + day: number; + month: number; + year: number; + users: number; + }[]> { + return this.col.aggregate<{ + day: number; + month: number; + year: number; + users: number; + }>([ { $match: { ...matchBasedOnDate(start, end), @@ -739,7 +768,13 @@ export class SessionsRaw extends BaseRaw { ]).toArray(); } - async getTotalOfSessionByHourAndDayBetweenDates({ start, end }: DestructuredRange): Promise { + async getTotalOfSessionByHourAndDayBetweenDates({ start, end }: DestructuredRange): Promise<{ + hour: number; + day: number; + month: number; + year: number; + users: number; + }[]> { const match = { $match: { type: 'computed-session', @@ -775,7 +810,14 @@ export class SessionsRaw extends BaseRaw { hour: -1, }, }; - return this.col.aggregate([match, rangeProject, unwind, groups.listGroup, groups.countGroup, presentationProject, sort]).toArray(); + + return this.col.aggregate<{ + hour: number; + day: number; + month: number; + year: number; + users: number; + }>([match, rangeProject, unwind, groups.listGroup, groups.countGroup, presentationProject, sort]).toArray(); } async getUniqueUsersOfYesterday(): Promise { diff --git a/app/statistics/server/lib/SAUMonitor.js b/app/statistics/server/lib/SAUMonitor.js index 43d41cad7e33c..9ffa5479a9e24 100644 --- a/app/statistics/server/lib/SAUMonitor.js +++ b/app/statistics/server/lib/SAUMonitor.js @@ -365,7 +365,7 @@ export class SAUMonitorClass { await aggregates.dailySessionsOfYesterday(Sessions.col, yesterday).forEach(async (record) => { record._id = `${ record.userId }-${ record.year }-${ record.month }-${ record.day }`; - await Sessions.updateOne({ _id: record._id }, record, { upsert: true }); + await Sessions.updateOne({ _id: record._id }, { $set: record }, { upsert: true }); }); await Sessions.updateMany(match, { diff --git a/app/utils/client/lib/RestApiClient.d.ts b/app/utils/client/lib/RestApiClient.d.ts index ecf1f22354f8d..c9d4c225488b6 100644 --- a/app/utils/client/lib/RestApiClient.d.ts +++ b/app/utils/client/lib/RestApiClient.d.ts @@ -2,7 +2,7 @@ import { Serialized } from '../../../../definition/Serialized'; export declare const APIClient: { delete(endpoint: string, params?: Serialized

    ): Promise>; - get(endpoint: string, params?: Serialized

    ): Promise>; + get(endpoint: string, params?: void extends P ? void : Serialized

    ): Promise>; post(endpoint: string, params?: Serialized

    , body?: B): Promise>; upload( endpoint: string, diff --git a/client/.eslintrc.js b/client/.eslintrc.js index baae88c35553a..da196844a21aa 100644 --- a/client/.eslintrc.js +++ b/client/.eslintrc.js @@ -63,6 +63,8 @@ module.exports = { plugins: ['@typescript-eslint', 'react', 'react-hooks', 'prettier'], rules: { '@typescript-eslint/ban-ts-ignore': 'off', + '@typescript-eslint/explicit-function-return-type': 'warn', + // '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/indent': 'off', '@typescript-eslint/interface-name-prefix': ['error', 'always'], '@typescript-eslint/no-extra-parens': 'off', @@ -146,5 +148,12 @@ module.exports = { mocha: true, }, }, + { + files: ['**/*.stories.ts', '**/*.stories.tsx'], + rules: { + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + }, + }, ], }; diff --git a/client/lib/createRouteGroup.ts b/client/lib/createRouteGroup.ts index 3b39890facb8a..a1c0d160cf2ab 100644 --- a/client/lib/createRouteGroup.ts +++ b/client/lib/createRouteGroup.ts @@ -1,4 +1,5 @@ -import { FlowRouter } from 'meteor/kadira:flow-router'; +import { FlowRouter, Group, RouteOptions } from 'meteor/kadira:flow-router'; +import { ReactiveVar } from 'meteor/reactive-var'; import { Tracker } from 'meteor/tracker'; import { ComponentType, createElement, lazy, ReactNode } from 'react'; @@ -8,15 +9,82 @@ import { createTemplateForComponent } from './portals/createTemplateForComponent type RouteRegister = { ( path: string, - params: Parameters[1] & - ( - | {} - | { - lazyRouteComponent: () => Promise<{ default: ComponentType }>; - props: Record; - } - ), - ): void; + options: RouteOptions & { + lazyRouteComponent: () => Promise<{ default: ComponentType }>; + props?: Record; + ready?: boolean; + }, + ): [register: () => void, unregister: () => void]; + (path: string, options: RouteOptions): void; +}; + +const registerLazyComponentRoute = ( + routeGroup: Group, + importRouter: () => Promise<{ + default: ComponentType<{ + renderRoute?: () => ReactNode; + }>; + }>, + path: string, + { + lazyRouteComponent, + props, + ready = true, + ...rest + }: RouteOptions & { + lazyRouteComponent: () => Promise<{ default: ComponentType }>; + props?: Record; + ready?: boolean; + }, +): [register: () => void, unregister: () => void] => { + const enabled = new ReactiveVar(ready ? true : undefined); + let computation: Tracker.Computation | undefined; + + const handleEnter = ( + _context: unknown, + _redirect: (pathDef: string) => void, + stop: () => void, + ): void => { + const _enabled = Tracker.nonreactive(() => enabled.get()); + if (_enabled === false) { + stop(); + return; + } + + computation = Tracker.autorun(() => { + const _enabled = enabled.get(); + + if (_enabled === false) { + FlowRouter.go('/'); + } + }); + }; + + const handleExit = (): void => { + computation?.stop(); + }; + + const RouteComponent = lazy(lazyRouteComponent); + const renderRoute = (): ReactNode => createElement(RouteComponent, props); + + routeGroup.route(path, { + ...rest, + triggersEnter: [handleEnter, ...(rest.triggersEnter ?? [])], + triggersExit: [handleExit, ...(rest.triggersExit ?? [])], + action() { + const center = createTemplateForComponent( + Tracker.nonreactive(() => FlowRouter.getRouteName()), + importRouter, + { + attachment: 'at-parent', + props: () => ({ renderRoute }), + }, + ); + appLayout.render('main', { center }); + }, + }); + + return [(): void => enabled.set(true), (): void => enabled.set(false)]; }; export const createRouteGroup = ( @@ -33,32 +101,31 @@ export const createRouteGroup = ( prefix, }); - const registerRoute: RouteRegister = (path, options) => { + function registerRoute( + path: string, + options: RouteOptions & { + lazyRouteComponent: () => Promise<{ default: ComponentType }>; + props?: Record; + ready?: boolean; + }, + ): [register: () => void, unregister: () => void]; + function registerRoute(path: string, options: RouteOptions): void; + function registerRoute( + path: string, + options: + | RouteOptions + | (RouteOptions & { + lazyRouteComponent: () => Promise<{ default: ComponentType }>; + props?: Record; + ready?: boolean; + }), + ): [register: () => void, unregister: () => void] | void { if ('lazyRouteComponent' in options) { - const { lazyRouteComponent, props, ...rest } = options; - - const RouteComponent = lazy(lazyRouteComponent); - const renderRoute = (): ReactNode => createElement(RouteComponent, props); - - routeGroup.route(path, { - ...rest, - action() { - const center = createTemplateForComponent( - Tracker.nonreactive(() => FlowRouter.getRouteName()), - importRouter, - { - attachment: 'at-parent', - props: () => ({ renderRoute }), - }, - ); - appLayout.render('main', { center }); - }, - }); - return; + return registerLazyComponentRoute(routeGroup, importRouter, path, options); } routeGroup.route(path, options); - }; + } registerRoute('/', { name: `${name}-index`, diff --git a/client/lib/createSidebarItems.ts b/client/lib/createSidebarItems.ts index 17f3cc9a3bf85..0a2808e70b4bb 100644 --- a/client/lib/createSidebarItems.ts +++ b/client/lib/createSidebarItems.ts @@ -2,6 +2,9 @@ import type { Subscription } from 'use-subscription'; type SidebarItem = { i18nLabel: string; + href?: string; + icon?: string; + permissionGranted?: boolean | (() => boolean); }; export const createSidebarItems = ( diff --git a/client/lib/download.ts b/client/lib/download.ts index 8edb28bf2f8b1..b0e361f1ff62a 100644 --- a/client/lib/download.ts +++ b/client/lib/download.ts @@ -37,7 +37,7 @@ export const downloadJsonAs = (jsonObject: unknown, basename: string): void => { ); }; -export const downloadCsvAs = (csvData: unknown[][], basename: string): void => { +export const downloadCsvAs = (csvData: readonly (readonly unknown[])[], basename: string): void => { const escapeCell = (cell: unknown): string => `"${String(cell).replace(/"/g, '""')}"`; const content = csvData.reduce( (content, row) => `${content + row.map(escapeCell).join(';')}\n`, diff --git a/client/lib/queryClient.ts b/client/lib/queryClient.ts new file mode 100644 index 0000000000000..0fd037d304c0d --- /dev/null +++ b/client/lib/queryClient.ts @@ -0,0 +1,3 @@ +import { QueryClient } from 'react-query'; + +export const queryClient = new QueryClient(); diff --git a/client/sidebar/header/UserDropdown.js b/client/sidebar/header/UserDropdown.js index 246ebe59ca511..f98aef0844492 100644 --- a/client/sidebar/header/UserDropdown.js +++ b/client/sidebar/header/UserDropdown.js @@ -34,6 +34,7 @@ const ADMIN_PERMISSIONS = [ 'manage-incoming-integrations', 'manage-own-outgoing-integrations', 'manage-own-incoming-integrations', + 'view-engagement-dashboard', ]; const style = { diff --git a/client/views/admin/index.ts b/client/views/admin/index.ts index a962afc2ff1c9..39ecf34bc30cd 100644 --- a/client/views/admin/index.ts +++ b/client/views/admin/index.ts @@ -1,2 +1,5 @@ export { registerAdminRoute } from './routes'; -export { registerAdminSidebarItem } from './sidebarItems'; +export { + registerAdminSidebarItem, + unregisterSidebarItem as unregisterAdminSidebarItem, +} from './sidebarItems'; diff --git a/client/views/root/AppRoot.tsx b/client/views/root/AppRoot.tsx index 155453cddbee9..3aa3453203a55 100644 --- a/client/views/root/AppRoot.tsx +++ b/client/views/root/AppRoot.tsx @@ -1,5 +1,7 @@ import React, { FC, lazy, Suspense } from 'react'; +import { QueryClientProvider } from 'react-query'; +import { queryClient } from '../../lib/queryClient'; import PageLoading from './PageLoading'; const ConnectionStatusBar = lazy( @@ -13,10 +15,12 @@ const PortalsWrapper = lazy(() => import('./PortalsWrapper')); const AppRoot: FC = () => ( }> - - - - + + + + + + ); diff --git a/definition/IUser.ts b/definition/IUser.ts index 55d1b62b29b9c..18642e511e7b3 100644 --- a/definition/IUser.ts +++ b/definition/IUser.ts @@ -99,14 +99,6 @@ export interface IRole { _id: string; } -export interface IDailyActiveUsers { - usersList: string[]; - users: number; - day: number; - month: number; - year: number; -} - export interface IUser extends IRocketChatRecord { _id: string; createdAt: Date; diff --git a/definition/externals/meteor/kadira-flow-router.d.ts b/definition/externals/meteor/kadira-flow-router.d.ts index 7bf423879c7df..76d8d597600e9 100644 --- a/definition/externals/meteor/kadira-flow-router.d.ts +++ b/definition/externals/meteor/kadira-flow-router.d.ts @@ -1,6 +1,11 @@ declare module 'meteor/kadira:flow-router' { import { Subscription } from 'meteor/meteor'; + type Context = { + params: Record; + queryParams: Record; + }; + type RouteOptions = { name: string; action?: ( @@ -13,13 +18,12 @@ declare module 'meteor/kadira:flow-router' { params?: Record, queryParams?: Record, ) => void; - triggersEnter?: unknown[]; - triggersExit?: unknown[]; - }; - - type Context = { - params: Record; - queryParams: Record; + triggersEnter?: (( + context: Context, + redirect: (pathDef: string) => void, + stop: () => void, + ) => void)[]; + triggersExit?: ((context: Context) => void)[]; }; class Route { diff --git a/definition/rest/.eslintrc.js b/definition/rest/.eslintrc.js new file mode 120000 index 0000000000000..4ce23c9428e75 --- /dev/null +++ b/definition/rest/.eslintrc.js @@ -0,0 +1 @@ +../../client/.eslintrc.js \ No newline at end of file diff --git a/definition/rest/.prettierrc b/definition/rest/.prettierrc new file mode 120000 index 0000000000000..9d5a1f5613c49 --- /dev/null +++ b/definition/rest/.prettierrc @@ -0,0 +1 @@ +../../client/.prettierrc \ No newline at end of file diff --git a/definition/rest/helpers/ReplacePlaceholders.ts b/definition/rest/helpers/ReplacePlaceholders.ts index d8503b4b8471f..96c910ce0ec3e 100644 --- a/definition/rest/helpers/ReplacePlaceholders.ts +++ b/definition/rest/helpers/ReplacePlaceholders.ts @@ -1,8 +1,7 @@ -export type ReplacePlaceholders = - string extends TPath - ? TPath - : TPath extends `${ infer Start }:${ infer _Param }/${ infer Rest }` - ? `${ Start }${ string }/${ ReplacePlaceholders }` - : TPath extends `${ infer Start }:${ infer _Param }` - ? `${ Start }${ string }` - : TPath; +export type ReplacePlaceholders = string extends TPath + ? TPath + : TPath extends `${infer Start}:${infer _Param}/${infer Rest}` + ? `${Start}${string}/${ReplacePlaceholders}` + : TPath extends `${infer Start}:${infer _Param}` + ? `${Start}${string}` + : TPath; diff --git a/definition/rest/index.ts b/definition/rest/index.ts index 8a2f9e3105a70..05d803a89c5a7 100644 --- a/definition/rest/index.ts +++ b/definition/rest/index.ts @@ -26,28 +26,28 @@ import type { TeamsEndpoints } from './v1/teams'; import type { UsersEndpoints } from './v1/users'; type CommunityEndpoints = BannersEndpoints & -ChatEndpoints & -ChannelsEndpoints & -CloudEndpoints & -CustomUserStatusEndpoints & -DmEndpoints & -DnsEndpoints & -EmojiCustomEndpoints & -GroupsEndpoints & -ImEndpoints & -LDAPEndpoints & -RoomsEndpoints & -RolesEndpoints & -TeamsEndpoints & -SettingsEndpoints & -UsersEndpoints & -AppsEndpoints & -OmnichannelEndpoints & -StatisticsEndpoints & -LicensesEndpoints & -MiscEndpoints & -PermissionsEndpoints & -InstancesEndpoints; + ChatEndpoints & + ChannelsEndpoints & + CloudEndpoints & + CustomUserStatusEndpoints & + DmEndpoints & + DnsEndpoints & + EmojiCustomEndpoints & + GroupsEndpoints & + ImEndpoints & + LDAPEndpoints & + RoomsEndpoints & + RolesEndpoints & + TeamsEndpoints & + SettingsEndpoints & + UsersEndpoints & + AppsEndpoints & + OmnichannelEndpoints & + StatisticsEndpoints & + LicensesEndpoints & + MiscEndpoints & + PermissionsEndpoints & + InstancesEndpoints; type Endpoints = CommunityEndpoints & EnterpriseEndpoints; @@ -57,15 +57,15 @@ type OperationsByPathPattern = TPathPatter type OperationsByPathPatternAndMethod< TPathPattern extends keyof Endpoints, - TMethod extends KeyOfEach = KeyOfEach + TMethod extends KeyOfEach = KeyOfEach, > = TMethod extends any ? { - pathPattern: TPathPattern; - method: TMethod; - path: ReplacePlaceholders; - params: GetParams; - result: GetResult; - } + pathPattern: TPathPattern; + method: TMethod; + path: ReplacePlaceholders; + params: GetParams; + result: GetResult; + } : never; type Operations = OperationsByPathPattern; @@ -89,35 +89,41 @@ export type MatchPathPattern = TPath extends any : never; export type JoinPathPattern = Extract< -PathPattern, -`${ TBasePath }/${ TSubPathPattern }` | TSubPathPattern + PathPattern, + `${TBasePath}/${TSubPathPattern}` | TSubPathPattern >; type GetParams = TOperation extends (...args: any) => any - ? Parameters[0] extends void ? void : Parameters[0] - : never + ? Parameters[0] extends void + ? void + : Parameters[0] + : never; type GetResult = TOperation extends (...args: any) => any ? ReturnType - : never + : never; -export type OperationParams = - TMethod extends keyof Endpoints[TPathPattern] - ? GetParams - : never; +export type OperationParams< + TMethod extends Method, + TPathPattern extends PathPattern, +> = TMethod extends keyof Endpoints[TPathPattern] + ? GetParams + : never; -export type OperationResult = - TMethod extends keyof Endpoints[TPathPattern] - ? GetResult - : never; +export type OperationResult< + TMethod extends Method, + TPathPattern extends PathPattern, +> = TMethod extends keyof Endpoints[TPathPattern] + ? GetResult + : never; export type UrlParams = string extends T ? Record - : T extends `${ infer _Start }:${ infer Param }/${ infer Rest }` - ? { [k in Param | keyof UrlParams]: string } - : T extends `${ infer _Start }:${ infer Param }` - ? { [k in Param]: string } - : {}; + : T extends `${infer _Start}:${infer Param}/${infer Rest}` + ? { [k in Param | keyof UrlParams]: string } + : T extends `${infer _Start}:${infer Param}` + ? { [k in Param]: string } + : {}; export type MethodOf = TPathPattern extends any ? keyof Endpoints[TPathPattern] diff --git a/definition/rest/v1/banners.ts b/definition/rest/v1/banners.ts index 7b667a4f26e9c..e0c963a9ec766 100644 --- a/definition/rest/v1/banners.ts +++ b/definition/rest/v1/banners.ts @@ -3,21 +3,21 @@ import type { BannerPlatform, IBanner } from '../../IBanner'; export type BannersEndpoints = { /* @deprecated */ 'banners.getNew': { - GET: (params: { platform: BannerPlatform; bid: IBanner['_id'] }) => ({ + GET: (params: { platform: BannerPlatform; bid: IBanner['_id'] }) => { banners: IBanner[]; - }); + }; }; 'banners/:id': { - GET: (params: { platform: BannerPlatform }) => ({ + GET: (params: { platform: BannerPlatform }) => { banners: IBanner[]; - }); + }; }; 'banners': { - GET: (params: { platform: BannerPlatform }) => ({ + GET: (params: { platform: BannerPlatform }) => { banners: IBanner[]; - }); + }; }; 'banners.dismiss': { diff --git a/definition/rest/v1/dm.ts b/definition/rest/v1/dm.ts index 4ce71b05469b1..f19d000dbc3f0 100644 --- a/definition/rest/v1/dm.ts +++ b/definition/rest/v1/dm.ts @@ -6,11 +6,11 @@ export type DmEndpoints = { POST: ( params: ( | { - username: Exclude; - } + username: Exclude; + } | { - usernames: string; - } + usernames: string; + } ) & { excludeSelf?: boolean; }, diff --git a/definition/rest/v1/emojiCustom.ts b/definition/rest/v1/emojiCustom.ts index c97caa027463e..89963edc4295a 100644 --- a/definition/rest/v1/emojiCustom.ts +++ b/definition/rest/v1/emojiCustom.ts @@ -6,8 +6,8 @@ export type EmojiCustomEndpoints = { 'emoji-custom.all': { GET: ( params: { query: string } & PaginatedRequest & { - sort: string; // {name: 'asc' | 'desc';}>; - } + sort: string; // {name: 'asc' | 'desc';}>; + }, ) => { emojis: ICustomEmojiDescriptor[]; } & PaginatedResult; diff --git a/definition/rest/v1/im.ts b/definition/rest/v1/im.ts index b88a5b2be0c77..6b9035e96ce16 100644 --- a/definition/rest/v1/im.ts +++ b/definition/rest/v1/im.ts @@ -7,11 +7,11 @@ export type ImEndpoints = { POST: ( params: ( | { - username: Exclude; - } + username: Exclude; + } | { - usernames: string; - } + usernames: string; + } ) & { excludeSelf?: boolean; }, diff --git a/definition/rest/v1/instances.ts b/definition/rest/v1/instances.ts index c792fc9b5fd54..f0a469d302a6a 100644 --- a/definition/rest/v1/instances.ts +++ b/definition/rest/v1/instances.ts @@ -2,15 +2,18 @@ import { IInstanceStatus } from '../../IInstanceStatus'; export type InstancesEndpoints = { 'instances.get': { - GET: () => ({ - instances: (IInstanceStatus | { - connection: { - address: unknown; - currentStatus: unknown; - instanceRecord: unknown; - broadcastAuth: unknown; - }; - })[]; - }); + GET: () => { + instances: ( + | IInstanceStatus + | { + connection: { + address: unknown; + currentStatus: unknown; + instanceRecord: unknown; + broadcastAuth: unknown; + }; + } + )[]; + }; }; }; diff --git a/definition/rest/v1/ldap.ts b/definition/rest/v1/ldap.ts index 7553d749eaf9c..5c83726b7da2b 100644 --- a/definition/rest/v1/ldap.ts +++ b/definition/rest/v1/ldap.ts @@ -7,9 +7,9 @@ export type LDAPEndpoints = { }; }; 'ldap.testSearch': { - POST: (params: { username: string }) => ({ + POST: (params: { username: string }) => { message: TranslationKey; - }); + }; }; 'ldap.syncNow': { POST: () => { diff --git a/definition/rest/v1/permissions.ts b/definition/rest/v1/permissions.ts index b9ec5ca958647..d27a78c030ac4 100644 --- a/definition/rest/v1/permissions.ts +++ b/definition/rest/v1/permissions.ts @@ -30,14 +30,14 @@ export const isBodyParamsValidPermissionUpdate = ajv.compile(permissionUpdatePro export type PermissionsEndpoints = { 'permissions.listAll': { - GET: (params: { updatedSince?: string }) => ({ + GET: (params: { updatedSince?: string }) => { update: IPermission[]; remove: IPermission[]; - }); + }; }; 'permissions.update': { - POST: (params: PermissionsUpdateProps) => ({ + POST: (params: PermissionsUpdateProps) => { permissions: IPermission[]; - }); + }; }; }; diff --git a/definition/rest/v1/roles.ts b/definition/rest/v1/roles.ts index bccc9a37e1d10..746d09d824559 100644 --- a/definition/rest/v1/roles.ts +++ b/definition/rest/v1/roles.ts @@ -4,7 +4,8 @@ import { IRole, IUser } from '../../IUser'; const ajv = new Ajv(); -type RoleCreateProps = Pick & Partial>; +type RoleCreateProps = Pick & + Partial>; const roleCreatePropsSchema: JSONSchemaType = { type: 'object', @@ -82,7 +83,7 @@ type RoleAddUserToRoleProps = { username: string; roleName: string; roomId?: string; -} +}; const roleAddUserToRolePropsSchema: JSONSchemaType = { type: 'object', @@ -102,7 +103,6 @@ const roleAddUserToRolePropsSchema: JSONSchemaType = { additionalProperties: false, }; - export const isRoleAddUserToRoleProps = ajv.compile(roleAddUserToRolePropsSchema); type RoleRemoveUserFromRoleProps = { @@ -110,7 +110,7 @@ type RoleRemoveUserFromRoleProps = { roleName: string; roomId?: string; scope?: string; -} +}; const roleRemoveUserFromRolePropsSchema: JSONSchemaType = { type: 'object', @@ -138,45 +138,45 @@ export const isRoleRemoveUserFromRoleProps = ajv.compile(roleRemoveUserFromRoleP type RoleSyncProps = { updatedSince?: string; -} +}; export type RolesEndpoints = { 'roles.list': { - GET: () => ({ + GET: () => { roles: IRole[]; - }); + }; }; 'roles.sync': { - GET: (params: RoleSyncProps) => ({ + GET: (params: RoleSyncProps) => { roles: { update: IRole[]; remove: IRole[]; }; - }); + }; }; 'roles.create': { - POST: (params: RoleCreateProps) => ({ + POST: (params: RoleCreateProps) => { role: IRole; - }); + }; }; 'roles.addUserToRole': { - POST: (params: RoleAddUserToRoleProps) => ({ + POST: (params: RoleAddUserToRoleProps) => { role: IRole; - }); + }; }; 'roles.getUsersInRole': { - GET: (params: { roomId: string; role: string; offset: number; count: number }) => ({ + GET: (params: { roomId: string; role: string; offset: number; count: number }) => { users: IUser[]; total: number; - }); + }; }; 'roles.update': { - POST: (role: RoleUpdateProps) => ({ + POST: (role: RoleUpdateProps) => { role: IRole; - }); + }; }; 'roles.delete': { @@ -184,8 +184,8 @@ export type RolesEndpoints = { }; 'roles.removeUserFromRole': { - POST: (props: RoleRemoveUserFromRoleProps) => ({ + POST: (props: RoleRemoveUserFromRoleProps) => { role: IRole; - }); + }; }; }; diff --git a/definition/rest/v1/settings.ts b/definition/rest/v1/settings.ts index 71ffab95bbf20..e88a6d144d672 100644 --- a/definition/rest/v1/settings.ts +++ b/definition/rest/v1/settings.ts @@ -1,11 +1,14 @@ import { ISetting, ISettingColor } from '../../ISetting'; import { PaginatedResult } from '../helpers/PaginatedResult'; -type SettingsUpdateProps = SettingsUpdatePropDefault | SettingsUpdatePropsActions | SettingsUpdatePropsColor; +type SettingsUpdateProps = + | SettingsUpdatePropDefault + | SettingsUpdatePropsActions + | SettingsUpdatePropsColor; type SettingsUpdatePropsActions = { execute: boolean; -} +}; export type OauthCustomConfiguration = { _id: string; @@ -42,24 +45,31 @@ export type OauthCustomConfiguration = { buttonLabelText: unknown; buttonLabelColor: unknown; buttonColor: unknown; -} +}; -export const isOauthCustomConfiguration = (config: any): config is OauthCustomConfiguration => Boolean(config); +export const isOauthCustomConfiguration = (config: any): config is OauthCustomConfiguration => + Boolean(config); -export const isSettingsUpdatePropsActions = (props: Partial): props is SettingsUpdatePropsActions => 'execute' in props; +export const isSettingsUpdatePropsActions = ( + props: Partial, +): props is SettingsUpdatePropsActions => 'execute' in props; type SettingsUpdatePropsColor = { editor: ISettingColor['editor']; value: ISetting['value']; -} +}; -export const isSettingsUpdatePropsColor = (props: Partial): props is SettingsUpdatePropsColor => 'editor' in props && 'value' in props; +export const isSettingsUpdatePropsColor = ( + props: Partial, +): props is SettingsUpdatePropsColor => 'editor' in props && 'value' in props; type SettingsUpdatePropDefault = { value: ISetting['value']; -} +}; -export const isSettingsUpdatePropDefault = (props: Partial): props is SettingsUpdatePropDefault => 'value' in props; +export const isSettingsUpdatePropDefault = ( + props: Partial, +): props is SettingsUpdatePropDefault => 'value' in props; export type SettingsEndpoints = { 'settings.public': { @@ -69,9 +79,9 @@ export type SettingsEndpoints = { }; 'settings.oauth': { - GET: () => ({ + GET: () => { services: Partial[]; - }); + }; }; 'settings.addCustomOAuth': { @@ -79,9 +89,9 @@ export type SettingsEndpoints = { }; 'settings': { - GET: () => ({ + GET: () => { settings: ISetting[]; - }); + }; }; 'settings/:_id': { diff --git a/definition/rest/v1/teams/TeamsAddMembersProps.spec.ts b/definition/rest/v1/teams/TeamsAddMembersProps.spec.ts index d06fcc7507d1c..cc35d52bc5d97 100644 --- a/definition/rest/v1/teams/TeamsAddMembersProps.spec.ts +++ b/definition/rest/v1/teams/TeamsAddMembersProps.spec.ts @@ -44,27 +44,53 @@ describe('TeamsAddMemberProps (definition/rest/v1)', () => { }); it('should return true if members with role is provided and teamId is provided', () => { - assert.isTrue(isTeamsAddMembersProps({ members: [{ userId: '123', roles: ['123'] }], teamId: '123' })); + assert.isTrue( + isTeamsAddMembersProps({ members: [{ userId: '123', roles: ['123'] }], teamId: '123' }), + ); }); it('should return true if members with role is provided and teamName is provided', () => { - assert.isTrue(isTeamsAddMembersProps({ members: [{ userId: '123', roles: ['123'] }], teamName: '123' })); + assert.isTrue( + isTeamsAddMembersProps({ members: [{ userId: '123', roles: ['123'] }], teamName: '123' }), + ); }); it('should return false if teamName was provided and members contains an invalid property', () => { - assert.isFalse(isTeamsAddMembersProps({ teamName: '123', members: [{ userId: '123', roles: ['123'], invalid: true }] })); + assert.isFalse( + isTeamsAddMembersProps({ + teamName: '123', + members: [{ userId: '123', roles: ['123'], invalid: true }], + }), + ); }); it('should return false if teamId was provided and members contains an invalid property', () => { - assert.isFalse(isTeamsAddMembersProps({ teamId: '123', members: [{ userId: '123', roles: ['123'], invalid: true }] })); + assert.isFalse( + isTeamsAddMembersProps({ + teamId: '123', + members: [{ userId: '123', roles: ['123'], invalid: true }], + }), + ); }); it('should return false if teamName informed but contains an invalid property', () => { - assert.isFalse(isTeamsAddMembersProps({ member: [{ userId: '123', roles: ['123'] }], teamName: '123', invalid: true })); + assert.isFalse( + isTeamsAddMembersProps({ + member: [{ userId: '123', roles: ['123'] }], + teamName: '123', + invalid: true, + }), + ); }); it('should return false if teamId informed but contains an invalid property', () => { - assert.isFalse(isTeamsAddMembersProps({ member: [{ userId: '123', roles: ['123'] }], teamId: '123', invalid: true })); + assert.isFalse( + isTeamsAddMembersProps({ + member: [{ userId: '123', roles: ['123'] }], + teamId: '123', + invalid: true, + }), + ); }); }); }); diff --git a/definition/rest/v1/teams/TeamsAddMembersProps.test.ts b/definition/rest/v1/teams/TeamsAddMembersProps.test.ts deleted file mode 100644 index f2d7ec71bc25f..0000000000000 --- a/definition/rest/v1/teams/TeamsAddMembersProps.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* eslint-env mocha */ -import chai from 'chai'; - -import { isTeamsAddMembersProps } from './TeamsAddMembersProps'; - -describe('TeamsAddMemberProps (definition/rest/v1)', () => { - describe('isTeamsAddMembersProps', () => { - it('should be a function', () => { - chai.assert.isFunction(isTeamsAddMembersProps); - }); - it('should return false if the parameter is empty', () => { - chai.assert.isFalse(isTeamsAddMembersProps({})); - }); - - it('should return false if teamId is provided but no member was provided', () => { - chai.assert.isFalse(isTeamsAddMembersProps({ teamId: '123' })); - }); - - it('should return false if teamName is provided but no member was provided', () => { - chai.assert.isFalse(isTeamsAddMembersProps({ teamName: '123' })); - }); - - it('should return false if members is provided but no teamId or teamName were provided', () => { - chai.assert.isFalse(isTeamsAddMembersProps({ members: [{ userId: '123' }] })); - }); - - it('should return false if teamName was provided but members are empty', () => { - chai.assert.isFalse(isTeamsAddMembersProps({ teamName: '123', members: [] })); - }); - - it('should return false if teamId was provided but members are empty', () => { - chai.assert.isFalse(isTeamsAddMembersProps({ teamId: '123', members: [] })); - }); - - it('should return false if members with role is provided but no teamId or teamName were provided', () => { - chai.assert.isFalse(isTeamsAddMembersProps({ members: [{ userId: '123', roles: ['123'] }] })); - }); - - it('should return true if members is provided and teamId is provided', () => { - chai.assert.isTrue(isTeamsAddMembersProps({ members: [{ userId: '123' }], teamId: '123' })); - }); - - it('should return true if members is provided and teamName is provided', () => { - chai.assert.isTrue(isTeamsAddMembersProps({ members: [{ userId: '123' }], teamName: '123' })); - }); - - it('should return true if members with role is provided and teamId is provided', () => { - chai.assert.isTrue(isTeamsAddMembersProps({ members: [{ userId: '123', roles: ['123'] }], teamId: '123' })); - }); - - it('should return true if members with role is provided and teamName is provided', () => { - chai.assert.isTrue(isTeamsAddMembersProps({ members: [{ userId: '123', roles: ['123'] }], teamName: '123' })); - }); - - it('should return false if teamName was provided and members contains an invalid property', () => { - chai.assert.isFalse(isTeamsAddMembersProps({ teamName: '123', members: [{ userId: '123', roles: ['123'], invalid: true }] })); - }); - - it('should return false if teamId was provided and members contains an invalid property', () => { - chai.assert.isFalse(isTeamsAddMembersProps({ teamId: '123', members: [{ userId: '123', roles: ['123'], invalid: true }] })); - }); - - it('should return false if teamName informed but contains an invalid property', () => { - chai.assert.isFalse(isTeamsAddMembersProps({ member: [{ userId: '123', roles: ['123'] }], teamName: '123', invalid: true })); - }); - - it('should return false if teamId informed but contains an invalid property', () => { - chai.assert.isFalse(isTeamsAddMembersProps({ member: [{ userId: '123', roles: ['123'] }], teamId: '123', invalid: true })); - }); - }); -}); diff --git a/definition/rest/v1/teams/TeamsAddMembersProps.ts b/definition/rest/v1/teams/TeamsAddMembersProps.ts index 599989fbf7af2..dfcf7da70f871 100644 --- a/definition/rest/v1/teams/TeamsAddMembersProps.ts +++ b/definition/rest/v1/teams/TeamsAddMembersProps.ts @@ -4,7 +4,9 @@ import { ITeamMemberParams } from '../../../../server/sdk/types/ITeamService'; const ajv = new Ajv(); -export type TeamsAddMembersProps = ({ teamId: string } | { teamName: string }) & { members: ITeamMemberParams[] }; +export type TeamsAddMembersProps = ({ teamId: string } | { teamName: string }) & { + members: ITeamMemberParams[]; +}; const teamsAddMembersPropsSchema: JSONSchemaType = { oneOf: [ @@ -17,7 +19,6 @@ const teamsAddMembersPropsSchema: JSONSchemaType = { members: { type: 'array', items: { - type: 'object', properties: { userId: { @@ -50,7 +51,6 @@ const teamsAddMembersPropsSchema: JSONSchemaType = { members: { type: 'array', items: { - type: 'object', properties: { userId: { diff --git a/definition/rest/v1/teams/TeamsConvertToChannelProps.spec.ts b/definition/rest/v1/teams/TeamsConvertToChannelProps.spec.ts index 0222e0acfe1c5..2167daf649912 100644 --- a/definition/rest/v1/teams/TeamsConvertToChannelProps.spec.ts +++ b/definition/rest/v1/teams/TeamsConvertToChannelProps.spec.ts @@ -32,7 +32,12 @@ describe('TeamsConvertToChannelProps (definition/rest/v1)', () => { }); it('should return false if an additionalProperties is provided', () => { - assert.isFalse(isTeamsConvertToChannelProps({ teamName: 'teamName', additionalProperties: 'additionalProperties' })); + assert.isFalse( + isTeamsConvertToChannelProps({ + teamName: 'teamName', + additionalProperties: 'additionalProperties', + }), + ); }); }); }); diff --git a/definition/rest/v1/teams/TeamsConvertToChannelProps.test.ts b/definition/rest/v1/teams/TeamsConvertToChannelProps.test.ts deleted file mode 100644 index 5c292c6f1b308..0000000000000 --- a/definition/rest/v1/teams/TeamsConvertToChannelProps.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* eslint-env mocha */ -import chai from 'chai'; - -import { isTeamsConvertToChannelProps } from './TeamsConvertToChannelProps'; - -describe('TeamsConvertToChannelProps (definition/rest/v1)', () => { - describe('isTeamsConvertToChannelProps', () => { - it('should be a function', () => { - chai.assert.isFunction(isTeamsConvertToChannelProps); - }); - it('should return false if neither teamName or teamId is provided', () => { - chai.assert.isFalse(isTeamsConvertToChannelProps({})); - }); - - it('should return true if teamName is provided', () => { - chai.assert.isTrue(isTeamsConvertToChannelProps({ teamName: 'teamName' })); - }); - - it('should return true if teamId is provided', () => { - chai.assert.isTrue(isTeamsConvertToChannelProps({ teamId: 'teamId' })); - }); - - it('should return false if both teamName and teamId are provided', () => { - chai.assert.isFalse(isTeamsConvertToChannelProps({ teamName: 'teamName', teamId: 'teamId' })); - }); - - it('should return false if teamName is not a string', () => { - chai.assert.isFalse(isTeamsConvertToChannelProps({ teamName: 1 })); - }); - - it('should return false if teamId is not a string', () => { - chai.assert.isFalse(isTeamsConvertToChannelProps({ teamId: 1 })); - }); - - it('should return false if an additionalProperties is provided', () => { - chai.assert.isFalse(isTeamsConvertToChannelProps({ teamName: 'teamName', additionalProperties: 'additionalProperties' })); - }); - }); -}); diff --git a/definition/rest/v1/teams/TeamsConvertToChannelProps.ts b/definition/rest/v1/teams/TeamsConvertToChannelProps.ts index af93a57020938..53455e986622d 100644 --- a/definition/rest/v1/teams/TeamsConvertToChannelProps.ts +++ b/definition/rest/v1/teams/TeamsConvertToChannelProps.ts @@ -1,6 +1,5 @@ import Ajv, { JSONSchemaType } from 'ajv'; - const ajv = new Ajv(); export type TeamsConvertToChannelProps = { @@ -24,9 +23,7 @@ const teamsConvertToTeamsPropsSchema: JSONSchemaType type: 'string', }, }, - required: [ - 'teamId', - ], + required: ['teamId'], additionalProperties: false, }, { @@ -43,9 +40,7 @@ const teamsConvertToTeamsPropsSchema: JSONSchemaType type: 'string', }, }, - required: [ - 'teamName', - ], + required: ['teamName'], additionalProperties: false, }, ], diff --git a/definition/rest/v1/teams/TeamsDeleteProps.spec.ts b/definition/rest/v1/teams/TeamsDeleteProps.spec.ts index aa5fbfa287c82..ee1cae3585457 100644 --- a/definition/rest/v1/teams/TeamsDeleteProps.spec.ts +++ b/definition/rest/v1/teams/TeamsDeleteProps.spec.ts @@ -53,11 +53,19 @@ describe('TeamsDeleteProps (definition/rest/v1)', () => { }); it('should return false if teamName and rooms are provided but an extra property is provided', () => { - assert.isFalse(isTeamsDeleteProps({ teamName: 'teamName', roomsToRemove: ['roomsToRemove'], extra: 'extra' })); + assert.isFalse( + isTeamsDeleteProps({ + teamName: 'teamName', + roomsToRemove: ['roomsToRemove'], + extra: 'extra', + }), + ); }); it('should return false if teamId and rooms are provided but an extra property is provided', () => { - assert.isFalse(isTeamsDeleteProps({ teamId: 'teamId', roomsToRemove: ['roomsToRemove'], extra: 'extra' })); + assert.isFalse( + isTeamsDeleteProps({ teamId: 'teamId', roomsToRemove: ['roomsToRemove'], extra: 'extra' }), + ); }); }); }); diff --git a/definition/rest/v1/teams/TeamsDeleteProps.test.ts b/definition/rest/v1/teams/TeamsDeleteProps.test.ts deleted file mode 100644 index 9efc17cd1dec1..0000000000000 --- a/definition/rest/v1/teams/TeamsDeleteProps.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* eslint-env mocha */ -import chai from 'chai'; - -import { isTeamsDeleteProps } from './TeamsDeleteProps'; - -describe('TeamsDeleteProps (definition/rest/v1)', () => { - describe('isTeamsDeleteProps', () => { - it('should be a function', () => { - chai.assert.isFunction(isTeamsDeleteProps); - }); - - it('should return false if neither teamName or teamId is provided', () => { - chai.assert.isFalse(isTeamsDeleteProps({})); - }); - - it('should return true if teamId is provided', () => { - chai.assert.isTrue(isTeamsDeleteProps({ teamId: 'teamId' })); - }); - - it('should return true if teamName is provided', () => { - chai.assert.isTrue(isTeamsDeleteProps({ teamName: 'teamName' })); - }); - - it('should return false if teamId and roomsToRemove are provided, but roomsToRemove is empty', () => { - chai.assert.isFalse(isTeamsDeleteProps({ teamId: 'teamId', roomsToRemove: [] })); - }); - - it('should return false if teamName and roomsToRemove are provided, but roomsToRemove is empty', () => { - chai.assert.isFalse(isTeamsDeleteProps({ teamName: 'teamName', roomsToRemove: [] })); - }); - - it('should return true if teamId and roomsToRemove are provided', () => { - chai.assert.isTrue(isTeamsDeleteProps({ teamId: 'teamId', roomsToRemove: ['roomId'] })); - }); - - it('should return true if teamName and roomsToRemove are provided', () => { - chai.assert.isTrue(isTeamsDeleteProps({ teamName: 'teamName', roomsToRemove: ['roomId'] })); - }); - - it('should return false if teamId and roomsToRemove are provided, but roomsToRemove is not an array', () => { - chai.assert.isFalse(isTeamsDeleteProps({ teamId: 'teamId', roomsToRemove: {} })); - }); - - it('should return false if teamName and roomsToRemove are provided, but roomsToRemove is not an array', () => { - chai.assert.isFalse(isTeamsDeleteProps({ teamName: 'teamName', roomsToRemove: {} })); - }); - - it('should return false if teamId and roomsToRemove are provided, but roomsToRemove is not an array of strings', () => { - chai.assert.isFalse(isTeamsDeleteProps({ teamId: 'teamId', roomsToRemove: [1] })); - }); - - it('should return false if teamName and roomsToRemove are provided, but roomsToRemove is not an array of strings', () => { - chai.assert.isFalse(isTeamsDeleteProps({ teamName: 'teamName', roomsToRemove: [1] })); - }); - - it('should return false if teamName and rooms are provided but an extra property is provided', () => { - chai.assert.isFalse(isTeamsDeleteProps({ teamName: 'teamName', roomsToRemove: ['roomsToRemove'], extra: 'extra' })); - }); - - it('should return false if teamId and rooms are provided but an extra property is provided', () => { - chai.assert.isFalse(isTeamsDeleteProps({ teamId: 'teamId', roomsToRemove: ['roomsToRemove'], extra: 'extra' })); - }); - }); -}); diff --git a/definition/rest/v1/teams/TeamsDeleteProps.ts b/definition/rest/v1/teams/TeamsDeleteProps.ts index 22582488d0aca..8e194032746d7 100644 --- a/definition/rest/v1/teams/TeamsDeleteProps.ts +++ b/definition/rest/v1/teams/TeamsDeleteProps.ts @@ -2,7 +2,9 @@ import Ajv, { JSONSchemaType } from 'ajv'; const ajv = new Ajv(); -export type TeamsDeleteProps = ({ teamId: string } | { teamName: string }) & { roomsToRemove?: string[] }; +export type TeamsDeleteProps = ({ teamId: string } | { teamName: string }) & { + roomsToRemove?: string[]; +}; const teamsDeletePropsSchema: JSONSchemaType = { oneOf: [ diff --git a/definition/rest/v1/teams/TeamsLeaveProps.test.ts b/definition/rest/v1/teams/TeamsLeaveProps.test.ts deleted file mode 100644 index 1b6255d979706..0000000000000 --- a/definition/rest/v1/teams/TeamsLeaveProps.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* eslint-env mocha */ -import chai from 'chai'; - -import { isTeamsLeaveProps } from './TeamsLeaveProps'; - -describe('TeamsLeaveProps (definition/rest/v1)', () => { - describe('isTeamsLeaveProps', () => { - it('should be a function', () => { - chai.assert.isFunction(isTeamsLeaveProps); - }); - - it('should return false if neither teamName or teamId is provided', () => { - chai.assert.isFalse(isTeamsLeaveProps({})); - }); - - it('should return true if teamId is provided', () => { - chai.assert.isTrue(isTeamsLeaveProps({ teamId: 'teamId' })); - }); - - it('should return true if teamName is provided', () => { - chai.assert.isTrue(isTeamsLeaveProps({ teamName: 'teamName' })); - }); - - it('should return false if teamId and roomsToRemove are provided, but roomsToRemove is empty', () => { - chai.assert.isFalse(isTeamsLeaveProps({ teamId: 'teamId', rooms: [] })); - }); - - it('should return false if teamName and rooms are provided, but rooms is empty', () => { - chai.assert.isFalse(isTeamsLeaveProps({ teamName: 'teamName', rooms: [] })); - }); - - it('should return true if teamId and rooms are provided', () => { - chai.assert.isTrue(isTeamsLeaveProps({ teamId: 'teamId', rooms: ['roomId'] })); - }); - - it('should return true if teamName and rooms are provided', () => { - chai.assert.isTrue(isTeamsLeaveProps({ teamName: 'teamName', rooms: ['roomId'] })); - }); - - it('should return false if teamId and rooms are provided, but rooms is not an array', () => { - chai.assert.isFalse(isTeamsLeaveProps({ teamId: 'teamId', rooms: {} })); - }); - - it('should return false if teamName and rooms are provided, but rooms is not an array', () => { - chai.assert.isFalse(isTeamsLeaveProps({ teamName: 'teamName', rooms: {} })); - }); - - it('should return false if teamId and rooms are provided, but rooms is not an array of strings', () => { - chai.assert.isFalse(isTeamsLeaveProps({ teamId: 'teamId', rooms: [1] })); - }); - - it('should return false if teamName and rooms are provided, but rooms is not an array of strings', () => { - chai.assert.isFalse(isTeamsLeaveProps({ teamName: 'teamName', rooms: [1] })); - }); - - it('should return false if teamName and rooms are provided but an extra property is provided', () => { - chai.assert.isFalse(isTeamsLeaveProps({ teamName: 'teamName', rooms: ['rooms'], extra: 'extra' })); - }); - - it('should return false if teamId and rooms are provided but an extra property is provided', () => { - chai.assert.isFalse(isTeamsLeaveProps({ teamId: 'teamId', rooms: ['rooms'], extra: 'extra' })); - }); - }); -}); diff --git a/definition/rest/v1/teams/TeamsLeaveProps.ts b/definition/rest/v1/teams/TeamsLeaveProps.ts index ac526886237bd..edda273d93c26 100644 --- a/definition/rest/v1/teams/TeamsLeaveProps.ts +++ b/definition/rest/v1/teams/TeamsLeaveProps.ts @@ -1,6 +1,5 @@ import Ajv, { JSONSchemaType } from 'ajv'; - const ajv = new Ajv(); export type TeamsLeaveProps = ({ teamId: string } | { teamName: string }) & { rooms?: string[] }; diff --git a/definition/rest/v1/teams/TeamsRemoveMemberProps.spec.ts b/definition/rest/v1/teams/TeamsRemoveMemberProps.spec.ts index 2d3afdaac3346..351e9a9f474b3 100644 --- a/definition/rest/v1/teams/TeamsRemoveMemberProps.spec.ts +++ b/definition/rest/v1/teams/TeamsRemoveMemberProps.spec.ts @@ -24,9 +24,10 @@ describe('Teams (definition/rest/v1)', () => { assert.isTrue(isTeamsRemoveMemberProps({ teamName: 'teamName', userId: 'userId' })); }); - it('should return false if teamName and userId are informed but rooms are empty', () => { - assert.isFalse(isTeamsRemoveMemberProps({ teamName: 'teamName', userId: 'userId', rooms: [] })); + assert.isFalse( + isTeamsRemoveMemberProps({ teamName: 'teamName', userId: 'userId', rooms: [] }), + ); }); it('should return false if teamId and userId are informed and rooms are empty', () => { @@ -38,23 +39,48 @@ describe('Teams (definition/rest/v1)', () => { }); it('should return true if teamId and userId are informed and rooms are informed', () => { - assert.isTrue(isTeamsRemoveMemberProps({ teamId: 'teamId', userId: 'userId', rooms: ['room'] })); + assert.isTrue( + isTeamsRemoveMemberProps({ teamId: 'teamId', userId: 'userId', rooms: ['room'] }), + ); }); it('should return false if teamId and userId are informed and rooms are informed but rooms is not an array of strings', () => { - assert.isFalse(isTeamsRemoveMemberProps({ teamId: 'teamId', userId: 'userId', rooms: [123] })); + assert.isFalse( + isTeamsRemoveMemberProps({ teamId: 'teamId', userId: 'userId', rooms: [123] }), + ); }); it('should return false if teamName and userId are informed and rooms are informed but there is an extra property', () => { - assert.isFalse(isTeamsRemoveMemberProps({ teamName: 'teamName', userId: 'userId', rooms: ['room'], extra: 'extra' })); + assert.isFalse( + isTeamsRemoveMemberProps({ + teamName: 'teamName', + userId: 'userId', + rooms: ['room'], + extra: 'extra', + }), + ); }); it('should return false if teamId and userId are informed and rooms are informed but there is an extra property', () => { - assert.isFalse(isTeamsRemoveMemberProps({ teamId: 'teamId', userId: 'userId', rooms: ['room'], extra: 'extra' })); + assert.isFalse( + isTeamsRemoveMemberProps({ + teamId: 'teamId', + userId: 'userId', + rooms: ['room'], + extra: 'extra', + }), + ); }); it('should return false if teamName and userId are informed and rooms are informed but there is an extra property', () => { - assert.isFalse(isTeamsRemoveMemberProps({ teamName: 'teamName', userId: 'userId', rooms: ['room'], extra: 'extra' })); + assert.isFalse( + isTeamsRemoveMemberProps({ + teamName: 'teamName', + userId: 'userId', + rooms: ['room'], + extra: 'extra', + }), + ); }); }); }); diff --git a/definition/rest/v1/teams/TeamsRemoveMemberProps.test.ts b/definition/rest/v1/teams/TeamsRemoveMemberProps.test.ts deleted file mode 100644 index f66c765e03480..0000000000000 --- a/definition/rest/v1/teams/TeamsRemoveMemberProps.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* eslint-env mocha */ -import chai from 'chai'; - -import { isTeamsRemoveMemberProps } from './TeamsRemoveMemberProps'; - -describe('Teams (definition/rest/v1)', () => { - describe('isTeamsRemoveMemberProps', () => { - it('should be a function', () => { - chai.assert.isFunction(isTeamsRemoveMemberProps); - }); - it('should return false if parameter is empty', () => { - chai.assert.isFalse(isTeamsRemoveMemberProps({})); - }); - it('should return false if teamId is is informed but missing userId', () => { - chai.assert.isFalse(isTeamsRemoveMemberProps({ teamId: 'teamId' })); - }); - it('should return false if teamName is is informed but missing userId', () => { - chai.assert.isFalse(isTeamsRemoveMemberProps({ teamName: 'teamName' })); - }); - - it('should return true if teamId and userId are informed', () => { - chai.assert.isTrue(isTeamsRemoveMemberProps({ teamId: 'teamId', userId: 'userId' })); - }); - it('should return true if teamName and userId are informed', () => { - chai.assert.isTrue(isTeamsRemoveMemberProps({ teamName: 'teamName', userId: 'userId' })); - }); - - - it('should return false if teamName and userId are informed but rooms are empty', () => { - chai.assert.isFalse(isTeamsRemoveMemberProps({ teamName: 'teamName', userId: 'userId', rooms: [] })); - }); - - it('should return false if teamId and userId are informed and rooms are empty', () => { - chai.assert.isFalse(isTeamsRemoveMemberProps({ teamId: 'teamId', userId: 'userId', rooms: [] })); - }); - - it('should return false if teamId and userId are informed but rooms are empty', () => { - chai.assert.isFalse(isTeamsRemoveMemberProps({ teamId: 'teamId', userId: 'userId', rooms: [] })); - }); - - it('should return true if teamId and userId are informed and rooms are informed', () => { - chai.assert.isTrue(isTeamsRemoveMemberProps({ teamId: 'teamId', userId: 'userId', rooms: ['room'] })); - }); - - it('should return false if teamId and userId are informed and rooms are informed but rooms is not an array of strings', () => { - chai.assert.isFalse(isTeamsRemoveMemberProps({ teamId: 'teamId', userId: 'userId', rooms: [123] })); - }); - - it('should return false if teamName and userId are informed and rooms are informed but there is an extra property', () => { - chai.assert.isFalse(isTeamsRemoveMemberProps({ teamName: 'teamName', userId: 'userId', rooms: ['room'], extra: 'extra' })); - }); - - it('should return false if teamId and userId are informed and rooms are informed but there is an extra property', () => { - chai.assert.isFalse(isTeamsRemoveMemberProps({ teamId: 'teamId', userId: 'userId', rooms: ['room'], extra: 'extra' })); - }); - - it('should return false if teamName and userId are informed and rooms are informed but there is an extra property', () => { - chai.assert.isFalse(isTeamsRemoveMemberProps({ teamName: 'teamName', userId: 'userId', rooms: ['room'], extra: 'extra' })); - }); - }); -}); diff --git a/definition/rest/v1/teams/TeamsRemoveMemberProps.ts b/definition/rest/v1/teams/TeamsRemoveMemberProps.ts index 7518fd351c8e8..04de75b1d3feb 100644 --- a/definition/rest/v1/teams/TeamsRemoveMemberProps.ts +++ b/definition/rest/v1/teams/TeamsRemoveMemberProps.ts @@ -2,7 +2,10 @@ import Ajv, { JSONSchemaType } from 'ajv'; const ajv = new Ajv(); -export type TeamsRemoveMemberProps = ({ teamId: string } | { teamName: string }) & { userId: string; rooms?: Array }; +export type TeamsRemoveMemberProps = ({ teamId: string } | { teamName: string }) & { + userId: string; + rooms?: Array; +}; const teamsRemoveMemberPropsSchema: JSONSchemaType = { oneOf: [ diff --git a/definition/rest/v1/teams/TeamsRemoveRoomProps.spec.ts b/definition/rest/v1/teams/TeamsRemoveRoomProps.spec.ts index 4a6a065831d7d..a6f9dc22e1096 100644 --- a/definition/rest/v1/teams/TeamsRemoveRoomProps.spec.ts +++ b/definition/rest/v1/teams/TeamsRemoveRoomProps.spec.ts @@ -20,7 +20,9 @@ describe('TeamsRemoveRoomProps (definition/rest/v1)', () => { assert.isTrue(isTeamsRemoveRoomProps({ roomId: 'roomId', teamName: 'teamName' })); }); it('should return false if roomId and teamName are provided but an additional property is provided', () => { - assert.isFalse(isTeamsRemoveRoomProps({ roomId: 'roomId', teamName: 'teamName', foo: 'bar' })); + assert.isFalse( + isTeamsRemoveRoomProps({ roomId: 'roomId', teamName: 'teamName', foo: 'bar' }), + ); }); }); }); diff --git a/definition/rest/v1/teams/TeamsRemoveRoomProps.test.ts b/definition/rest/v1/teams/TeamsRemoveRoomProps.test.ts deleted file mode 100644 index 226185dcc9553..0000000000000 --- a/definition/rest/v1/teams/TeamsRemoveRoomProps.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* eslint-env mocha */ -import chai from 'chai'; - -import { isTeamsRemoveRoomProps } from './TeamsRemoveRoomProps'; - -describe('TeamsRemoveRoomProps (definition/rest/v1)', () => { - describe('isTeamsRemoveRoomProps', () => { - it('should be a function', () => { - chai.assert.isFunction(isTeamsRemoveRoomProps); - }); - it('should return false if roomId is not provided', () => { - chai.assert.isFalse(isTeamsRemoveRoomProps({})); - }); - it('should return false if roomId is provided but no teamId or teamName were provided', () => { - chai.assert.isFalse(isTeamsRemoveRoomProps({ roomId: 'roomId' })); - }); - it('should return false if roomId is provided and teamId is provided', () => { - chai.assert.isTrue(isTeamsRemoveRoomProps({ roomId: 'roomId', teamId: 'teamId' })); - }); - it('should return true if roomId is provided and teamName is provided', () => { - chai.assert.isTrue(isTeamsRemoveRoomProps({ roomId: 'roomId', teamName: 'teamName' })); - }); - it('should return false if roomId and teamName are provided but an additional property is provided', () => { - chai.assert.isFalse(isTeamsRemoveRoomProps({ roomId: 'roomId', teamName: 'teamName', foo: 'bar' })); - }); - }); -}); diff --git a/definition/rest/v1/teams/TeamsRemoveRoomProps.ts b/definition/rest/v1/teams/TeamsRemoveRoomProps.ts index a41db5538898b..643799d3cf6ec 100644 --- a/definition/rest/v1/teams/TeamsRemoveRoomProps.ts +++ b/definition/rest/v1/teams/TeamsRemoveRoomProps.ts @@ -4,7 +4,9 @@ import type { IRoom } from '../../../IRoom'; const ajv = new Ajv(); -export type TeamsRemoveRoomProps = ({ teamId: string } | { teamName: string }) & { roomId: IRoom['_id'] }; +export type TeamsRemoveRoomProps = ({ teamId: string } | { teamName: string }) & { + roomId: IRoom['_id']; +}; export const teamsRemoveRoomPropsSchema: JSONSchemaType = { oneOf: [ diff --git a/definition/rest/v1/teams/TeamsUpdateMemberProps.spec.ts b/definition/rest/v1/teams/TeamsUpdateMemberProps.spec.ts index 8e25f1516059a..838a9f95ca027 100644 --- a/definition/rest/v1/teams/TeamsUpdateMemberProps.spec.ts +++ b/definition/rest/v1/teams/TeamsUpdateMemberProps.spec.ts @@ -36,23 +36,37 @@ describe('TeamsUpdateMemberProps (definition/rest/v1)', () => { }); it('should return true if member with role is provided and teamId is provided', () => { - assert.isTrue(isTeamsUpdateMemberProps({ member: { userId: '123', roles: ['123'] }, teamId: '123' })); + assert.isTrue( + isTeamsUpdateMemberProps({ member: { userId: '123', roles: ['123'] }, teamId: '123' }), + ); }); it('should return true if member with role is provided and teamName is provided', () => { - assert.isTrue(isTeamsUpdateMemberProps({ member: { userId: '123', roles: ['123'] }, teamName: '123' })); + assert.isTrue( + isTeamsUpdateMemberProps({ member: { userId: '123', roles: ['123'] }, teamName: '123' }), + ); }); it('should return false if teamName was provided and member contains an invalid property', () => { - assert.isFalse(isTeamsUpdateMemberProps({ member: { userId: '123', invalid: '123' }, teamName: '123' })); + assert.isFalse( + isTeamsUpdateMemberProps({ member: { userId: '123', invalid: '123' }, teamName: '123' }), + ); }); it('should return false if teamId was provided and member contains an invalid property', () => { - assert.isFalse(isTeamsUpdateMemberProps({ member: { userId: '123', invalid: '123' }, teamId: '123' })); + assert.isFalse( + isTeamsUpdateMemberProps({ member: { userId: '123', invalid: '123' }, teamId: '123' }), + ); }); it('should return false if contains an invalid property', () => { - assert.isFalse(isTeamsUpdateMemberProps({ member: { userId: '123', roles: ['123'] }, teamName: '123', invalid: true })); + assert.isFalse( + isTeamsUpdateMemberProps({ + member: { userId: '123', roles: ['123'] }, + teamName: '123', + invalid: true, + }), + ); }); }); }); diff --git a/definition/rest/v1/teams/TeamsUpdateMemberProps.test.ts b/definition/rest/v1/teams/TeamsUpdateMemberProps.test.ts deleted file mode 100644 index ed6a329772a4d..0000000000000 --- a/definition/rest/v1/teams/TeamsUpdateMemberProps.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* eslint-env mocha */ -import chai from 'chai'; - -import { isTeamsUpdateMemberProps } from './TeamsUpdateMemberProps'; - -describe('TeamsUpdateMemberProps (definition/rest/v1)', () => { - describe('isTeamsUpdateMemberProps', () => { - it('should be a function', () => { - chai.assert.isFunction(isTeamsUpdateMemberProps); - }); - it('should return false if the parameter is empty', () => { - chai.assert.isFalse(isTeamsUpdateMemberProps({})); - }); - - it('should return false if teamId is provided but no member was provided', () => { - chai.assert.isFalse(isTeamsUpdateMemberProps({ teamId: '123' })); - }); - - it('should return false if teamName is provided but no member was provided', () => { - chai.assert.isFalse(isTeamsUpdateMemberProps({ teamName: '123' })); - }); - - it('should return false if member is provided but no teamId or teamName were provided', () => { - chai.assert.isFalse(isTeamsUpdateMemberProps({ member: { userId: '123' } })); - }); - - it('should return false if member with role is provided but no teamId or teamName were provided', () => { - chai.assert.isFalse(isTeamsUpdateMemberProps({ member: { userId: '123', roles: ['123'] } })); - }); - - it('should return true if member is provided and teamId is provided', () => { - chai.assert.isTrue(isTeamsUpdateMemberProps({ member: { userId: '123' }, teamId: '123' })); - }); - - it('should return true if member is provided and teamName is provided', () => { - chai.assert.isTrue(isTeamsUpdateMemberProps({ member: { userId: '123' }, teamName: '123' })); - }); - - it('should return true if member with role is provided and teamId is provided', () => { - chai.assert.isTrue(isTeamsUpdateMemberProps({ member: { userId: '123', roles: ['123'] }, teamId: '123' })); - }); - - it('should return true if member with role is provided and teamName is provided', () => { - chai.assert.isTrue(isTeamsUpdateMemberProps({ member: { userId: '123', roles: ['123'] }, teamName: '123' })); - }); - - it('should return false if teamName was provided and member contains an invalid property', () => { - chai.assert.isFalse(isTeamsUpdateMemberProps({ member: { userId: '123', invalid: '123' }, teamName: '123' })); - }); - - it('should return false if teamId was provided and member contains an invalid property', () => { - chai.assert.isFalse(isTeamsUpdateMemberProps({ member: { userId: '123', invalid: '123' }, teamId: '123' })); - }); - - it('should return false if contains an invalid property', () => { - chai.assert.isFalse(isTeamsUpdateMemberProps({ member: { userId: '123', roles: ['123'] }, teamName: '123', invalid: true })); - }); - }); -}); diff --git a/definition/rest/v1/teams/TeamsUpdateMemberProps.ts b/definition/rest/v1/teams/TeamsUpdateMemberProps.ts index 5a65fc6238ffd..a107bc4ce83ba 100644 --- a/definition/rest/v1/teams/TeamsUpdateMemberProps.ts +++ b/definition/rest/v1/teams/TeamsUpdateMemberProps.ts @@ -4,7 +4,9 @@ import { ITeamMemberParams } from '../../../../server/sdk/types/ITeamService'; const ajv = new Ajv(); -export type TeamsUpdateMemberProps = ({ teamId: string } | { teamName: string }) & { member: ITeamMemberParams }; +export type TeamsUpdateMemberProps = ({ teamId: string } | { teamName: string }) & { + member: ITeamMemberParams; +}; const teamsUpdateMemberPropsSchema: JSONSchemaType = { oneOf: [ diff --git a/definition/rest/v1/teams/TeamsUpdateProps.spec.ts b/definition/rest/v1/teams/TeamsUpdateProps.spec.ts index 8f8fdb8b072f2..97bbca49a986d 100644 --- a/definition/rest/v1/teams/TeamsUpdateProps.spec.ts +++ b/definition/rest/v1/teams/TeamsUpdateProps.spec.ts @@ -18,143 +18,175 @@ describe('TeamsUpdateMemberProps (definition/rest/v1)', () => { assert.isFalse(isTeamsUpdateProps(new Error())); }); it('should return false when only teamName is provided to TeamsUpdateProps', () => { - assert.isFalse(isTeamsUpdateProps({ - teamName: 'teamName', - })); + assert.isFalse( + isTeamsUpdateProps({ + teamName: 'teamName', + }), + ); }); it('should return false when only teamId is provided to TeamsUpdateProps', () => { - assert.isFalse(isTeamsUpdateProps({ - teamId: 'teamId', - })); + assert.isFalse( + isTeamsUpdateProps({ + teamId: 'teamId', + }), + ); }); it('should return false when teamName and data are provided to TeamsUpdateProps but data is an empty object', () => { - assert.isFalse(isTeamsUpdateProps({ - teamName: 'teamName', - data: {}, - })); + assert.isFalse( + isTeamsUpdateProps({ + teamName: 'teamName', + data: {}, + }), + ); }); it('should return false when teamId and data are provided to TeamsUpdateProps but data is an empty object', () => { - assert.isFalse(isTeamsUpdateProps({ - teamId: 'teamId', - data: {}, - })); + assert.isFalse( + isTeamsUpdateProps({ + teamId: 'teamId', + data: {}, + }), + ); }); it('should return false when teamName and data are provided to TeamsUpdateProps but data is not an object', () => { - assert.isFalse(isTeamsUpdateProps({ - teamName: 'teamName', - data: 'data', - })); + assert.isFalse( + isTeamsUpdateProps({ + teamName: 'teamName', + data: 'data', + }), + ); }); it('should return false when teamId and data are provided to TeamsUpdateProps but data is not an object', () => { - assert.isFalse(isTeamsUpdateProps({ - teamId: 'teamId', - data: 'data', - })); + assert.isFalse( + isTeamsUpdateProps({ + teamId: 'teamId', + data: 'data', + }), + ); }); it('should return true when teamName and data.name are provided to TeamsUpdateProps', () => { - assert.isTrue(isTeamsUpdateProps({ - teamName: 'teamName', - data: { - name: 'name', - }, - })); + assert.isTrue( + isTeamsUpdateProps({ + teamName: 'teamName', + data: { + name: 'name', + }, + }), + ); }); it('should return true when teamId and data.name are provided to TeamsUpdateProps', () => { - assert.isTrue(isTeamsUpdateProps({ - teamId: 'teamId', - data: { - name: 'name', - }, - })); + assert.isTrue( + isTeamsUpdateProps({ + teamId: 'teamId', + data: { + name: 'name', + }, + }), + ); }); it('should return true when teamName and data.type are provided to TeamsUpdateProps', () => { - assert.isTrue(isTeamsUpdateProps({ - teamName: 'teamName', - data: { - type: 0, - }, - })); + assert.isTrue( + isTeamsUpdateProps({ + teamName: 'teamName', + data: { + type: 0, + }, + }), + ); }); it('should return true when teamId and data.type are provided to TeamsUpdateProps', () => { - assert.isTrue(isTeamsUpdateProps({ - teamId: 'teamId', - data: { - type: 0, - }, - })); + assert.isTrue( + isTeamsUpdateProps({ + teamId: 'teamId', + data: { + type: 0, + }, + }), + ); }); it('should return true when teamName and data.name and data.type are provided to TeamsUpdateProps', () => { - assert.isTrue(isTeamsUpdateProps({ - teamName: 'teamName', - data: { - name: 'name', - type: 0, - }, - })); + assert.isTrue( + isTeamsUpdateProps({ + teamName: 'teamName', + data: { + name: 'name', + type: 0, + }, + }), + ); }); it('should return true when teamId and data.name and data.type are provided to TeamsUpdateProps', () => { - assert.isTrue(isTeamsUpdateProps({ - teamId: 'teamId', - data: { - name: 'name', - type: 0, - }, - })); + assert.isTrue( + isTeamsUpdateProps({ + teamId: 'teamId', + data: { + name: 'name', + type: 0, + }, + }), + ); }); it('should return false when teamName, data.name, data.type are some more extra data are provided to TeamsUpdateProps', () => { - assert.isFalse(isTeamsUpdateProps({ - teamName: 'teamName', - data: { - name: 'name', - type: 0, - extra: 'extra', - }, - })); + assert.isFalse( + isTeamsUpdateProps({ + teamName: 'teamName', + data: { + name: 'name', + type: 0, + extra: 'extra', + }, + }), + ); }); it('should return false when teamId, data.name, data.type are some more extra data are provided to TeamsUpdateProps', () => { - assert.isFalse(isTeamsUpdateProps({ - teamId: 'teamId', - data: { - name: 'name', - type: 0, - extra: 'extra', - }, - })); + assert.isFalse( + isTeamsUpdateProps({ + teamId: 'teamId', + data: { + name: 'name', + type: 0, + extra: 'extra', + }, + }), + ); }); it('should return false when teamName, data.name, data.type are some more extra parameter are provided to TeamsUpdateProps', () => { - assert.isFalse(isTeamsUpdateProps({ - teamName: 'teamName', - extra: 'extra', - data: { - name: 'name', - type: 0, - }, - })); + assert.isFalse( + isTeamsUpdateProps({ + teamName: 'teamName', + extra: 'extra', + data: { + name: 'name', + type: 0, + }, + }), + ); }); it('should return false when teamId, data.name, data.type are some more extra parameter are provided to TeamsUpdateProps', () => { - assert.isFalse(isTeamsUpdateProps({ - teamId: 'teamId', - extra: 'extra', - data: { - name: 'name', - type: 0, - }, - })); + assert.isFalse( + isTeamsUpdateProps({ + teamId: 'teamId', + extra: 'extra', + data: { + name: 'name', + type: 0, + }, + }), + ); }); }); }); diff --git a/definition/rest/v1/teams/TeamsUpdateProps.test.ts b/definition/rest/v1/teams/TeamsUpdateProps.test.ts deleted file mode 100644 index ce09985901d1d..0000000000000 --- a/definition/rest/v1/teams/TeamsUpdateProps.test.ts +++ /dev/null @@ -1,161 +0,0 @@ -/* eslint-env mocha */ -import chai from 'chai'; - -import { isTeamsUpdateProps } from './TeamsUpdateProps'; - -describe('TeamsUpdateMemberProps (definition/rest/v1)', () => { - describe('isTeamsUpdateProps', () => { - it('should be a function', () => { - chai.assert.isFunction(isTeamsUpdateProps); - }); - it('should return false when provided anything that is not an TeamsUpdateProps', () => { - chai.assert.isFalse(isTeamsUpdateProps(undefined)); - chai.assert.isFalse(isTeamsUpdateProps(null)); - chai.assert.isFalse(isTeamsUpdateProps('')); - chai.assert.isFalse(isTeamsUpdateProps(123)); - chai.assert.isFalse(isTeamsUpdateProps({})); - chai.assert.isFalse(isTeamsUpdateProps([])); - chai.assert.isFalse(isTeamsUpdateProps(new Date())); - chai.assert.isFalse(isTeamsUpdateProps(new Error())); - }); - it('should return false when only teamName is provided to TeamsUpdateProps', () => { - chai.assert.isFalse(isTeamsUpdateProps({ - teamName: 'teamName', - })); - }); - - it('should return false when only teamId is provided to TeamsUpdateProps', () => { - chai.assert.isFalse(isTeamsUpdateProps({ - teamId: 'teamId', - })); - }); - - it('should return false when teamName and data are provided to TeamsUpdateProps but data is an empty object', () => { - chai.assert.isFalse(isTeamsUpdateProps({ - teamName: 'teamName', - data: {}, - })); - }); - - it('should return false when teamId and data are provided to TeamsUpdateProps but data is an empty object', () => { - chai.assert.isFalse(isTeamsUpdateProps({ - teamId: 'teamId', - data: {}, - })); - }); - - it('should return false when teamName and data are provided to TeamsUpdateProps but data is not an object', () => { - chai.assert.isFalse(isTeamsUpdateProps({ - teamName: 'teamName', - data: 'data', - })); - }); - - it('should return false when teamId and data are provided to TeamsUpdateProps but data is not an object', () => { - chai.assert.isFalse(isTeamsUpdateProps({ - teamId: 'teamId', - data: 'data', - })); - }); - - it('should return true when teamName and data.name are provided to TeamsUpdateProps', () => { - chai.assert.isTrue(isTeamsUpdateProps({ - teamName: 'teamName', - data: { - name: 'name', - }, - })); - }); - - it('should return true when teamId and data.name are provided to TeamsUpdateProps', () => { - chai.assert.isTrue(isTeamsUpdateProps({ - teamId: 'teamId', - data: { - name: 'name', - }, - })); - }); - - it('should return true when teamName and data.type are provided to TeamsUpdateProps', () => { - chai.assert.isTrue(isTeamsUpdateProps({ - teamName: 'teamName', - data: { - type: 0, - }, - })); - }); - - it('should return true when teamId and data.type are provided to TeamsUpdateProps', () => { - chai.assert.isTrue(isTeamsUpdateProps({ - teamId: 'teamId', - data: { - type: 0, - }, - })); - }); - - it('should return true when teamName and data.name and data.type are provided to TeamsUpdateProps', () => { - chai.assert.isTrue(isTeamsUpdateProps({ - teamName: 'teamName', - data: { - name: 'name', - type: 0, - }, - })); - }); - - it('should return true when teamId and data.name and data.type are provided to TeamsUpdateProps', () => { - chai.assert.isTrue(isTeamsUpdateProps({ - teamId: 'teamId', - data: { - name: 'name', - type: 0, - }, - })); - }); - - it('should return false when teamName, data.name, data.type are some more extra data are provided to TeamsUpdateProps', () => { - chai.assert.isFalse(isTeamsUpdateProps({ - teamName: 'teamName', - data: { - name: 'name', - type: 0, - extra: 'extra', - }, - })); - }); - - it('should return false when teamId, data.name, data.type are some more extra data are provided to TeamsUpdateProps', () => { - chai.assert.isFalse(isTeamsUpdateProps({ - teamId: 'teamId', - data: { - name: 'name', - type: 0, - extra: 'extra', - }, - })); - }); - - it('should return false when teamName, data.name, data.type are some more extra parameter are provided to TeamsUpdateProps', () => { - chai.assert.isFalse(isTeamsUpdateProps({ - teamName: 'teamName', - extra: 'extra', - data: { - name: 'name', - type: 0, - }, - })); - }); - - it('should return false when teamId, data.name, data.type are some more extra parameter are provided to TeamsUpdateProps', () => { - chai.assert.isFalse(isTeamsUpdateProps({ - teamId: 'teamId', - extra: 'extra', - data: { - name: 'name', - type: 0, - }, - })); - }); - }); -}); diff --git a/definition/rest/v1/teams/TeamsUpdateProps.ts b/definition/rest/v1/teams/TeamsUpdateProps.ts index b019efdb1549b..b5f92e10da7e1 100644 --- a/definition/rest/v1/teams/TeamsUpdateProps.ts +++ b/definition/rest/v1/teams/TeamsUpdateProps.ts @@ -5,13 +5,15 @@ import { TEAM_TYPE } from '../../../ITeam'; const ajv = new Ajv(); export type TeamsUpdateProps = ({ teamId: string } | { teamName: string }) & { - data: ({ - name: string; - type?: TEAM_TYPE; - } | { - name?: string; - type: TEAM_TYPE; - }); + data: + | { + name: string; + type?: TEAM_TYPE; + } + | { + name?: string; + type: TEAM_TYPE; + }; }; const teamsUpdatePropsSchema: JSONSchemaType = { @@ -38,10 +40,7 @@ const teamsUpdatePropsSchema: JSONSchemaType = { }, type: { type: 'number', - enum: [ - TEAM_TYPE.PUBLIC, - TEAM_TYPE.PRIVATE, - ], + enum: [TEAM_TYPE.PUBLIC, TEAM_TYPE.PRIVATE], }, }, additionalProperties: false, diff --git a/definition/rest/v1/teams/index.ts b/definition/rest/v1/teams/index.ts index 1b981937f1686..504617938512e 100644 --- a/definition/rest/v1/teams/index.ts +++ b/definition/rest/v1/teams/index.ts @@ -1,19 +1,21 @@ +import type { + ITeamAutocompleteResult, + ITeamMemberInfo, +} from '../../../../server/sdk/types/ITeamService'; import type { IRoom } from '../../../IRoom'; import type { ITeam } from '../../../ITeam'; import type { IUser } from '../../../IUser'; -import type { PaginatedResult } from '../../helpers/PaginatedResult'; import type { PaginatedRequest } from '../../helpers/PaginatedRequest'; -import type { ITeamAutocompleteResult, ITeamMemberInfo } from '../../../../server/sdk/types/ITeamService'; -import type { TeamsRemoveRoomProps } from './TeamsRemoveRoomProps'; -import type { TeamsConvertToChannelProps } from './TeamsConvertToChannelProps'; -import type { TeamsUpdateMemberProps } from './TeamsUpdateMemberProps'; +import type { PaginatedResult } from '../../helpers/PaginatedResult'; import type { TeamsAddMembersProps } from './TeamsAddMembersProps'; -import type { TeamsRemoveMemberProps } from './TeamsRemoveMemberProps'; +import type { TeamsConvertToChannelProps } from './TeamsConvertToChannelProps'; import type { TeamsDeleteProps } from './TeamsDeleteProps'; import type { TeamsLeaveProps } from './TeamsLeaveProps'; +import type { TeamsRemoveMemberProps } from './TeamsRemoveMemberProps'; +import type { TeamsRemoveRoomProps } from './TeamsRemoveRoomProps'; +import type { TeamsUpdateMemberProps } from './TeamsUpdateMemberProps'; import type { TeamsUpdateProps } from './TeamsUpdateProps'; - type TeamProps = | TeamsRemoveRoomProps | TeamsConvertToChannelProps @@ -24,9 +26,13 @@ type TeamProps = | TeamsLeaveProps | TeamsUpdateProps; -export const isTeamPropsWithTeamName = (props: T): props is T & { teamName: string } => 'teamName' in props; +export const isTeamPropsWithTeamName = ( + props: T, +): props is T & { teamName: string } => 'teamName' in props; -export const isTeamPropsWithTeamId = (props: T): props is T & { teamId: string } => 'teamId' in props; +export const isTeamPropsWithTeamId = ( + props: T, +): props is T & { teamId: string } => 'teamId' in props; export type TeamsEndpoints = { 'teams.list': { @@ -59,12 +65,12 @@ export type TeamsEndpoints = { }; } & { [key: string]: - | string - | { - open: boolean; - ls: Date; - prid: IRoom['_id']; - }; + | string + | { + open: boolean; + ls: Date; + prid: IRoom['_id']; + }; }; }; owner?: IUser['_id']; @@ -78,15 +84,25 @@ export type TeamsEndpoints = { }; 'teams.addRooms': { - POST: (params: { rooms: IRoom['_id'][]; teamId: string } | { rooms: IRoom['_id'][]; teamName: string }) => ({ rooms: IRoom[] }); + POST: ( + params: + | { rooms: IRoom['_id'][]; teamId: string } + | { rooms: IRoom['_id'][]; teamName: string }, + ) => { rooms: IRoom[] }; }; 'teams.removeRoom': { - POST: (params: TeamsRemoveRoomProps) => ({ room: IRoom }); + POST: (params: TeamsRemoveRoomProps) => { room: IRoom }; }; 'teams.members': { - GET: (params: ({ teamId: string } | { teamName: string }) & { status?: string[]; username?: string; name?: string }) => (PaginatedResult & { members: ITeamMemberInfo[] }); + GET: ( + params: ({ teamId: string } | { teamName: string }) & { + status?: string[]; + username?: string; + name?: string; + }, + ) => PaginatedResult & { members: ITeamMemberInfo[] }; }; 'teams.addMembers': { @@ -105,13 +121,12 @@ export type TeamsEndpoints = { POST: (params: TeamsLeaveProps) => void; }; - 'teams.info': { - GET: (params: ({ teamId: string } | { teamName: string }) & {}) => ({ teamInfo: Partial }); + GET: (params: ({ teamId: string } | { teamName: string }) & {}) => { teamInfo: Partial }; }; 'teams.autocomplete': { - GET: (params: { name: string }) => ({ teams: ITeamAutocompleteResult[] }); + GET: (params: { name: string }) => { teams: ITeamAutocompleteResult[] }; }; 'teams.update': { @@ -123,24 +138,29 @@ export type TeamsEndpoints = { }; 'teams.listRoomsOfUser': { - GET: (params: { - teamId: ITeam['_id']; - userId: IUser['_id']; - canUserDelete?: boolean; - } | { - teamName: ITeam['name']; - userId: IUser['_id']; - canUserDelete?: boolean; - } + GET: ( + params: + | { + teamId: ITeam['_id']; + userId: IUser['_id']; + canUserDelete?: boolean; + } + | { + teamName: ITeam['name']; + userId: IUser['_id']; + canUserDelete?: boolean; + }, ) => PaginatedResult & { rooms: IRoom[] }; }; 'teams.listRooms': { - GET: (params: PaginatedRequest & ({ teamId: string } | { teamName: string }) & { filter?: string; type?: string }) => PaginatedResult & { rooms: IRoom[] }; + GET: ( + params: PaginatedRequest & + ({ teamId: string } | { teamName: string }) & { filter?: string; type?: string }, + ) => PaginatedResult & { rooms: IRoom[] }; }; - 'teams.updateRoom': { - POST: (params: { roomId: IRoom['_id']; isDefault: boolean }) => ({ room: IRoom }); + POST: (params: { roomId: IRoom['_id']; isDefault: boolean }) => { room: IRoom }; }; }; diff --git a/ee/app/engagement-dashboard/client/components/ChannelsTab/ChannelsTab.stories.js b/ee/app/engagement-dashboard/client/components/ChannelsTab/ChannelsTab.stories.js deleted file mode 100644 index 420ecaa95630c..0000000000000 --- a/ee/app/engagement-dashboard/client/components/ChannelsTab/ChannelsTab.stories.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Margins } from '@rocket.chat/fuselage'; -import React from 'react'; - -import ChannelsTab from '.'; - -export default { - title: 'admin/enterprise/engagement/ChannelsTab', - component: ChannelsTab, - decorators: [ - (fn) => , - ], -}; - -export const _default = () => ; diff --git a/ee/app/engagement-dashboard/client/components/ChannelsTab/TableSection.js b/ee/app/engagement-dashboard/client/components/ChannelsTab/TableSection.js deleted file mode 100644 index 9fbf33d21ac48..0000000000000 --- a/ee/app/engagement-dashboard/client/components/ChannelsTab/TableSection.js +++ /dev/null @@ -1,163 +0,0 @@ -import { Box, Icon, Margins, Pagination, Select, Skeleton, Table, Tile, ActionButton } from '@rocket.chat/fuselage'; -import moment from 'moment'; -import React, { useMemo, useState } from 'react'; - -import { useTranslation } from '../../../../../../client/contexts/TranslationContext'; -import { useEndpointData } from '../../../../../../client/hooks/useEndpointData'; -import Growth from '../../../../../../client/components/data/Growth'; -import { Section } from '../Section'; -import { downloadCsvAs } from '../../../../../../client/lib/download'; - -const TableSection = () => { - const t = useTranslation(); - - const periodOptions = useMemo(() => [ - ['last 7 days', t('Last_7_days')], - ['last 30 days', t('Last_30_days')], - ['last 90 days', t('Last_90_days')], - ], [t]); - - const [periodId, setPeriodId] = useState('last 7 days'); - - const period = useMemo(() => { - switch (periodId) { - case 'last 7 days': - return { - start: moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(7, 'days'), - end: moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(1), - }; - - case 'last 30 days': - return { - start: moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(30, 'days'), - end: moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(1), - }; - - case 'last 90 days': - return { - start: moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(90, 'days'), - end: moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(1), - }; - } - }, [periodId]); - - const handlePeriodChange = (periodId) => setPeriodId(periodId); - - const [current, setCurrent] = useState(0); - const [itemsPerPage, setItemsPerPage] = useState(25); - - const params = useMemo(() => ({ - start: period.start.toISOString(), - end: period.end.toISOString(), - offset: current, - count: itemsPerPage, - }), [period, current, itemsPerPage]); - - const { value: data } = useEndpointData('engagement-dashboard/channels/list', params); - - const channels = useMemo(() => { - if (!data) { - return; - } - - return data.channels.map(({ - room: { t, name, usernames, ts, _updatedAt }, - messages, - diffFromLastWeek, - }) => ({ - t, - name: name || usernames.join(' × '), - createdAt: ts, - updatedAt: _updatedAt, - messagesCount: messages, - messagesVariation: diffFromLastWeek, - })); - }, [data]); - - const downloadData = () => { - const data = [ - ['Room type', 'Name', 'Messages', 'Last Update Date', 'Creation Date'], - ...channels.map(({ - createdAt, - messagesCount, - name, - t, - updatedAt, - }) => [t, name, messagesCount, updatedAt, createdAt]), - ]; - downloadCsvAs(data, `Channels_start_${ params.start }_end_${ params.end }`); - }; - - return

    - - - {t('Users')} - {t('Messages')} - {t('Channels')} - - - - {(tab === 'users' && ) - || (tab === 'messages' && ) - || (tab === 'channels' && )} - - - ; -}; diff --git a/ee/app/engagement-dashboard/client/components/EngagementDashboardPage.stories.js b/ee/app/engagement-dashboard/client/components/EngagementDashboardPage.stories.js deleted file mode 100644 index f8b0af335c0e0..0000000000000 --- a/ee/app/engagement-dashboard/client/components/EngagementDashboardPage.stories.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; - -import { EngagementDashboardPage } from './EngagementDashboardPage'; - -export default { - title: 'admin/enterprise/engagement/EngagementDashboardPage', - component: EngagementDashboardPage, - decorators: [(fn) =>
    ], -}; - -export const _default = () => ; diff --git a/ee/app/engagement-dashboard/client/components/EngagementDashboardRoute.js b/ee/app/engagement-dashboard/client/components/EngagementDashboardRoute.js deleted file mode 100644 index 3122ea19a3efa..0000000000000 --- a/ee/app/engagement-dashboard/client/components/EngagementDashboardRoute.js +++ /dev/null @@ -1,26 +0,0 @@ -import React, { useEffect } from 'react'; - -import { useCurrentRoute, useRoute } from '../../../../../client/contexts/RouterContext'; -import { EngagementDashboardPage } from './EngagementDashboardPage'; - -export function EngagementDashboardRoute() { - const engagementDashboardRoute = useRoute('engagement-dashboard'); - const [routeName, { tab }] = useCurrentRoute(); - - useEffect(() => { - if (routeName !== 'engagement-dashboard') { - return; - } - - if (!tab) { - engagementDashboardRoute.replace({ tab: 'users' }); - } - }, [routeName, engagementDashboardRoute, tab]); - - return engagementDashboardRoute.push({ tab })} - />; -} - -export default EngagementDashboardRoute; diff --git a/ee/app/engagement-dashboard/client/components/MessagesTab/MessagesPerChannelSection.js b/ee/app/engagement-dashboard/client/components/MessagesTab/MessagesPerChannelSection.js deleted file mode 100644 index ca54427e1d166..0000000000000 --- a/ee/app/engagement-dashboard/client/components/MessagesTab/MessagesPerChannelSection.js +++ /dev/null @@ -1,236 +0,0 @@ -import { ResponsivePie } from '@nivo/pie'; -import { Box, Flex, Icon, Margins, Select, Skeleton, Table, Tile, ActionButton } from '@rocket.chat/fuselage'; -import moment from 'moment'; -import React, { useMemo, useState } from 'react'; - -import { useTranslation } from '../../../../../../client/contexts/TranslationContext'; -import { useEndpointData } from '../../../../../../client/hooks/useEndpointData'; -import { LegendSymbol } from '../data/LegendSymbol'; -import { Section } from '../Section'; -import { downloadCsvAs } from '../../../../../../client/lib/download'; - -const MessagesPerChannelSection = () => { - const t = useTranslation(); - - const periodOptions = useMemo(() => [ - ['last 7 days', t('Last_7_days')], - ['last 30 days', t('Last_30_days')], - ['last 90 days', t('Last_90_days')], - ], [t]); - - const [periodId, setPeriodId] = useState('last 7 days'); - - const period = useMemo(() => { - switch (periodId) { - case 'last 7 days': - return { - start: moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(7, 'days'), - end: moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(1), - }; - - case 'last 30 days': - return { - start: moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(30, 'days'), - end: moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(1), - }; - - case 'last 90 days': - return { - start: moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(90, 'days'), - end: moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(1), - }; - } - }, [periodId]); - - const handlePeriodChange = (periodId) => setPeriodId(periodId); - - const params = useMemo(() => ({ - start: period.start.toISOString(), - end: period.end.toISOString(), - }), [period]); - - const { value: pieData } = useEndpointData('engagement-dashboard/messages/origin', params); - const { value: tableData } = useEndpointData('engagement-dashboard/messages/top-five-popular-channels', params); - - const [pie, table] = useMemo(() => { - if (!pieData || !tableData) { - return []; - } - - const pie = pieData.origins.reduce((obj, { messages, t }) => ({ ...obj, [t]: messages }), {}); - - const table = tableData.channels.reduce((entries, { t, messages, name, usernames }, i) => - [...entries, { i, t, name: name || usernames.join(' × '), messages }], []); - - return [pie, table]; - }, [pieData, tableData]); - - const downloadData = () => { - const data = [ - ['Room Type', 'Messages'], - ...pieData.origins.map(({ t, messages }) => [t, messages]), - ]; - downloadCsvAs(data, `MessagesPerChannelSection_start_${ params.start }_end_${ params.end }`); - }; - - - return
    } - > - , - variation: data ? variatonFromPeriod : 0, - description: periodOptions.find(([id]) => id === periodId)[1], - }, - { - count: data ? countFromYesterday : , - variation: data ? variationFromYesterday : 0, - description: t('Yesterday'), - }, - ]} - /> - - {data - ? - - - - moment(date).format('dddd'), - }) || null } - axisLeft={null} - animate={true} - motionStiffness={90} - motionDamping={15} - theme={{ - // TODO: Get it from theme - axis: { - ticks: { - text: { - fill: '#9EA2A8', - fontFamily: 'Inter, -apple-system, system-ui, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Meiryo UI", Arial, sans-serif', - fontSize: '10px', - fontStyle: 'normal', - fontWeight: '600', - letterSpacing: '0.2px', - lineHeight: '12px', - }, - }, - }, - tooltip: { - container: { - backgroundColor: '#1F2329', - boxShadow: '0px 0px 12px rgba(47, 52, 61, 0.12), 0px 0px 2px rgba(47, 52, 61, 0.08)', - borderRadius: 2, - }, - }, - }} - tooltip={({ value }) => - {t('Value_messages', { value })} - } - /> - - - - - : } - -
    ; -}; - -export default MessagesSentSection; diff --git a/ee/app/engagement-dashboard/client/components/MessagesTab/MessagesTab.stories.js b/ee/app/engagement-dashboard/client/components/MessagesTab/MessagesTab.stories.js deleted file mode 100644 index aa3e886b3e437..0000000000000 --- a/ee/app/engagement-dashboard/client/components/MessagesTab/MessagesTab.stories.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Margins } from '@rocket.chat/fuselage'; -import React from 'react'; - -import MessagesTab from '.'; - -export default { - title: 'admin/enterprise/engagement/MessagesTab', - component: MessagesTab, - decorators: [ - (fn) => , - ], -}; - -export const _default = () => ; diff --git a/ee/app/engagement-dashboard/client/components/Section.js b/ee/app/engagement-dashboard/client/components/Section.js deleted file mode 100644 index 754ec689b2b0e..0000000000000 --- a/ee/app/engagement-dashboard/client/components/Section.js +++ /dev/null @@ -1,22 +0,0 @@ -import { Box, Flex, InputBox, Margins } from '@rocket.chat/fuselage'; -import React from 'react'; - -export function Section({ - children, - title, - filter = , -}) { - return - - - {title} - {filter && - - {filter} - - } - - {children} - - ; -} diff --git a/ee/app/engagement-dashboard/client/components/UsersTab/ActiveUsersSection.js b/ee/app/engagement-dashboard/client/components/UsersTab/ActiveUsersSection.js deleted file mode 100644 index ac5f42c33e642..0000000000000 --- a/ee/app/engagement-dashboard/client/components/UsersTab/ActiveUsersSection.js +++ /dev/null @@ -1,259 +0,0 @@ -import { ResponsiveLine } from '@nivo/line'; -import { Box, Flex, Skeleton, Tile, ActionButton } from '@rocket.chat/fuselage'; -import moment from 'moment'; -import React, { useMemo } from 'react'; - -import { useTranslation } from '../../../../../../client/contexts/TranslationContext'; -import { useEndpointData } from '../../../../../../client/hooks/useEndpointData'; -import { useFormatDate } from '../../../../../../client/hooks/useFormatDate'; -import CounterSet from '../../../../../../client/components/data/CounterSet'; -import { LegendSymbol } from '../data/LegendSymbol'; -import { Section } from '../Section'; -import { downloadCsvAs } from '../../../../../../client/lib/download'; - -const ActiveUsersSection = ({ timezone }) => { - const t = useTranslation(); - const utc = timezone === 'utc'; - const formatDate = useFormatDate(); - const period = useMemo(() => ({ - start: utc - ? moment.utc().subtract(30, 'days') - : moment().subtract(30, 'days'), - end: utc - ? moment.utc().subtract(1, 'days') - : moment().subtract(1, 'days'), - }), [utc]); - - const params = useMemo(() => ({ - start: period.start.clone().subtract(29, 'days').toISOString(), - end: period.end.toISOString(), - }), [period]); - - const { value: data } = useEndpointData('engagement-dashboard/users/active-users', useMemo(() => params, [params])); - - const [ - countDailyActiveUsers, - diffDailyActiveUsers, - countWeeklyActiveUsers, - diffWeeklyActiveUsers, - countMonthlyActiveUsers, - diffMonthlyActiveUsers, - dauValues, - wauValues, - mauValues, - ] = useMemo(() => { - if (!data) { - return []; - } - - const createPoint = (i) => ({ - x: moment(period.start).add(i, 'days').toDate(), - y: 0, - }); - - const createPoints = () => Array.from({ length: moment(period.end).diff(period.start, 'days') + 1 }, (_, i) => createPoint(i)); - - const dauValues = createPoints(); - const prevDauValue = createPoint(-1); - const wauValues = createPoints(); - const prevWauValue = createPoint(-1); - const mauValues = createPoints(); - const prevMauValue = createPoint(-1); - - const usersListsMap = data.month.reduce((map, dayData) => { - const date = utc - ? moment.utc({ year: dayData.year, month: dayData.month - 1, day: dayData.day }).endOf('day') - : moment({ year: dayData.year, month: dayData.month - 1, day: dayData.day }).endOf('day'); - const dateOffset = date.diff(period.start, 'days'); - if (dateOffset >= 0) { - map[dateOffset] = dayData.usersList; - dauValues[dateOffset].y = dayData.users; - } - return map; - }, {}); - - const distributeValueOverPoints = (usersListsMap, dateOffset, T, array) => { - const usersSet = new Set(); - for (let k = dateOffset; T > 0; k--, T--) { - if (usersListsMap[k]) { - usersListsMap[k].forEach((userId) => usersSet.add(userId)); - } - } - array[dateOffset].y = usersSet.size; - }; - - for (let i = 0; i < 30; i++) { - distributeValueOverPoints(usersListsMap, i, 7, wauValues); - distributeValueOverPoints(usersListsMap, i, 30, mauValues); - } - prevWauValue.y = wauValues[28].y; - prevMauValue.y = mauValues[28].y; - prevDauValue.y = dauValues[28].y; - - return [ - dauValues[dauValues.length - 1].y, - dauValues[dauValues.length - 1].y - prevDauValue.y, - wauValues[wauValues.length - 1].y, - wauValues[wauValues.length - 1].y - prevWauValue.y, - mauValues[mauValues.length - 1].y, - mauValues[mauValues.length - 1].y - prevMauValue.y, - dauValues, - wauValues, - mauValues, - ]; - }, [data, period.end, period.start, utc]); - - const downloadData = () => { - const values = []; - - for (let i = 0; i < 30; i++) { - values.push([moment(dauValues[i].x).format('YYYY-MM-DD'), dauValues[i].y, wauValues[i].y, mauValues[i].y]); - } - - const data = [ - ['Date', 'DAU', 'WAU', 'MAU'], - ...values, - ]; - downloadCsvAs(data, `ActiveUsersSection_start_${ params.start }_end_${ params.end }`); - }; - - - return
    }> - , - variation: data ? diffDailyActiveUsers : 0, - description: <> {t('Daily_Active_Users')}, - }, - { - count: data ? countWeeklyActiveUsers : , - variation: data ? diffWeeklyActiveUsers : 0, - description: <> {t('Weekly_Active_Users')}, - }, - { - count: data ? countMonthlyActiveUsers : , - variation: data ? diffMonthlyActiveUsers : 0, - description: <> {t('Monthly_Active_Users')}, - }, - ]} - /> - - {data - ? - - - - moment(date).format(dauValues.length === 7 ? 'dddd' : 'L'), - }} - animate={true} - motionStiffness={90} - motionDamping={15} - theme={{ - // TODO: Get it from theme - axis: { - ticks: { - text: { - fill: '#9EA2A8', - fontFamily: 'Inter, -apple-system, system-ui, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Meiryo UI", Arial, sans-serif', - fontSize: '10px', - fontStyle: 'normal', - fontWeight: '600', - letterSpacing: '0.2px', - lineHeight: '12px', - }, - }, - }, - tooltip: { - container: { - backgroundColor: '#1F2329', - boxShadow: '0px 0px 12px rgba(47, 52, 61, 0.12), 0px 0px 2px rgba(47, 52, 61, 0.08)', - borderRadius: 2, - }, - }, - }} - enableSlices='x' - sliceTooltip={({ slice: { points } }) => - - {formatDate(points[0].data.x)} - {points.map(({ serieId, data: { y: activeUsers } }) => - - {(serieId === 'dau' && t('DAU_value', { value: activeUsers })) - || (serieId === 'wau' && t('WAU_value', { value: activeUsers })) - || (serieId === 'mau' && t('MAU_value', { value: activeUsers }))} - )} - - } - /> - - - - - : } - -
    ; -}; - -export default ActiveUsersSection; diff --git a/ee/app/engagement-dashboard/client/components/UsersTab/BusiestChatTimesSection.js b/ee/app/engagement-dashboard/client/components/UsersTab/BusiestChatTimesSection.js deleted file mode 100644 index 34d22838cf257..0000000000000 --- a/ee/app/engagement-dashboard/client/components/UsersTab/BusiestChatTimesSection.js +++ /dev/null @@ -1,261 +0,0 @@ -import { ResponsiveBar } from '@nivo/bar'; -import { Box, Button, Chevron, Flex, Margins, Select, Skeleton } from '@rocket.chat/fuselage'; -import { useBreakpoints } from '@rocket.chat/fuselage-hooks'; -import moment from 'moment'; -import React, { useMemo, useState } from 'react'; - -import { useTranslation } from '../../../../../../client/contexts/TranslationContext'; -import { useEndpointData } from '../../../../../../client/hooks/useEndpointData'; -import { Section } from '../Section'; - -const ContentForHours = ({ displacement, onPreviousDateClick, onNextDateClick, timezone }) => { - const t = useTranslation(); - const isLgScreen = useBreakpoints().includes('lg'); - const utc = timezone === 'utc'; - - const currentDate = useMemo(() => { - if (utc) { - return moment().utc({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(1, 'days').subtract(displacement, 'days'); - } - return moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(1).subtract(displacement, 'days'); - }, [displacement, utc]); - - const params = useMemo(() => ({ start: currentDate.toISOString() }), [currentDate]); - const { value: data } = useEndpointData('engagement-dashboard/users/chat-busier/hourly-data', useMemo(() => params, [params])); - const values = useMemo(() => { - if (!data) { - return []; - } - - const divider = 2; - const values = Array.from({ length: 24 / divider }, (_, i) => ({ - hour: String(divider * i), - users: 0, - })); - for (const { hour, users } of data.hours) { - const i = Math.floor(hour / divider); - values[i] = values[i] || { hour: String(divider * i), users: 0 }; - values[i].users += users; - } - - return values; - }, [data]); - - return <> - - - - {currentDate.format(displacement < 7 ? 'dddd' : 'L')} - - - - {data - ? - - - moment().set({ hour, minute: 0, second: 0 }).format('LT'), - }} - axisLeft={null} - animate={true} - motionStiffness={90} - motionDamping={15} - theme={{ - // TODO: Get it from theme - axis: { - ticks: { - text: { - fill: '#9EA2A8', - fontFamily: 'Inter, -apple-system, system-ui, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Meiryo UI", Arial, sans-serif', - fontSize: '10px', - fontStyle: 'normal', - fontWeight: '600', - letterSpacing: '0.2px', - lineHeight: '12px', - }, - }, - }, - tooltip: { - backgroundColor: '#1F2329', - boxShadow: '0px 0px 12px rgba(47, 52, 61, 0.12), 0px 0px 2px rgba(47, 52, 61, 0.08)', - borderRadius: 2, - padding: 4, - }, - }} - tooltip={({ value }) => - {t('Value_users', { value })} - } - /> - - - - : } - ; -}; - -const ContentForDays = ({ displacement, onPreviousDateClick, onNextDateClick, timezone }) => { - const utc = timezone === 'utc'; - const currentDate = useMemo(() => { - if (utc) { - return moment.utc().subtract(displacement, 'weeks'); - } - return moment().subtract(displacement, 'weeks'); - }, [displacement, utc]); - const formattedCurrentDate = useMemo(() => { - const startOfWeekDate = currentDate.clone().subtract(6, 'days'); - return `${ startOfWeekDate.format('L') } - ${ currentDate.format('L') }`; - }, [currentDate]); - const params = useMemo(() => ({ start: currentDate.toISOString() }), [currentDate]); - const { value: data } = useEndpointData('engagement-dashboard/users/chat-busier/weekly-data', useMemo(() => params, [params])); - const values = useMemo(() => (data ? data.month.map(({ users, day, month, year }) => ({ - users, - day: String(moment({ year, month: month - 1, day }).valueOf()), - })).sort(({ day: a }, { day: b }) => a - b) : []), [data]); - - return <> - - - - - - - {formattedCurrentDate} - - - - - - - - {data - ? - - - - moment(parseInt(timestamp, 10)).format('L'), - }} - axisLeft={null} - animate={true} - motionStiffness={90} - motionDamping={15} - theme={{ - // TODO: Get it from theme - axis: { - ticks: { - text: { - fill: '#9EA2A8', - fontFamily: 'Inter, -apple-system, system-ui, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Meiryo UI", Arial, sans-serif', - fontSize: '10px', - fontStyle: 'normal', - fontWeight: '600', - letterSpacing: '0.2px', - lineHeight: '12px', - }, - }, - }, - }} - /> - - - - - : } - - ; -}; - -const BusiestChatTimesSection = ({ timezone }) => { - const t = useTranslation(); - - const [timeUnit, setTimeUnit] = useState('hours'); - const timeUnitOptions = useMemo(() => [ - ['hours', t('Hours')], - ['days', t('Days')], - ], [t]); - - const [displacement, setDisplacement] = useState(0); - - const handleTimeUnitChange = (timeUnit) => { - setTimeUnit(timeUnit); - setDisplacement(0); - }; - - const handlePreviousDateClick = () => setDisplacement((displacement) => displacement + 1); - const handleNextDateClick = () => setDisplacement((displacement) => displacement - 1); - - const Content = (timeUnit === 'hours' && ContentForHours) || (timeUnit === 'days' && ContentForDays); - - return
    } - > - -
    ; -}; - -export default BusiestChatTimesSection; diff --git a/ee/app/engagement-dashboard/client/components/UsersTab/NewUsersSection.js b/ee/app/engagement-dashboard/client/components/UsersTab/NewUsersSection.js deleted file mode 100644 index 76c76390fecbb..0000000000000 --- a/ee/app/engagement-dashboard/client/components/UsersTab/NewUsersSection.js +++ /dev/null @@ -1,234 +0,0 @@ -import { ResponsiveBar } from '@nivo/bar'; -import { Box, Flex, Select, Skeleton, ActionButton } from '@rocket.chat/fuselage'; -import { useResizeObserver } from '@rocket.chat/fuselage-hooks'; -import moment from 'moment'; -import React, { useMemo, useState } from 'react'; - -import { useTranslation } from '../../../../../../client/contexts/TranslationContext'; -import { useEndpointData } from '../../../../../../client/hooks/useEndpointData'; -import { useFormatDate } from '../../../../../../client/hooks/useFormatDate'; -import CounterSet from '../../../../../../client/components/data/CounterSet'; -import { Section } from '../Section'; -import { downloadCsvAs } from '../../../../../../client/lib/download'; - -const TICK_WIDTH = 45; - -const NewUsersSection = ({ timezone }) => { - const t = useTranslation(); - const utc = timezone === 'utc'; - - const periodOptions = useMemo(() => [ - ['last 7 days', t('Last_7_days')], - ['last 30 days', t('Last_30_days')], - ['last 90 days', t('Last_90_days')], - ], [t]); - - const [periodId, setPeriodId] = useState('last 7 days'); - - const formatDate = useFormatDate(); - - const period = useMemo(() => { - switch (periodId) { - case 'last 7 days': - return { - start: utc - ? moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(7, 'days').utc() - : moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(7, 'days'), - end: utc - ? moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(1, 'days').utc() - : moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(1, 'days'), - }; - - case 'last 30 days': - return { - start: utc - ? moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(30, 'days').utc() - : moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(30, 'days'), - end: utc - ? moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(1, 'days').utc() - : moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(1, 'days'), - }; - - case 'last 90 days': - return { - start: utc - ? moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(90, 'days').utc() - : moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(90, 'days'), - end: utc - ? moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(1, 'days').utc() - : moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(1, 'days'), - }; - } - }, [periodId, utc]); - - const handlePeriodChange = (periodId) => setPeriodId(periodId); - - const params = useMemo(() => ({ - start: period.start.toISOString(), - end: period.end.toISOString(), - }), [period]); - - const { value: data } = useEndpointData('engagement-dashboard/users/new-users', useMemo(() => params, [params])); - - const { ref: sizeRef, contentBoxSize: { inlineSize = 600 } = {} } = useResizeObserver(); - - const maxTicks = Math.ceil(inlineSize / TICK_WIDTH); - - const tickValues = useMemo(() => { - const arrayLength = moment(period.end).diff(period.start, 'days') + 1; - if (arrayLength <= maxTicks || !maxTicks) { - return null; - } - - const values = Array.from({ length: arrayLength }, (_, i) => moment(period.start).add(i, 'days').format('YYYY-MM-DD')); - - const relation = Math.ceil(values.length / maxTicks); - - return values.reduce((acc, cur, i) => { - if ((i + 1) % relation === 0) { acc = [...acc, cur]; } - return acc; - }, []); - }, [period, maxTicks]); - - const [ - countFromPeriod, - variatonFromPeriod, - countFromYesterday, - variationFromYesterday, - values, - ] = useMemo(() => { - if (!data) { - return []; - } - - const values = Array.from({ length: moment(period.end).diff(period.start, 'days') + 1 }, (_, i) => ({ - date: moment(period.start).add(i, 'days').format('YYYY-MM-DD'), - newUsers: 0, - })); - for (const { day, users } of data.days) { - const i = utc - ? moment(day).utc().diff(period.start, 'days') - : moment(day).diff(period.start, 'days'); - if (i >= 0) { - values[i].newUsers += users; - } - } - - return [ - data.period.count, - data.period.variation, - data.yesterday.count, - data.yesterday.variation, - values, - ]; - }, [data, period, utc]); - - const downloadData = () => { - const data = [ - ['Date', 'New Users'], - ...values.map(({ date, newUsers }) => [date, newUsers]), - ]; - downloadCsvAs(data, `NewUsersSection_start_${ params.start }_end_${ params.end }`); - }; - - return
    {}} - > - {data - ? - - - - (dates.length === 7 ? moment(isoString).format('dddd') : ''), - }} - axisLeft={{ - // TODO: Get it from theme - tickSize: 0, - tickPadding: 4, - tickRotation: 0, - format: (hour) => moment().set({ hour: parseInt(hour, 10), minute: 0, second: 0 }).format('LT'), - }} - hoverTarget='cell' - animate={dates.length <= 7} - motionStiffness={90} - motionDamping={15} - theme={{ - // TODO: Get it from theme - axis: { - ticks: { - text: { - fill: '#9EA2A8', - fontFamily: 'Inter, -apple-system, system-ui, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Meiryo UI", Arial, sans-serif', - fontSize: 10, - fontStyle: 'normal', - fontWeight: '600', - letterSpacing: '0.2px', - lineHeight: '12px', - }, - }, - }, - tooltip: { - container: { - backgroundColor: '#1F2329', - boxShadow: '0px 0px 12px rgba(47, 52, 61, 0.12), 0px 0px 2px rgba(47, 52, 61, 0.08)', - borderRadius: 2, - }, - }, - }} - tooltip={({ value }) => - {t('Value_users', { value })} - } - /> - - - - - : } -
    ; -}; - -export default UsersByTimeOfTheDaySection; diff --git a/ee/app/engagement-dashboard/client/components/UsersTab/UsersTab.Stories.js b/ee/app/engagement-dashboard/client/components/UsersTab/UsersTab.Stories.js deleted file mode 100644 index bf925ca34cf46..0000000000000 --- a/ee/app/engagement-dashboard/client/components/UsersTab/UsersTab.Stories.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Margins } from '@rocket.chat/fuselage'; -import React from 'react'; - -import { UsersTab } from '.'; - -export default { - title: 'admin/enterprise/engagement/UsersTab', - component: UsersTab, - decorators: [ - (fn) => , - ], -}; - -export const _default = () => ; diff --git a/ee/app/engagement-dashboard/client/components/UsersTab/index.js b/ee/app/engagement-dashboard/client/components/UsersTab/index.js deleted file mode 100644 index 57be36ba1eee8..0000000000000 --- a/ee/app/engagement-dashboard/client/components/UsersTab/index.js +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import { useBreakpoints } from '@rocket.chat/fuselage-hooks'; -import { Box, Divider, Flex, Margins } from '@rocket.chat/fuselage'; - -import NewUsersSection from './NewUsersSection'; -import ActiveUsersSection from './ActiveUsersSection'; -import UsersByTimeOfTheDaySection from './UsersByTimeOfTheDaySection'; -import BusiestChatTimesSection from './BusiestChatTimesSection'; - -const UsersTab = ({ timezone }) => { - const isXxlScreen = useBreakpoints().includes('xxl'); - - return <> - - - - - - - - - - - - - - - ; -}; - -export default UsersTab; diff --git a/ee/app/engagement-dashboard/client/components/data/Histogram.js b/ee/app/engagement-dashboard/client/components/data/Histogram.js deleted file mode 100644 index 30653cf05a1b5..0000000000000 --- a/ee/app/engagement-dashboard/client/components/data/Histogram.js +++ /dev/null @@ -1,85 +0,0 @@ -import { ResponsiveBar } from '@nivo/bar'; -import { Box, Flex } from '@rocket.chat/fuselage'; -import React from 'react'; - -import { polychromaticColors } from './colors'; - -export function Histogram() { - return - - - `${ users }%`, - }} - axisLeft={{ - tickSize: 0, - tickPadding: 5, - tickRotation: 0, - format: (utc) => `UTF ${ utc }`, - }} - animate={true} - motionStiffness={90} - motionDamping={15} - theme={{ - font: 'inherit', - fontStyle: 'normal', - fontWeight: 600, - fontSize: 10, - lineHeight: 12, - letterSpacing: 0.2, - color: '#9EA2A8', - grid: { - line: { - stroke: '#CBCED1', - strokeWidth: 1, - strokeDasharray: '4 1.5', - }, - }, - }} - /> - - - ; -} diff --git a/ee/app/engagement-dashboard/client/components/data/Histogram.stories.js b/ee/app/engagement-dashboard/client/components/data/Histogram.stories.js deleted file mode 100644 index e38e87275b70a..0000000000000 --- a/ee/app/engagement-dashboard/client/components/data/Histogram.stories.js +++ /dev/null @@ -1,16 +0,0 @@ -import { Box, Flex, Margins } from '@rocket.chat/fuselage'; -import React from 'react'; - -import { Histogram } from './Histogram'; - -export default { - title: 'admin/enterprise/engagement/data/Histogram', - component: Histogram, - decorators: [(fn) => - - - - ], -}; - -export const _default = () => ; diff --git a/ee/app/engagement-dashboard/client/components/data/LegendSymbol.js b/ee/app/engagement-dashboard/client/components/data/LegendSymbol.js deleted file mode 100644 index 947631e97999b..0000000000000 --- a/ee/app/engagement-dashboard/client/components/data/LegendSymbol.js +++ /dev/null @@ -1,19 +0,0 @@ -import { Box, Margins } from '@rocket.chat/fuselage'; -import React from 'react'; - -export function LegendSymbol({ color = 'currentColor' }) { - return - ; -} diff --git a/ee/app/engagement-dashboard/client/components/data/LegendSymbol.stories.js b/ee/app/engagement-dashboard/client/components/data/LegendSymbol.stories.js deleted file mode 100644 index ca8a466d7b9dd..0000000000000 --- a/ee/app/engagement-dashboard/client/components/data/LegendSymbol.stories.js +++ /dev/null @@ -1,25 +0,0 @@ -import { Box, Margins } from '@rocket.chat/fuselage'; -import React from 'react'; - -import { LegendSymbol } from './LegendSymbol'; -import { monochromaticColors, polychromaticColors } from './colors'; - -export default { - title: 'admin/enterprise/engagement/data/LegendSymbol', - component: LegendSymbol, - decorators: [(fn) => ], -}; - -export const _default = () => - - Legend text -; - -export const withColor = () => <> - {monochromaticColors.map((color) => - {color} - )} - {polychromaticColors.map((color) => - {color} - )} -; diff --git a/ee/app/engagement-dashboard/client/components/data/colors.js b/ee/app/engagement-dashboard/client/components/data/colors.js deleted file mode 100644 index 9373ef2f3c444..0000000000000 --- a/ee/app/engagement-dashboard/client/components/data/colors.js +++ /dev/null @@ -1,2 +0,0 @@ -export const monochromaticColors = ['#E8F2FF', '#D1EBFE', '#A4D3FE', '#76B7FC', '#549DF9', '#1D74F5', '#10529E']; -export const polychromaticColors = ['#FFD031', '#2DE0A5', '#1D74F5']; diff --git a/ee/app/engagement-dashboard/client/index.js b/ee/app/engagement-dashboard/client/index.js deleted file mode 100644 index 86c73e4462ce6..0000000000000 --- a/ee/app/engagement-dashboard/client/index.js +++ /dev/null @@ -1 +0,0 @@ -import './routes'; diff --git a/ee/app/engagement-dashboard/client/routes.js b/ee/app/engagement-dashboard/client/routes.js deleted file mode 100644 index 87ea504892126..0000000000000 --- a/ee/app/engagement-dashboard/client/routes.js +++ /dev/null @@ -1,33 +0,0 @@ -import { hasAllPermission } from '../../../../app/authorization'; -import { registerAdminRoute, registerAdminSidebarItem } from '../../../../client/views/admin'; -import { hasLicense } from '../../license/client'; -import { createTemplateForComponent } from '../../../../client/lib/portals/createTemplateForComponent'; -import { appLayout } from '../../../../client/lib/appLayout'; - -registerAdminRoute('/engagement-dashboard/:tab?', { - name: 'engagement-dashboard', - action: async () => { - const licensed = await hasLicense('engagement-dashboard'); - if (!licensed) { - return; - } - - const EngagementDashboardRoute = createTemplateForComponent('EngagementDashboardRoute', () => import('./components/EngagementDashboardRoute'), { attachment: 'at-parent' }); - appLayout.render('main', { center: EngagementDashboardRoute }); - }, -}); - -hasLicense('engagement-dashboard').then((enabled) => { - if (!enabled) { - return; - } - - registerAdminSidebarItem({ - href: 'engagement-dashboard', - i18nLabel: 'Engagement Dashboard', - icon: 'file-keynote', - permissionGranted: () => hasAllPermission('view-statistics'), - }); -}).catch((error) => { - console.error('Error checking license.', error); -}); diff --git a/ee/app/engagement-dashboard/server/api/channels.js b/ee/app/engagement-dashboard/server/api/channels.js deleted file mode 100644 index e97dd3995df08..0000000000000 --- a/ee/app/engagement-dashboard/server/api/channels.js +++ /dev/null @@ -1,26 +0,0 @@ -import { check } from 'meteor/check'; - -import { API } from '../../../../../app/api/server'; -import { findAllChannelsWithNumberOfMessages } from '../lib/channels'; -import { transformDatesForAPI } from './helpers/date'; - -API.v1.addRoute('engagement-dashboard/channels/list', { authRequired: true }, { - get() { - const { start, end } = this.requestParams(); - const { offset, count } = this.getPaginationItems(); - - check(start, String); - check(end, String); - - const { channels, total } = Promise.await(findAllChannelsWithNumberOfMessages({ - ...transformDatesForAPI(start, end), - options: { offset, count }, - })); - return API.v1.success({ - channels, - count: channels.length, - offset, - total, - }); - }, -}); diff --git a/ee/app/engagement-dashboard/server/api/helpers/date.js b/ee/app/engagement-dashboard/server/api/helpers/date.js deleted file mode 100644 index 0aae7b2b01325..0000000000000 --- a/ee/app/engagement-dashboard/server/api/helpers/date.js +++ /dev/null @@ -1,14 +0,0 @@ -export const transformDatesForAPI = (start, end) => { - if (isNaN(Date.parse(start))) { - throw new Error('The "start" query parameter must be a valid date.'); - } - if (end && isNaN(Date.parse(end))) { - throw new Error('The "end" query parameter must be a valid date.'); - } - start = new Date(start); - end = new Date(end); - return { - start, - end, - }; -}; diff --git a/ee/app/engagement-dashboard/server/api/messages.js b/ee/app/engagement-dashboard/server/api/messages.js deleted file mode 100644 index 94b6428c011eb..0000000000000 --- a/ee/app/engagement-dashboard/server/api/messages.js +++ /dev/null @@ -1,41 +0,0 @@ -import { check } from 'meteor/check'; - -import { API } from '../../../../../app/api/server'; -import { findWeeklyMessagesSentData, findMessagesSentOrigin, findTopFivePopularChannelsByMessageSentQuantity } from '../lib/messages'; -import { transformDatesForAPI } from './helpers/date'; - -API.v1.addRoute('engagement-dashboard/messages/messages-sent', { authRequired: true }, { - get() { - const { start, end } = this.requestParams(); - - check(start, String); - check(end, String); - - const data = Promise.await(findWeeklyMessagesSentData(transformDatesForAPI(start, end))); - return API.v1.success(data); - }, -}); - -API.v1.addRoute('engagement-dashboard/messages/origin', { authRequired: true }, { - get() { - const { start, end } = this.requestParams(); - - check(start, String); - check(end, String); - - const data = Promise.await(findMessagesSentOrigin(transformDatesForAPI(start, end))); - return API.v1.success(data); - }, -}); - -API.v1.addRoute('engagement-dashboard/messages/top-five-popular-channels', { authRequired: true }, { - get() { - const { start, end } = this.requestParams(); - - check(start, String); - check(end, String); - - const data = Promise.await(findTopFivePopularChannelsByMessageSentQuantity(transformDatesForAPI(start, end))); - return API.v1.success(data); - }, -}); diff --git a/ee/app/engagement-dashboard/server/api/users.js b/ee/app/engagement-dashboard/server/api/users.js deleted file mode 100644 index 8a675146237a1..0000000000000 --- a/ee/app/engagement-dashboard/server/api/users.js +++ /dev/null @@ -1,67 +0,0 @@ -import { check } from 'meteor/check'; - -import { API } from '../../../../../app/api/server'; -import { - findWeeklyUsersRegisteredData, - findActiveUsersMonthlyData, - findBusiestsChatsInADayByHours, - findBusiestsChatsWithinAWeek, - findUserSessionsByHourWithinAWeek, -} from '../lib/users'; -import { transformDatesForAPI } from './helpers/date'; - -API.v1.addRoute('engagement-dashboard/users/new-users', { authRequired: true }, { - get() { - const { start, end } = this.requestParams(); - - check(start, String); - check(end, String); - - const data = Promise.await(findWeeklyUsersRegisteredData(transformDatesForAPI(start, end))); - return API.v1.success(data); - }, -}); - -API.v1.addRoute('engagement-dashboard/users/active-users', { authRequired: true }, { - get() { - const { start, end } = this.requestParams(); - - check(start, String); - check(end, String); - - const data = Promise.await(findActiveUsersMonthlyData(transformDatesForAPI(start, end))); - return API.v1.success(data); - }, -}); - -API.v1.addRoute('engagement-dashboard/users/chat-busier/hourly-data', { authRequired: true }, { - get() { - const { start } = this.requestParams(); - - const data = Promise.await(findBusiestsChatsInADayByHours(transformDatesForAPI(start))); - return API.v1.success(data); - }, -}); - -API.v1.addRoute('engagement-dashboard/users/chat-busier/weekly-data', { authRequired: true }, { - get() { - const { start } = this.requestParams(); - - check(start, String); - - const data = Promise.await(findBusiestsChatsWithinAWeek(transformDatesForAPI(start))); - return API.v1.success(data); - }, -}); - -API.v1.addRoute('engagement-dashboard/users/users-by-time-of-the-day-in-a-week', { authRequired: true }, { - get() { - const { start, end } = this.requestParams(); - - check(start, String); - check(end, String); - - const data = Promise.await(findUserSessionsByHourWithinAWeek(transformDatesForAPI(start, end))); - return API.v1.success(data); - }, -}); diff --git a/ee/app/engagement-dashboard/server/index.js b/ee/app/engagement-dashboard/server/index.js deleted file mode 100644 index f87494c93e267..0000000000000 --- a/ee/app/engagement-dashboard/server/index.js +++ /dev/null @@ -1,15 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { onLicense } from '../../license/server'; -import { fillFirstDaysOfMessagesIfNeeded } from './lib/messages'; -import { fillFirstDaysOfUsersIfNeeded } from './lib/users'; - -onLicense('engagement-dashboard', async () => { - await import('./listeners'); - await import('./api'); - Meteor.startup(async () => { - const date = new Date(); - fillFirstDaysOfUsersIfNeeded(date); - fillFirstDaysOfMessagesIfNeeded(date); - }); -}); diff --git a/ee/app/engagement-dashboard/server/lib/channels.js b/ee/app/engagement-dashboard/server/lib/channels.js deleted file mode 100644 index 923365e310851..0000000000000 --- a/ee/app/engagement-dashboard/server/lib/channels.js +++ /dev/null @@ -1,27 +0,0 @@ -import moment from 'moment'; - -import { Rooms } from '../../../../../app/models/server/raw'; -import { convertDateToInt, diffBetweenDaysInclusive } from './date'; - -export const findAllChannelsWithNumberOfMessages = async ({ start, end, options = {} }) => { - const daysBetweenDates = diffBetweenDaysInclusive(end, start); - const endOfLastWeek = moment(start).clone().subtract(1, 'days').toDate(); - const startOfLastWeek = moment(endOfLastWeek).clone().subtract(daysBetweenDates, 'days').toDate(); - const total = await Rooms.findChannelsWithNumberOfMessagesBetweenDate({ - start: convertDateToInt(start), - end: convertDateToInt(end), - startOfLastWeek: convertDateToInt(startOfLastWeek), - endOfLastWeek: convertDateToInt(endOfLastWeek), - onlyCount: true, - }).toArray(); - return { - channels: await Rooms.findChannelsWithNumberOfMessagesBetweenDate({ - start: convertDateToInt(start), - end: convertDateToInt(end), - startOfLastWeek: convertDateToInt(startOfLastWeek), - endOfLastWeek: convertDateToInt(endOfLastWeek), - options, - }), - total: total.length ? total[0].total : 0, - }; -}; diff --git a/ee/app/engagement-dashboard/server/lib/date.js b/ee/app/engagement-dashboard/server/lib/date.js deleted file mode 100644 index 189c5bd0ff36e..0000000000000 --- a/ee/app/engagement-dashboard/server/lib/date.js +++ /dev/null @@ -1,11 +0,0 @@ -import moment from 'moment'; - -export const convertDateToInt = (date) => parseInt(moment(date).clone().format('YYYYMMDD')); -export const convertIntToDate = (intValue) => moment(intValue, 'YYYYMMDD').clone().toDate(); -export const diffBetweenDays = (start, end) => moment(new Date(start)).clone().diff(new Date(end), 'days'); -export const diffBetweenDaysInclusive = (start, end) => diffBetweenDays(start, end) + 1; - -export const getTotalOfWeekItems = (weekItems, property) => weekItems.reduce((acc, item) => { - acc += item[property]; - return acc; -}, 0); diff --git a/ee/app/engagement-dashboard/server/listeners/index.js b/ee/app/engagement-dashboard/server/listeners/index.js deleted file mode 100644 index 0ca660eabe683..0000000000000 --- a/ee/app/engagement-dashboard/server/listeners/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import './messages'; -import './users'; diff --git a/ee/app/engagement-dashboard/server/listeners/messages.js b/ee/app/engagement-dashboard/server/listeners/messages.js deleted file mode 100644 index 56af5840abbec..0000000000000 --- a/ee/app/engagement-dashboard/server/listeners/messages.js +++ /dev/null @@ -1,5 +0,0 @@ -import { callbacks } from '../../../../../app/callbacks/server'; -import { handleMessagesSent, handleMessagesDeleted } from '../lib/messages'; - -callbacks.add('afterSaveMessage', handleMessagesSent); -callbacks.add('afterDeleteMessage', handleMessagesDeleted); diff --git a/ee/app/engagement-dashboard/server/listeners/users.js b/ee/app/engagement-dashboard/server/listeners/users.js deleted file mode 100644 index b6b730d2d9686..0000000000000 --- a/ee/app/engagement-dashboard/server/listeners/users.js +++ /dev/null @@ -1,4 +0,0 @@ -import { callbacks } from '../../../../../app/callbacks/server'; -import { handleUserCreated } from '../lib/users'; - -callbacks.add('afterCreateUser', handleUserCreated); diff --git a/ee/app/license/client/index.ts b/ee/app/license/client/index.ts index cb6aff1426ba0..744cdb774614f 100644 --- a/ee/app/license/client/index.ts +++ b/ee/app/license/client/index.ts @@ -1,17 +1,15 @@ -import { CachedCollectionManager } from '../../../../app/ui-cached-collection'; -import { call } from '../../../../client/lib/utils/call'; +import { fetchFeatures } from '../../../client/lib/fetchFeatures'; +import { queryClient } from '../../../../client/lib/queryClient'; -const allModules = new Promise>((resolve, reject) => { - CachedCollectionManager.onLogin(async () => { - try { - const features: string[] = await call('license:getModules'); - resolve(new Set(features)); - } catch (e) { - console.error('Error getting modules', e); - reject(e); - } +const allModules = queryClient.fetchQuery({ + queryKey: ['ee.features'], + queryFn: fetchFeatures, +}) + .then((features) => new Set(features)) + .catch((e) => { + console.error('Error getting modules', e); + return Promise.reject(e); }); -}); export async function hasLicense(feature: string): Promise { try { diff --git a/ee/app/license/server/bundles.ts b/ee/app/license/server/bundles.ts index f793d0888c95c..f66ff10a58439 100644 --- a/ee/app/license/server/bundles.ts +++ b/ee/app/license/server/bundles.ts @@ -1,5 +1,18 @@ +export type BundleFeature = + | 'auditing' + | 'canned-responses' + | 'ldap-enterprise' + | 'livechat-enterprise' + | 'omnichannel-mobile-enterprise' + | 'engagement-dashboard' + | 'push-privacy' + | 'scalability' + | 'teams-mention' + | 'saml-enterprise' + | 'oauth-enterprise'; + interface IBundle { - [key: string]: string[]; + [key: string]: BundleFeature[]; } const bundles: IBundle = { diff --git a/ee/app/license/server/license.ts b/ee/app/license/server/license.ts index dd666753f6218..888279c29c739 100644 --- a/ee/app/license/server/license.ts +++ b/ee/app/license/server/license.ts @@ -1,7 +1,7 @@ import { EventEmitter } from 'events'; import { Users } from '../../../../app/models/server'; -import { getBundleModules, isBundle, getBundleFromModule } from './bundles'; +import { getBundleModules, isBundle, getBundleFromModule, BundleFeature } from './bundles'; import decrypt from './decrypt'; import { getTagColor } from './getTagColor'; import { ILicense } from '../definitions/ILicense'; @@ -295,7 +295,7 @@ export function canAddNewUser(): boolean { return License.canAddNewUser(); } -export function onLicense(feature: string, cb: (...args: any[]) => void): void { +export function onLicense(feature: BundleFeature, cb: (...args: any[]) => void): void { if (hasLicense(feature)) { return cb(); } @@ -303,6 +303,63 @@ export function onLicense(feature: string, cb: (...args: any[]) => void): void { EnterpriseLicenses.once(`valid:${ feature }`, cb); } +export function onValidFeature(feature: BundleFeature, cb: () => void): (() => void) { + EnterpriseLicenses.on(`valid:${ feature }`, cb); + + if (hasLicense(feature)) { + cb(); + } + + return (): void => { + EnterpriseLicenses.off(`valid:${ feature }`, cb); + }; +} + +export function onInvalidFeature(feature: BundleFeature, cb: () => void): (() => void) { + EnterpriseLicenses.on(`invalid:${ feature }`, cb); + + if (!hasLicense(feature)) { + cb(); + } + + return (): void => { + EnterpriseLicenses.off(`invalid:${ feature }`, cb); + }; +} + +export function onToggledFeature(feature: BundleFeature, { + up, + down, +}: { + up?: () => void; + down?: () => void; +}): (() => void) { + let enabled = hasLicense(feature); + + const offValidFeature = onValidFeature(feature, () => { + if (!enabled) { + up?.(); + enabled = true; + } + }); + + const offInvalidFeature = onInvalidFeature(feature, () => { + if (enabled) { + down?.(); + enabled = false; + } + }); + + if (enabled) { + up?.(); + } + + return (): void => { + offValidFeature(); + offInvalidFeature(); + }; +} + export function onModule(cb: (...args: any[]) => void): void { EnterpriseLicenses.on('module', cb); } @@ -326,7 +383,7 @@ export interface IOverrideClassProperties { type Class = { new(...args: any[]): any }; -export function overwriteClassOnLicense(license: string, original: Class, overwrite: IOverrideClassProperties): void { +export function overwriteClassOnLicense(license: BundleFeature, original: Class, overwrite: IOverrideClassProperties): void { onLicense(license, () => { Object.entries(overwrite).forEach(([key, value]) => { const originalFn = original.prototype[key]; diff --git a/ee/client/.eslintrc.js b/ee/client/.eslintrc.js deleted file mode 100644 index 4d6bf74449b7b..0000000000000 --- a/ee/client/.eslintrc.js +++ /dev/null @@ -1,137 +0,0 @@ -module.exports = { - root: true, - extends: ['@rocket.chat/eslint-config', 'prettier'], - parser: 'babel-eslint', - plugins: ['react', 'react-hooks', 'prettier'], - rules: { - 'import/named': 'error', - 'import/order': [ - 'error', - { - 'newlines-between': 'always', - 'groups': ['builtin', 'external', 'internal', ['parent', 'sibling', 'index']], - 'alphabetize': { - order: 'asc', - }, - }, - ], - 'jsx-quotes': ['error', 'prefer-single'], - 'new-cap': [ - 'error', - { capIsNewExceptions: ['HTML.Comment', 'HTML.Raw', 'HTML.DIV', 'SHA256'] }, - ], - 'prefer-arrow-callback': ['error', { allowNamedFunctions: true }], - 'prettier/prettier': 2, - 'react/display-name': 'error', - 'react/jsx-uses-react': 'error', - 'react/jsx-uses-vars': 'error', - 'react/jsx-no-undef': 'error', - 'react/jsx-fragments': ['error', 'syntax'], - 'react/no-multi-comp': 'error', - 'react-hooks/rules-of-hooks': 'error', - 'react-hooks/exhaustive-deps': [ - 'warn', - { - additionalHooks: '(useComponentDidUpdate)', - }, - ], - }, - settings: { - 'import/resolver': { - node: { - extensions: ['.js', '.ts', '.tsx'], - }, - }, - 'react': { - version: 'detect', - }, - }, - env: { - browser: true, - es6: true, - }, - overrides: [ - { - files: ['**/*.ts', '**/*.tsx'], - extends: [ - 'plugin:@typescript-eslint/recommended', - 'plugin:@typescript-eslint/eslint-recommended', - '@rocket.chat/eslint-config', - 'prettier', - ], - parser: '@typescript-eslint/parser', - plugins: ['@typescript-eslint', 'react', 'react-hooks', 'prettier'], - rules: { - '@typescript-eslint/ban-ts-ignore': 'off', - '@typescript-eslint/indent': 'off', - '@typescript-eslint/interface-name-prefix': ['error', 'always'], - '@typescript-eslint/no-extra-parens': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-unused-vars': [ - 'error', - { - argsIgnorePattern: '^_', - }, - ], - 'func-call-spacing': 'off', - 'indent': 'off', - 'import/order': [ - 'error', - { - 'newlines-between': 'always', - 'groups': ['builtin', 'external', 'internal', ['parent', 'sibling', 'index']], - 'alphabetize': { - order: 'asc', - }, - }, - ], - 'jsx-quotes': ['error', 'prefer-single'], - 'new-cap': [ - 'error', - { capIsNewExceptions: ['HTML.Comment', 'HTML.Raw', 'HTML.DIV', 'SHA256'] }, - ], - 'no-extra-parens': 'off', - 'no-spaced-func': 'off', - 'no-unused-vars': 'off', - 'no-useless-constructor': 'off', - 'no-use-before-define': 'off', - 'prefer-arrow-callback': ['error', { allowNamedFunctions: true }], - 'prettier/prettier': 2, - 'react/display-name': 'error', - 'react/jsx-uses-react': 'error', - 'react/jsx-uses-vars': 'error', - 'react/jsx-no-undef': 'error', - 'react/jsx-fragments': ['error', 'syntax'], - 'react/no-multi-comp': 'error', - 'react-hooks/rules-of-hooks': 'error', - 'react-hooks/exhaustive-deps': [ - 'warn', - { - additionalHooks: '(useComponentDidUpdate)', - }, - ], - }, - env: { - browser: true, - es6: true, - }, - settings: { - 'import/resolver': { - node: { - extensions: ['.js', '.ts', '.tsx'], - }, - }, - 'react': { - version: 'detect', - }, - }, - }, - { - files: ['**/*.stories.js', '**/*.stories.jsx', '**/*.stories.ts', '**/*.stories.tsx'], - rules: { - 'react/display-name': 'off', - 'react/no-multi-comp': 'off', - }, - }, - ], -}; diff --git a/ee/client/.eslintrc.js b/ee/client/.eslintrc.js new file mode 120000 index 0000000000000..4ce23c9428e75 --- /dev/null +++ b/ee/client/.eslintrc.js @@ -0,0 +1 @@ +../../client/.eslintrc.js \ No newline at end of file diff --git a/ee/client/.prettierrc b/ee/client/.prettierrc deleted file mode 100644 index 0244eac568448..0000000000000 --- a/ee/client/.prettierrc +++ /dev/null @@ -1,12 +0,0 @@ -{ - "semi": true, - "bracketSpacing": true, - "arrowParens": "always", - "endOfLine": "lf", - "jsxSingleQuote": true, - "printWidth": 100, - "quoteProps": "consistent", - "singleQuote": true, - "trailingComma": "all", - "useTabs": true -} diff --git a/ee/client/.prettierrc b/ee/client/.prettierrc new file mode 120000 index 0000000000000..9d5a1f5613c49 --- /dev/null +++ b/ee/client/.prettierrc @@ -0,0 +1 @@ +../../client/.prettierrc \ No newline at end of file diff --git a/ee/client/audit/AuditPage.stories.js b/ee/client/audit/AuditPage.stories.js index 67bddf5a601c2..ce17ec4778212 100644 --- a/ee/client/audit/AuditPage.stories.js +++ b/ee/client/audit/AuditPage.stories.js @@ -3,7 +3,7 @@ import React from 'react'; import AuditPage from './AuditPage'; export default { - title: 'ee/Audit', + title: 'auditing/Audit', component: AuditPage, }; diff --git a/ee/client/index.ts b/ee/client/index.ts index 6cbeef1d20e15..079dc5ba69844 100644 --- a/ee/client/index.ts +++ b/ee/client/index.ts @@ -1,7 +1,7 @@ import '../app/auditing/client'; import '../app/authorization/client'; import '../app/canned-responses/client'; -import '../app/engagement-dashboard/client'; import '../app/license/client'; import '../app/livechat-enterprise/client'; import './omnichannel'; +import './startup'; diff --git a/ee/client/lib/fetchFeatures.ts b/ee/client/lib/fetchFeatures.ts new file mode 100644 index 0000000000000..c03b13cb4d43d --- /dev/null +++ b/ee/client/lib/fetchFeatures.ts @@ -0,0 +1,9 @@ +import { CachedCollectionManager } from '../../../app/ui-cached-collection/client'; +import { call } from '../../../client/lib/utils/call'; + +export const fetchFeatures = (): Promise => + new Promise((resolve, reject) => { + CachedCollectionManager.onLogin(() => { + call('license:getModules').then(resolve, reject); + }); + }); diff --git a/ee/client/lib/getFromRestApi.ts b/ee/client/lib/getFromRestApi.ts new file mode 100644 index 0000000000000..ec9f35154112e --- /dev/null +++ b/ee/client/lib/getFromRestApi.ts @@ -0,0 +1,24 @@ +import { APIClient } from '../../../app/utils/client/lib/RestApiClient'; +import { Serialized } from '../../../definition/Serialized'; +import { + MatchPathPattern, + OperationParams, + OperationResult, + PathFor, +} from '../../../definition/rest'; + +export const getFromRestApi = + >(endpoint: TPath) => + async ( + params: void extends OperationParams<'GET', MatchPathPattern> + ? void + : Serialized>>, + ): Promise>>> => { + const response = await APIClient.get(endpoint, params); + + if (typeof response === 'string') { + throw new Error('invalid response data type'); + } + + return response; + }; diff --git a/ee/client/lib/onToggledFeature.ts b/ee/client/lib/onToggledFeature.ts new file mode 100644 index 0000000000000..723e1816f28f3 --- /dev/null +++ b/ee/client/lib/onToggledFeature.ts @@ -0,0 +1,42 @@ +import { QueryObserver } from 'react-query'; + +import { queryClient } from '../../../client/lib/queryClient'; +import type { BundleFeature } from '../../app/license/server/bundles'; +import { fetchFeatures } from './fetchFeatures'; + +export const onToggledFeature = ( + feature: BundleFeature, + { + up, + down, + }: { + up?: () => void; + down?: () => void; + }, +): (() => void) => { + const observer = new QueryObserver(queryClient, { + queryKey: ['ee.features'], + queryFn: fetchFeatures, + }); + + let enabled = false; + + return observer.subscribe((result) => { + if (!result.isSuccess) { + return; + } + + const features = result.data; + const hasFeature = features.includes(feature); + + if (!enabled && hasFeature) { + up?.(); + enabled = true; + } + + if (enabled && !hasFeature) { + down?.(); + enabled = false; + } + }); +}; diff --git a/ee/client/omnichannel/BusinessHoursTable.stories.js b/ee/client/omnichannel/BusinessHoursTable.stories.js index 0301ac69a0855..8c9819b4c5e53 100644 --- a/ee/client/omnichannel/BusinessHoursTable.stories.js +++ b/ee/client/omnichannel/BusinessHoursTable.stories.js @@ -4,7 +4,7 @@ import React from 'react'; import BusinessHoursTable from './BusinessHoursTable'; export default { - title: 'omnichannel/businessHours/ee/BusinessHoursTable', + title: 'omnichannel/businessHours/BusinessHoursTable', component: BusinessHoursTable, }; diff --git a/ee/client/omnichannel/additionalForms/BusinessHoursMultiple.stories.js b/ee/client/omnichannel/additionalForms/BusinessHoursMultiple.stories.js index d3425b9b20bdb..6325b324e84ce 100644 --- a/ee/client/omnichannel/additionalForms/BusinessHoursMultiple.stories.js +++ b/ee/client/omnichannel/additionalForms/BusinessHoursMultiple.stories.js @@ -4,7 +4,7 @@ import React from 'react'; import BusinessHoursMultiple from './BusinessHoursMultiple'; export default { - title: 'omnichannel/businessHours/ee/BusinessHoursMultiple', + title: 'omnichannel/businessHours/BusinessHoursMultiple', component: BusinessHoursMultiple, }; diff --git a/ee/client/omnichannel/additionalForms/BusinessHoursTimeZone.stories.js b/ee/client/omnichannel/additionalForms/BusinessHoursTimeZone.stories.js index a9cd284f4dff7..5db113b2fc20d 100644 --- a/ee/client/omnichannel/additionalForms/BusinessHoursTimeZone.stories.js +++ b/ee/client/omnichannel/additionalForms/BusinessHoursTimeZone.stories.js @@ -4,7 +4,7 @@ import React from 'react'; import BusinessHoursTimeZone from './BusinessHoursTimeZone'; export default { - title: 'omnichannel/businessHours/ee/BusinessHoursTimeZone', + title: 'omnichannel/businessHours/BusinessHoursTimeZone', component: BusinessHoursTimeZone, }; diff --git a/ee/client/startup/engagementDashboard.ts b/ee/client/startup/engagementDashboard.ts new file mode 100644 index 0000000000000..d21bd5abf6527 --- /dev/null +++ b/ee/client/startup/engagementDashboard.ts @@ -0,0 +1,33 @@ +import { Meteor } from 'meteor/meteor'; + +import { hasAllPermission } from '../../../app/authorization/client'; +import { + registerAdminRoute, + registerAdminSidebarItem, + unregisterAdminSidebarItem, +} from '../../../client/views/admin'; +import { onToggledFeature } from '../lib/onToggledFeature'; + +const [registerRoute, unregisterRoute] = registerAdminRoute('/engagement-dashboard/:tab?', { + name: 'engagement-dashboard', + lazyRouteComponent: () => import('../views/admin/engagementDashboard/EngagementDashboardRoute'), + ready: false, +}); + +onToggledFeature('engagement-dashboard', { + up: () => + Meteor.startup(() => { + registerAdminSidebarItem({ + href: '/admin/engagement-dashboard', + i18nLabel: 'Engagement Dashboard', + icon: 'file-keynote', + permissionGranted: () => hasAllPermission('view-engagement-dashboard'), + }); + registerRoute(); + }), + down: () => + Meteor.startup(() => { + unregisterAdminSidebarItem('Engagement Dashboard'); + unregisterRoute(); + }), +}); diff --git a/ee/client/startup/index.ts b/ee/client/startup/index.ts new file mode 100644 index 0000000000000..546da11f5f46a --- /dev/null +++ b/ee/client/startup/index.ts @@ -0,0 +1 @@ +import './engagementDashboard'; diff --git a/ee/client/views/admin/engagementDashboard/EngagementDashboardPage.stories.tsx b/ee/client/views/admin/engagementDashboard/EngagementDashboardPage.stories.tsx new file mode 100644 index 0000000000000..02e5ca19e9e96 --- /dev/null +++ b/ee/client/views/admin/engagementDashboard/EngagementDashboardPage.stories.tsx @@ -0,0 +1,13 @@ +import { Meta, Story } from '@storybook/react'; +import React, { ReactElement } from 'react'; + +import EngagementDashboardPage from './EngagementDashboardPage'; + +export default { + title: 'admin/engagementDashboard/EngagementDashboardPage', + component: EngagementDashboardPage, + decorators: [(fn): ReactElement =>
    ], +} as Meta; + +export const Default: Story = () => ; +Default.storyName = 'EngagementDashboardPage'; diff --git a/ee/client/views/admin/engagementDashboard/EngagementDashboardPage.tsx b/ee/client/views/admin/engagementDashboard/EngagementDashboardPage.tsx new file mode 100644 index 0000000000000..0d7f67af89acf --- /dev/null +++ b/ee/client/views/admin/engagementDashboard/EngagementDashboardPage.tsx @@ -0,0 +1,66 @@ +import { Box, Select, Tabs } from '@rocket.chat/fuselage'; +import React, { ReactElement, useCallback, useMemo, useState } from 'react'; + +import Page from '../../../../../client/components/Page'; +import { useTranslation } from '../../../../../client/contexts/TranslationContext'; +import ChannelsTab from './channels/ChannelsTab'; +import MessagesTab from './messages/MessagesTab'; +import UsersTab from './users/UsersTab'; + +type EngagementDashboardPageProps = { + tab: 'users' | 'messages' | 'channels'; + onSelectTab?: (tab: 'users' | 'messages' | 'channels') => void; +}; + +const EngagementDashboardPage = ({ + tab = 'users', + onSelectTab, +}: EngagementDashboardPageProps): ReactElement => { + const t = useTranslation(); + + const timezoneOptions = useMemo( + () => [ + ['utc', t('UTC_Timezone')], + ['local', t('Local_Timezone')], + ], + [t], + ); + + const [timezoneId, setTimezoneId] = useState<'utc' | 'local'>('utc'); + const handleTimezoneChange = (timezoneId: string): void => + setTimezoneId(timezoneId as 'utc' | 'local'); + + const handleTabClick = useCallback( + (tab: 'users' | 'messages' | 'channels'): undefined | (() => void) => + onSelectTab ? (): void => onSelectTab(tab) : undefined, + [onSelectTab], + ); + + return ( + + + onChange(value as TPeriod)} + /> + ); +}; + +export default PeriodSelector; diff --git a/ee/client/views/admin/engagementDashboard/data/colors.ts b/ee/client/views/admin/engagementDashboard/data/colors.ts new file mode 100644 index 0000000000000..1013778aeca7b --- /dev/null +++ b/ee/client/views/admin/engagementDashboard/data/colors.ts @@ -0,0 +1,14 @@ +import colors from '@rocket.chat/fuselage-tokens/colors.json'; + +export const monochromaticColors = [ + colors.b100, + colors.b200, + colors.b300, + colors.b400, + colors.b500, + colors.b600, + colors.b700, + colors.b800, + colors.b900, +]; +export const polychromaticColors = [colors.y500, colors.g500, colors.b500]; diff --git a/ee/client/views/admin/engagementDashboard/data/periods.ts b/ee/client/views/admin/engagementDashboard/data/periods.ts new file mode 100644 index 0000000000000..3b1113833cbaa --- /dev/null +++ b/ee/client/views/admin/engagementDashboard/data/periods.ts @@ -0,0 +1,76 @@ +import moment from 'moment'; + +import { TranslationKey } from '../../../../../../client/contexts/TranslationContext'; + +const label = ( + translationKey: TranslationKey, + ...replacements: unknown[] +): readonly [translationKey: TranslationKey, ...replacements: unknown[]] => [ + translationKey, + ...replacements, +]; + +const lastNDays = + ( + n: number, + ): ((utc: boolean) => { + start: Date; + end: Date; + }) => + (utc): { start: Date; end: Date } => ({ + start: utc + ? moment.utc().startOf('day').subtract(n, 'days').toDate() + : moment() + .startOf('day') + .subtract(n + 1, 'days') + .toDate(), + end: utc + ? moment.utc().endOf('day').subtract(1, 'days').toDate() + : moment().endOf('day').toDate(), + }); + +export const periods = [ + { + key: 'last 7 days', + label: label('Last_7_days'), + range: lastNDays(7), + }, + { + key: 'last 30 days', + label: label('Last_30_days'), + range: lastNDays(30), + }, + { + key: 'last 90 days', + label: label('Last_90_days'), + range: lastNDays(90), + }, +] as const; + +export type Period = typeof periods[number]; + +export const getPeriod = (key: typeof periods[number]['key']): Period => { + const period = periods.find((period) => period.key === key); + + if (!period) { + throw new Error(`"${key}" is not a valid period key`); + } + + return period; +}; + +export const getPeriodRange = ( + key: typeof periods[number]['key'], + utc = false, +): { + start: Date; + end: Date; +} => { + const period = periods.find((period) => period.key === key); + + if (!period) { + throw new Error(`"${key}" is not a valid period key`); + } + + return period.range(utc); +}; diff --git a/ee/client/views/admin/engagementDashboard/data/usePeriodLabel.ts b/ee/client/views/admin/engagementDashboard/data/usePeriodLabel.ts new file mode 100644 index 0000000000000..075b0578844ed --- /dev/null +++ b/ee/client/views/admin/engagementDashboard/data/usePeriodLabel.ts @@ -0,0 +1,10 @@ +import { useMemo } from 'react'; + +import { useTranslation } from '../../../../../../client/contexts/TranslationContext'; +import { getPeriod, Period } from './periods'; + +export const usePeriodLabel = (period: Period['key']): string => { + const t = useTranslation(); + + return useMemo(() => t(...getPeriod(period).label), [period, t]); +}; diff --git a/ee/client/views/admin/engagementDashboard/data/usePeriodSelectorState.ts b/ee/client/views/admin/engagementDashboard/data/usePeriodSelectorState.ts new file mode 100644 index 0000000000000..35ce960e58338 --- /dev/null +++ b/ee/client/views/admin/engagementDashboard/data/usePeriodSelectorState.ts @@ -0,0 +1,25 @@ +import { useState } from 'react'; + +import { Period } from './periods'; + +export const usePeriodSelectorState = ( + ...periods: TPeriod[] +): [ + period: TPeriod, + periodSelectorProps: { + periods: TPeriod[]; + value: TPeriod; + onChange: (value: TPeriod) => void; + }, +] => { + const [period, setPeriod] = useState(periods[0]); + + return [ + period, + { + periods, + value: period, + onChange: (value): void => setPeriod(value), + }, + ]; +}; diff --git a/ee/client/views/admin/engagementDashboard/messages/MessagesPerChannelSection.tsx b/ee/client/views/admin/engagementDashboard/messages/MessagesPerChannelSection.tsx new file mode 100644 index 0000000000000..789a4a5e6648c --- /dev/null +++ b/ee/client/views/admin/engagementDashboard/messages/MessagesPerChannelSection.tsx @@ -0,0 +1,254 @@ +import { ResponsivePie } from '@nivo/pie'; +import { Box, Flex, Icon, Margins, Skeleton, Table, Tile } from '@rocket.chat/fuselage'; +import colors from '@rocket.chat/fuselage-tokens/colors'; +import React, { ReactElement, useMemo } from 'react'; + +import { useTranslation } from '../../../../../../client/contexts/TranslationContext'; +import Section from '../Section'; +import DownloadDataButton from '../data/DownloadDataButton'; +import LegendSymbol from '../data/LegendSymbol'; +import PeriodSelector from '../data/PeriodSelector'; +import { usePeriodSelectorState } from '../data/usePeriodSelectorState'; +import { useMessageOrigins } from './useMessageOrigins'; +import { useTopFivePopularChannels } from './useTopFivePopularChannels'; + +const MessagesPerChannelSection = (): ReactElement => { + const [period, periodSelectorProps] = usePeriodSelectorState( + 'last 7 days', + 'last 30 days', + 'last 90 days', + ); + + const t = useTranslation(); + + const { data: messageOriginsData } = useMessageOrigins({ period }); + const { data: topFivePopularChannelsData } = useTopFivePopularChannels({ period }); + + const pie = useMemo( + () => + messageOriginsData?.origins?.reduce<{ [roomType: string]: number }>( + (obj, { messages, t }) => ({ ...obj, [t]: messages }), + {}, + ), + [messageOriginsData], + ); + + const table = useMemo( + () => + topFivePopularChannelsData?.channels?.reduce< + { + i: number; + t: string; + name?: string; + messages: number; + }[] + >( + (entries, { t, messages, name, usernames }, i) => [ + ...entries, + { i, t, name: name || usernames?.join(' × '), messages }, + ], + [], + ), + [topFivePopularChannelsData], + ); + + return ( +
    + + + messageOriginsData?.origins.map(({ t, messages }) => [t, messages]) + } + /> + + } + > + + + + + + + + {pie ? ( + + + + + + ( + + {t('Value_messages', { value })} + + )} + /> + + + + + + + + + + + {t('Private_Chats')} + + + + {t('Private_Channels')} + + + + {t('Public_Channels')} + + + + + + + ) : ( + + )} + + + + + + + {table ? ( + {t('Most_popular_channels_top_5')} + ) : ( + + )} + + {table && !table.length && ( + + {t('Not_enough_data')} + + )} + {(!table || !!table.length) && ( + + + + {'#'} + {t('Channel')} + {t('Number_of_messages')} + + + + {table && + table.map(({ i, t, name, messages }) => ( + + {i + 1}. + + + {(t === 'd' && ) || + (t === 'c' && ) || + (t === 'p' && )} + + {name} + + {messages} + + ))} + {!table && + Array.from({ length: 5 }, (_, i) => ( + + + + + + + + + + + + ))} + +
    + )} +
    +
    +
    +
    +
    +
    +
    + ); +}; + +export default MessagesPerChannelSection; diff --git a/ee/client/views/admin/engagementDashboard/messages/MessagesSentSection.tsx b/ee/client/views/admin/engagementDashboard/messages/MessagesSentSection.tsx new file mode 100644 index 0000000000000..a753a40327d27 --- /dev/null +++ b/ee/client/views/admin/engagementDashboard/messages/MessagesSentSection.tsx @@ -0,0 +1,177 @@ +import { ResponsiveBar } from '@nivo/bar'; +import { Box, Flex, Skeleton } from '@rocket.chat/fuselage'; +import colors from '@rocket.chat/fuselage-tokens/colors.json'; +import moment from 'moment'; +import React, { ReactElement, useMemo } from 'react'; + +import CounterSet from '../../../../../../client/components/data/CounterSet'; +import { useTranslation } from '../../../../../../client/contexts/TranslationContext'; +import Section from '../Section'; +import DownloadDataButton from '../data/DownloadDataButton'; +import PeriodSelector from '../data/PeriodSelector'; +import { usePeriodLabel } from '../data/usePeriodLabel'; +import { usePeriodSelectorState } from '../data/usePeriodSelectorState'; +import { useMessagesSent } from './useMessagesSent'; + +const MessagesSentSection = (): ReactElement => { + const [period, periodSelectorProps] = usePeriodSelectorState( + 'last 7 days', + 'last 30 days', + 'last 90 days', + ); + const periodLabel = usePeriodLabel(period); + + const t = useTranslation(); + + const { data } = useMessagesSent({ period }); + + const [countFromPeriod, variatonFromPeriod, countFromYesterday, variationFromYesterday, values] = + useMemo(() => { + if (!data) { + return []; + } + + const values = Array.from( + { length: moment(data.end).diff(data.start, 'days') + 1 }, + (_, i) => ({ + date: moment(data.start).add(i, 'days').toISOString(), + newMessages: 0, + }), + ); + + for (const { day, messages } of data.days ?? []) { + const i = moment(day).diff(data.start, 'days'); + if (i >= 0) { + values[i].newMessages += messages; + } + } + + return [ + data.period?.count, + data.period?.variation, + data.yesterday?.count, + data.yesterday?.variation, + values, + ]; + }, [data]); + + return ( +
    + + + values?.map(({ date, newMessages }) => [date, newMessages]) + } + /> + + } + > + , + variation: variatonFromPeriod ?? 0, + description: periodLabel, + }, + { + count: countFromYesterday ?? , + variation: variationFromYesterday ?? 0, + description: t('Yesterday'), + }, + ]} + /> + + {values ? ( + + + + + moment(date).format('dddd'), + }) || + null + } + axisLeft={null} + animate={true} + // @ts-ignore + motionStiffness={90} + motionDamping={15} + theme={{ + // TODO: Get it from theme + axis: { + ticks: { + text: { + fill: colors.n600, + fontFamily: + 'Inter, -apple-system, system-ui, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Meiryo UI", Arial, sans-serif', + fontSize: '10px', + fontStyle: 'normal', + fontWeight: 600, + letterSpacing: '0.2px', + lineHeight: '12px', + }, + }, + }, + tooltip: { + container: { + backgroundColor: colors.n900, + boxShadow: + '0px 0px 12px rgba(47, 52, 61, 0.12), 0px 0px 2px rgba(47, 52, 61, 0.08)', + borderRadius: 2, + }, + }, + }} + tooltip={({ value }): ReactElement => ( + + {t('Value_messages', { value })} + + )} + /> + + + + + ) : ( + + )} + +
    + ); +}; + +export default MessagesSentSection; diff --git a/ee/client/views/admin/engagementDashboard/messages/MessagesTab.stories.tsx b/ee/client/views/admin/engagementDashboard/messages/MessagesTab.stories.tsx new file mode 100644 index 0000000000000..1ab04dcd6d4b9 --- /dev/null +++ b/ee/client/views/admin/engagementDashboard/messages/MessagesTab.stories.tsx @@ -0,0 +1,14 @@ +import { Margins } from '@rocket.chat/fuselage'; +import { Meta, Story } from '@storybook/react'; +import React from 'react'; + +import MessagesTab from './MessagesTab'; + +export default { + title: 'admin/engagementDashboard/MessagesTab', + component: MessagesTab, + decorators: [(fn) => ], +} as Meta; + +export const Default: Story = () => ; +Default.storyName = 'MessagesTab'; diff --git a/ee/app/engagement-dashboard/client/components/MessagesTab/index.js b/ee/client/views/admin/engagementDashboard/messages/MessagesTab.tsx similarity index 54% rename from ee/app/engagement-dashboard/client/components/MessagesTab/index.js rename to ee/client/views/admin/engagementDashboard/messages/MessagesTab.tsx index 8c111a61d41d5..438ebd5938ba9 100644 --- a/ee/app/engagement-dashboard/client/components/MessagesTab/index.js +++ b/ee/client/views/admin/engagementDashboard/messages/MessagesTab.tsx @@ -1,13 +1,15 @@ -import React from 'react'; import { Divider } from '@rocket.chat/fuselage'; +import React, { ReactElement } from 'react'; -import MessagesSentSection from './MessagesSentSection'; import MessagesPerChannelSection from './MessagesPerChannelSection'; +import MessagesSentSection from './MessagesSentSection'; -const MessagesTab = () => <> - - - -; +const MessagesTab = (): ReactElement => ( + <> + + + + +); export default MessagesTab; diff --git a/ee/client/views/admin/engagementDashboard/messages/useMessageOrigins.ts b/ee/client/views/admin/engagementDashboard/messages/useMessageOrigins.ts new file mode 100644 index 0000000000000..edb5e9d0eb594 --- /dev/null +++ b/ee/client/views/admin/engagementDashboard/messages/useMessageOrigins.ts @@ -0,0 +1,31 @@ +import { useQuery } from 'react-query'; + +import { getFromRestApi } from '../../../../lib/getFromRestApi'; +import { getPeriodRange, Period } from '../data/periods'; + +type UseMessageOriginsOptions = { period: Period['key'] }; + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export const useMessageOrigins = ({ period }: UseMessageOriginsOptions) => + useQuery( + ['admin/engagement-dashboard/messages/origins', { period }], + async () => { + const { start, end } = getPeriodRange(period); + + const response = await getFromRestApi('/v1/engagement-dashboard/messages/origin')({ + start: start.toISOString(), + end: end.toISOString(), + }); + + return response + ? { + ...response, + start, + end, + } + : undefined; + }, + { + refetchInterval: 5 * 60 * 1000, + }, + ); diff --git a/ee/client/views/admin/engagementDashboard/messages/useMessagesSent.ts b/ee/client/views/admin/engagementDashboard/messages/useMessagesSent.ts new file mode 100644 index 0000000000000..6612109bcd436 --- /dev/null +++ b/ee/client/views/admin/engagementDashboard/messages/useMessagesSent.ts @@ -0,0 +1,31 @@ +import { useQuery } from 'react-query'; + +import { getFromRestApi } from '../../../../lib/getFromRestApi'; +import { getPeriodRange, Period } from '../data/periods'; + +type UseMessagesSentOptions = { period: Period['key'] }; + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export const useMessagesSent = ({ period }: UseMessagesSentOptions) => + useQuery( + ['admin/engagement-dashboard/messages/messages-sent', { period }], + async () => { + const { start, end } = getPeriodRange(period); + + const response = await getFromRestApi('/v1/engagement-dashboard/messages/messages-sent')({ + start: start.toISOString(), + end: end.toISOString(), + }); + + return response + ? { + ...response, + start, + end, + } + : undefined; + }, + { + refetchInterval: 5 * 60 * 1000, + }, + ); diff --git a/ee/client/views/admin/engagementDashboard/messages/useTopFivePopularChannels.ts b/ee/client/views/admin/engagementDashboard/messages/useTopFivePopularChannels.ts new file mode 100644 index 0000000000000..7080b438618a8 --- /dev/null +++ b/ee/client/views/admin/engagementDashboard/messages/useTopFivePopularChannels.ts @@ -0,0 +1,33 @@ +import { useQuery } from 'react-query'; + +import { getFromRestApi } from '../../../../lib/getFromRestApi'; +import { getPeriodRange, Period } from '../data/periods'; + +type UseTopFivePopularChannelsOptions = { period: Period['key'] }; + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export const useTopFivePopularChannels = ({ period }: UseTopFivePopularChannelsOptions) => + useQuery( + ['admin/engagement-dashboard/messages/top-five-popular-channels', { period }], + async () => { + const { start, end } = getPeriodRange(period); + + const response = await getFromRestApi( + '/v1/engagement-dashboard/messages/top-five-popular-channels', + )({ + start: start.toISOString(), + end: end.toISOString(), + }); + + return response + ? { + ...response, + start, + end, + } + : undefined; + }, + { + refetchInterval: 5 * 60 * 1000, + }, + ); diff --git a/ee/client/views/admin/engagementDashboard/users/ActiveUsersSection.tsx b/ee/client/views/admin/engagementDashboard/users/ActiveUsersSection.tsx new file mode 100644 index 0000000000000..0723acc792e51 --- /dev/null +++ b/ee/client/views/admin/engagementDashboard/users/ActiveUsersSection.tsx @@ -0,0 +1,291 @@ +import { ResponsiveLine } from '@nivo/line'; +import { Box, Flex, Skeleton, Tile } from '@rocket.chat/fuselage'; +import colors from '@rocket.chat/fuselage-tokens/colors.json'; +import moment from 'moment'; +import React, { ReactElement, useMemo } from 'react'; + +import CounterSet from '../../../../../../client/components/data/CounterSet'; +import { useTranslation } from '../../../../../../client/contexts/TranslationContext'; +import { useFormatDate } from '../../../../../../client/hooks/useFormatDate'; +import Section from '../Section'; +import DownloadDataButton from '../data/DownloadDataButton'; +import LegendSymbol from '../data/LegendSymbol'; +import { useActiveUsers } from './useActiveUsers'; + +type ActiveUsersSectionProps = { + timezone: 'utc' | 'local'; +}; + +const ActiveUsersSection = ({ timezone }: ActiveUsersSectionProps): ReactElement => { + const utc = timezone === 'utc'; + const { data } = useActiveUsers({ utc }); + + const [ + countDailyActiveUsers, + diffDailyActiveUsers, + countWeeklyActiveUsers, + diffWeeklyActiveUsers, + countMonthlyActiveUsers, + diffMonthlyActiveUsers, + dauValues = [], + wauValues = [], + mauValues = [], + ] = useMemo(() => { + if (!data) { + return []; + } + + const createPoint = (i: number): { x: Date; y: number } => ({ + x: moment(data.start).add(i, 'days').toDate(), + y: 0, + }); + + const createPoints = (): { x: Date; y: number }[] => + Array.from({ length: moment(data.end).diff(data.start, 'days') + 1 }, (_, i) => + createPoint(i), + ); + + const dauValues = createPoints(); + const prevDauValue = createPoint(-1); + const wauValues = createPoints(); + const prevWauValue = createPoint(-1); + const mauValues = createPoints(); + const prevMauValue = createPoint(-1); + + const usersListsMap = data.month.reduce<{ [x: number]: string[] }>((map, dayData) => { + const date = utc + ? moment + .utc({ year: dayData.year, month: dayData.month - 1, day: dayData.day }) + .endOf('day') + : moment({ year: dayData.year, month: dayData.month - 1, day: dayData.day }).endOf('day'); + const dateOffset = date.diff(data.start, 'days'); + if (dateOffset >= 0) { + map[dateOffset] = dayData.usersList; + dauValues[dateOffset].y = dayData.users; + } + return map; + }, {}); + + const distributeValueOverPoints = ( + usersListsMap: { [x: number]: string[] }, + dateOffset: number, + T: number, + array: { x: Date; y: number }[], + ): void => { + const usersSet = new Set(); + for (let k = dateOffset; T > 0; k--, T--) { + if (usersListsMap[k]) { + usersListsMap[k].forEach((userId) => usersSet.add(userId)); + } + } + array[dateOffset].y = usersSet.size; + }; + + for (let i = 0; i < 30; i++) { + distributeValueOverPoints(usersListsMap, i, 7, wauValues); + distributeValueOverPoints(usersListsMap, i, 30, mauValues); + } + prevWauValue.y = wauValues[28].y; + prevMauValue.y = mauValues[28].y; + prevDauValue.y = dauValues[28].y; + + return [ + dauValues[dauValues.length - 1].y, + dauValues[dauValues.length - 1].y - prevDauValue.y, + wauValues[wauValues.length - 1].y, + wauValues[wauValues.length - 1].y - prevWauValue.y, + mauValues[mauValues.length - 1].y, + mauValues[mauValues.length - 1].y - prevMauValue.y, + dauValues, + wauValues, + mauValues, + ]; + }, [data, utc]); + + const formatDate = useFormatDate(); + const t = useTranslation(); + + return ( +
    { + const values = []; + + for (let i = 0; i < 30; i++) { + values.push([ + dauValues[i].x.toISOString(), + dauValues[i].y, + wauValues[i].y, + mauValues[i].y, + ]); + } + + return values; + }} + /> + } + > + , + variation: diffDailyActiveUsers ?? 0, + description: ( + <> + {t('Daily_Active_Users')} + + ), + }, + { + count: countWeeklyActiveUsers ?? , + variation: diffWeeklyActiveUsers ?? 0, + description: ( + <> + {t('Weekly_Active_Users')} + + ), + }, + { + count: countMonthlyActiveUsers ?? , + variation: diffMonthlyActiveUsers ?? 0, + description: ( + <> + {t('Monthly_Active_Users')} + + ), + }, + ]} + /> + + {data ? ( + + + + + + moment(date).format(dauValues.length === 7 ? 'dddd' : 'L'), + }} + animate={true} + motionStiffness={90} + motionDamping={15} + theme={{ + // TODO: Get it from theme + axis: { + ticks: { + text: { + fill: '#9EA2A8', + fontFamily: + 'Inter, -apple-system, system-ui, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Meiryo UI", Arial, sans-serif', + fontSize: '10px', + fontStyle: 'normal', + fontWeight: 600, + letterSpacing: '0.2px', + lineHeight: '12px', + }, + }, + }, + tooltip: { + container: { + backgroundColor: '#1F2329', + boxShadow: + '0px 0px 12px rgba(47, 52, 61, 0.12), 0px 0px 2px rgba(47, 52, 61, 0.08)', + borderRadius: 2, + }, + }, + }} + enableSlices='x' + sliceTooltip={({ slice: { points } }): ReactElement => ( + + + {formatDate(points[0].data.x)} + {points.map(({ serieId, data: { y: activeUsers } }) => ( + + + {(serieId === 'dau' && t('DAU_value', { value: activeUsers })) || + (serieId === 'wau' && t('WAU_value', { value: activeUsers })) || + (serieId === 'mau' && t('MAU_value', { value: activeUsers }))} + + + ))} + + + )} + /> + + + + + ) : ( + + )} + +
    + ); +}; + +export default ActiveUsersSection; diff --git a/ee/client/views/admin/engagementDashboard/users/BusiestChatTimesSection.tsx b/ee/client/views/admin/engagementDashboard/users/BusiestChatTimesSection.tsx new file mode 100644 index 0000000000000..839f0cfd6e97c --- /dev/null +++ b/ee/client/views/admin/engagementDashboard/users/BusiestChatTimesSection.tsx @@ -0,0 +1,59 @@ +import { Select } from '@rocket.chat/fuselage'; +import React, { ReactElement, useMemo, useState } from 'react'; + +import { useTranslation } from '../../../../../../client/contexts/TranslationContext'; +import Section from '../Section'; +import ContentForDays from './ContentForDays'; +import ContentForHours from './ContentForHours'; + +type TimeUnit = 'hours' | 'days'; + +type BusiestChatTimesSectionProps = { + timezone: 'utc' | 'local'; +}; + +const BusiestChatTimesSection = ({ timezone }: BusiestChatTimesSectionProps): ReactElement => { + const t = useTranslation(); + + const [timeUnit, setTimeUnit] = useState('hours'); + const timeUnitOptions = useMemo( + () => [ + ['hours', t('Hours')], + ['days', t('Days')], + ], + [t], + ); + + const [displacement, setDisplacement] = useState(0); + + const handleTimeUnitChange = (timeUnit: string): void => { + setTimeUnit(timeUnit as TimeUnit); + setDisplacement(0); + }; + + const handlePreviousDateClick = (): void => setDisplacement((displacement) => displacement + 1); + const handleNextDateClick = (): void => setDisplacement((displacement) => displacement - 1); + + const Content = ( + { + hours: ContentForHours, + days: ContentForDays, + } as const + )[timeUnit]; + + return ( +
    } + > + +
    + ); +}; + +export default BusiestChatTimesSection; diff --git a/ee/client/views/admin/engagementDashboard/users/ContentForDays.tsx b/ee/client/views/admin/engagementDashboard/users/ContentForDays.tsx new file mode 100644 index 0000000000000..296d5661de010 --- /dev/null +++ b/ee/client/views/admin/engagementDashboard/users/ContentForDays.tsx @@ -0,0 +1,139 @@ +import { ResponsiveBar } from '@nivo/bar'; +import { Box, Button, Chevron, Flex, Margins, Skeleton } from '@rocket.chat/fuselage'; +import colors from '@rocket.chat/fuselage-tokens/colors'; +import moment from 'moment'; +import React, { ReactElement, useMemo } from 'react'; + +import { useWeeklyChatActivity } from './useWeeklyChatActivity'; + +type ContentForDaysProps = { + displacement: number; + onPreviousDateClick: () => void; + onNextDateClick: () => void; + timezone: 'utc' | 'local'; +}; + +const ContentForDays = ({ + displacement, + onPreviousDateClick, + onNextDateClick, + timezone, +}: ContentForDaysProps): ReactElement => { + const utc = timezone === 'utc'; + const { data } = useWeeklyChatActivity({ displacement, utc }); + + const formattedCurrentDate = useMemo(() => { + if (!data) { + return null; + } + + const endOfWeek = moment(data.day); + const startOfWeek = moment(data.day).subtract(6, 'days'); + return `${startOfWeek.format('L')} - ${endOfWeek.format('L')}`; + }, [data]); + + const values = useMemo( + () => + data?.month + ?.map(({ users, day, month, year }) => ({ + users, + day: moment({ year, month: month - 1, day }), + })) + ?.sort(({ day: a }, { day: b }) => a.diff(b)) + ?.map(({ users, day }) => ({ users, day: String(day.valueOf()) })) ?? [], + [data], + ); + + return ( + <> + + + + + + + {formattedCurrentDate} + + + + + + + + {data ? ( + + + + + moment(parseInt(timestamp, 10)).format('L'), + }} + axisLeft={null} + animate={true} + // @ts-ignore + motionStiffness={90} + motionDamping={15} + theme={{ + // TODO: Get it from theme + axis: { + ticks: { + text: { + fill: colors.n600, + fontFamily: + 'Inter, -apple-system, system-ui, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Meiryo UI", Arial, sans-serif', + fontSize: '10px', + fontStyle: 'normal', + fontWeight: 600, + letterSpacing: '0.2px', + lineHeight: '12px', + }, + }, + }, + }} + /> + + + + + ) : ( + + )} + + + ); +}; + +export default ContentForDays; diff --git a/ee/client/views/admin/engagementDashboard/users/ContentForHours.tsx b/ee/client/views/admin/engagementDashboard/users/ContentForHours.tsx new file mode 100644 index 0000000000000..28b259947655c --- /dev/null +++ b/ee/client/views/admin/engagementDashboard/users/ContentForHours.tsx @@ -0,0 +1,140 @@ +import { ResponsiveBar } from '@nivo/bar'; +import { Box, Button, Chevron, Skeleton } from '@rocket.chat/fuselage'; +import { useBreakpoints } from '@rocket.chat/fuselage-hooks'; +import colors from '@rocket.chat/fuselage-tokens/colors.json'; +import moment from 'moment'; +import React, { ReactElement, useMemo } from 'react'; + +import { useTranslation } from '../../../../../../client/contexts/TranslationContext'; +import { useHourlyChatActivity } from './useHourlyChatActivity'; + +type ContentForHoursProps = { + displacement: number; + onPreviousDateClick: () => void; + onNextDateClick: () => void; + timezone: 'utc' | 'local'; +}; + +const ContentForHours = ({ + displacement, + onPreviousDateClick, + onNextDateClick, + timezone, +}: ContentForHoursProps): ReactElement => { + const utc = timezone === 'utc'; + const { data } = useHourlyChatActivity({ displacement, utc }); + + const t = useTranslation(); + const isLgScreen = useBreakpoints().includes('lg'); + + const values = useMemo(() => { + if (!data) { + return []; + } + + const divider = 2; + const values = Array.from({ length: 24 / divider }, (_, i) => ({ + hour: String(divider * i), + users: 0, + })); + + for (const { hour, users } of data?.hours ?? []) { + const i = Math.floor(hour / divider); + values[i] = values[i] || { hour: String(divider * i), users: 0 }; + values[i].users += users; + } + + return values; + }, [data]); + + return ( + <> + + + + {data ? moment(data.day).format(displacement < 7 ? 'dddd' : 'L') : null} + + + + {data ? ( + + + + + moment().set({ hour, minute: 0, second: 0 }).format('LT'), + }} + axisLeft={null} + animate={true} + motionStiffness={90} + motionDamping={15} + theme={{ + // TODO: Get it from theme + axis: { + ticks: { + text: { + fill: colors.n600, + fontFamily: + 'Inter, -apple-system, system-ui, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Meiryo UI", Arial, sans-serif', + fontSize: '10px', + fontStyle: 'normal', + fontWeight: 600, + letterSpacing: '0.2px', + lineHeight: '12px', + }, + }, + }, + tooltip: { + // @ts-ignore + backgroundColor: colors.n900, + boxShadow: + '0px 0px 12px rgba(47, 52, 61, 0.12), 0px 0px 2px rgba(47, 52, 61, 0.08)', + borderRadius: 2, + padding: 4, + }, + }} + tooltip={({ value }): ReactElement => ( + + {t('Value_users', { value })} + + )} + /> + + + + ) : ( + + )} + + ); +}; + +export default ContentForHours; diff --git a/ee/client/views/admin/engagementDashboard/users/NewUsersSection.tsx b/ee/client/views/admin/engagementDashboard/users/NewUsersSection.tsx new file mode 100644 index 0000000000000..c1b4b8d62e133 --- /dev/null +++ b/ee/client/views/admin/engagementDashboard/users/NewUsersSection.tsx @@ -0,0 +1,224 @@ +import { ResponsiveBar } from '@nivo/bar'; +import { Box, Flex, Skeleton } from '@rocket.chat/fuselage'; +import { useResizeObserver } from '@rocket.chat/fuselage-hooks'; +import colors from '@rocket.chat/fuselage-tokens/colors.json'; +import moment from 'moment'; +import React, { ReactElement, useMemo } from 'react'; + +import CounterSet from '../../../../../../client/components/data/CounterSet'; +import { useTranslation } from '../../../../../../client/contexts/TranslationContext'; +import { useFormatDate } from '../../../../../../client/hooks/useFormatDate'; +import Section from '../Section'; +import DownloadDataButton from '../data/DownloadDataButton'; +import PeriodSelector from '../data/PeriodSelector'; +import { usePeriodLabel } from '../data/usePeriodLabel'; +import { usePeriodSelectorState } from '../data/usePeriodSelectorState'; +import { useNewUsers } from './useNewUsers'; + +const TICK_WIDTH = 45; + +type NewUsersSectionProps = { + timezone: 'utc' | 'local'; +}; + +const NewUsersSection = ({ timezone }: NewUsersSectionProps): ReactElement => { + const [period, periodSelectorProps] = usePeriodSelectorState( + 'last 7 days', + 'last 30 days', + 'last 90 days', + ); + const periodLabel = usePeriodLabel(period); + + const utc = timezone === 'utc'; + const { data } = useNewUsers({ period, utc }); + + const t = useTranslation(); + + const formatDate = useFormatDate(); + + const { ref: sizeRef, contentBoxSize: { inlineSize = 600 } = {} } = useResizeObserver(); + + const maxTicks = Math.ceil(inlineSize / TICK_WIDTH); + + const tickValues = useMemo(() => { + if (!data) { + return undefined; + } + + const arrayLength = moment(data.end).diff(data.start, 'days') + 1; + if (arrayLength <= maxTicks || !maxTicks) { + return undefined; + } + + const values = Array.from({ length: arrayLength }, (_, i) => + moment(data.start).add(i, 'days').format('YYYY-MM-DD'), + ); + + const relation = Math.ceil(values.length / maxTicks); + + return values.reduce((acc, cur, i) => { + if ((i + 1) % relation === 0) { + acc = [...acc, cur]; + } + return acc; + }, [] as string[]); + }, [data, maxTicks]); + + const [countFromPeriod, variatonFromPeriod, countFromYesterday, variationFromYesterday, values] = + useMemo(() => { + if (!data) { + return []; + } + + const values = Array.from( + { length: moment(data.end).diff(data.start, 'days') + 1 }, + (_, i) => ({ + date: moment(data.start).add(i, 'days').format('YYYY-MM-DD'), + newUsers: 0, + }), + ); + for (const { day, users } of data.days) { + const i = utc + ? moment(day).utc().diff(data.start, 'days') + : moment(day).diff(data.start, 'days'); + if (i >= 0) { + values[i].newUsers += users; + } + } + + return [ + data.period.count, + data.period.variation, + data.yesterday.count, + data.yesterday.variation, + values, + ]; + }, [data, utc]); + + return ( +
    + + + values?.map(({ date, newUsers }) => [date, newUsers]) + } + /> + + } + > + , + variation: variatonFromPeriod ?? 0, + description: periodLabel, + }, + { + count: countFromYesterday ?? , + variation: variationFromYesterday ?? 0, + description: t('Yesterday'), + }, + ]} + /> + + {values ? ( + + + + + + moment(date).format(values?.length === 7 ? 'dddd' : 'DD/MM'), + }} + axisLeft={{ + tickSize: 0, + // TODO: Get it from theme + tickPadding: 4, + tickRotation: 0, + }} + animate={true} + motionStiffness={90} + motionDamping={15} + theme={{ + // TODO: Get it from theme + axis: { + ticks: { + text: { + fill: colors.n600, + fontFamily: + 'Inter, -apple-system, system-ui, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Meiryo UI", Arial, sans-serif', + fontSize: '10px', + fontStyle: 'normal', + fontWeight: 600, + letterSpacing: '0.2px', + lineHeight: '12px', + }, + }, + }, + tooltip: { + // @ts-ignore + backgroundColor: colors.n900, + boxShadow: + '0px 0px 12px rgba(47, 52, 61, 0.12), 0px 0px 2px rgba(47, 52, 61, 0.08)', + borderRadius: 2, + padding: 4, + }, + }} + tooltip={({ value, indexValue }): ReactElement => ( + + {t('Value_users', { value })}, {formatDate(indexValue)} + + )} + /> + + + + + ) : ( + + + + )} + +
    + ); +}; + +export default NewUsersSection; diff --git a/ee/client/views/admin/engagementDashboard/users/UsersByTimeOfTheDaySection.tsx b/ee/client/views/admin/engagementDashboard/users/UsersByTimeOfTheDaySection.tsx new file mode 100644 index 0000000000000..933e4869cb422 --- /dev/null +++ b/ee/client/views/admin/engagementDashboard/users/UsersByTimeOfTheDaySection.tsx @@ -0,0 +1,198 @@ +import { ResponsiveHeatMap } from '@nivo/heatmap'; +import { Box, Flex, Skeleton } from '@rocket.chat/fuselage'; +import colors from '@rocket.chat/fuselage-tokens/colors.json'; +import moment from 'moment'; +import React, { ReactElement, useMemo } from 'react'; + +import { useTranslation } from '../../../../../../client/contexts/TranslationContext'; +import Section from '../Section'; +import DownloadDataButton from '../data/DownloadDataButton'; +import PeriodSelector from '../data/PeriodSelector'; +import { usePeriodSelectorState } from '../data/usePeriodSelectorState'; +import { useUsersByTimeOfTheDay } from './useUsersByTimeOfTheDay'; + +type UsersByTimeOfTheDaySectionProps = { + timezone: 'utc' | 'local'; +}; + +const UsersByTimeOfTheDaySection = ({ + timezone, +}: UsersByTimeOfTheDaySectionProps): ReactElement => { + const [period, periodSelectorProps] = usePeriodSelectorState( + 'last 7 days', + 'last 30 days', + 'last 90 days', + ); + + const utc = timezone === 'utc'; + + const { data } = useUsersByTimeOfTheDay({ period, utc }); + + const t = useTranslation(); + + const [dates, values] = useMemo(() => { + if (!data) { + return []; + } + + const dates = Array.from( + { + length: utc + ? moment(data.end).diff(data.start, 'days') + 1 + : moment(data.end).diff(data.start, 'days') - 1, + }, + (_, i) => + moment(data.start) + .endOf('day') + .add(utc ? i : i + 1, 'days'), + ); + + const values = Array.from( + { length: 24 }, + (_, hour) => + ({ + hour: String(hour), + ...dates + .map((date) => ({ [date.toISOString()]: 0 })) + .reduce((obj, elem) => ({ ...obj, ...elem }), {}), + } as { [date: string]: number } & { hour: string }), + ); + + const timezoneOffset = moment().utcOffset() / 60; + + for (const { users, hour, day, month, year } of data.week) { + const date = utc + ? moment.utc([year, month - 1, day, hour]) + : moment([year, month - 1, day, hour]).add(timezoneOffset, 'hours'); + + if (utc || (!date.isSame(data.end) && !date.clone().startOf('day').isSame(data.start))) { + values[date.hour()][date.endOf('day').toISOString()] += users; + } + } + + return [dates.map((date) => date.toISOString()), values]; + }, [data, utc]); + + return ( +
    + + + data?.week + ?.map(({ users, hour, day, month, year }) => ({ + date: moment([year, month - 1, day, hour, 0, 0, 0]), + users, + })) + ?.sort((a, b) => a.date.diff(b.date)) + ?.map(({ date, users }) => [date.toISOString(), users]) + } + /> + + } + > + {values ? ( + + + + + + dates?.length === 7 ? moment(isoString).format('dddd') : '', + }} + axisLeft={{ + // TODO: Get it from theme + tickSize: 0, + tickPadding: 4, + tickRotation: 0, + format: (hour): string => + moment() + .set({ hour: parseInt(hour, 10), minute: 0, second: 0 }) + .format('LT'), + }} + hoverTarget='cell' + animate={dates && dates.length <= 7} + motionStiffness={90} + motionDamping={15} + theme={{ + // TODO: Get it from theme + axis: { + ticks: { + text: { + fill: colors.n600, + fontFamily: + 'Inter, -apple-system, system-ui, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Meiryo UI", Arial, sans-serif', + fontSize: 10, + fontStyle: 'normal', + fontWeight: 600, + letterSpacing: '0.2px', + lineHeight: '12px', + }, + }, + }, + tooltip: { + container: { + backgroundColor: colors.n900, + boxShadow: + '0px 0px 12px rgba(47, 52, 61, 0.12), 0px 0px 2px rgba(47, 52, 61, 0.08)', + borderRadius: 2, + }, + }, + }} + tooltip={({ value }): ReactElement => ( + + {t('Value_users', { value })} + + )} + /> + + + + + ) : ( + + )} +
    + ); +}; + +export default UsersByTimeOfTheDaySection; diff --git a/ee/client/views/admin/engagementDashboard/users/UsersTab.stories.tsx b/ee/client/views/admin/engagementDashboard/users/UsersTab.stories.tsx new file mode 100644 index 0000000000000..45ec3cd06fc51 --- /dev/null +++ b/ee/client/views/admin/engagementDashboard/users/UsersTab.stories.tsx @@ -0,0 +1,14 @@ +import { Margins } from '@rocket.chat/fuselage'; +import { Meta, Story } from '@storybook/react'; +import React, { ReactElement } from 'react'; + +import UsersTab from './UsersTab'; + +export default { + title: 'admin/engagementDashboard/UsersTab', + component: UsersTab, + decorators: [(fn): ReactElement => ], +} as Meta; + +export const Default: Story = () => ; +Default.storyName = 'UsersTab'; diff --git a/ee/client/views/admin/engagementDashboard/users/UsersTab.tsx b/ee/client/views/admin/engagementDashboard/users/UsersTab.tsx new file mode 100644 index 0000000000000..f870c4fa3593f --- /dev/null +++ b/ee/client/views/admin/engagementDashboard/users/UsersTab.tsx @@ -0,0 +1,37 @@ +import { Box, Divider, Flex, Margins } from '@rocket.chat/fuselage'; +import { useBreakpoints } from '@rocket.chat/fuselage-hooks'; +import React, { ReactElement } from 'react'; + +import ActiveUsersSection from './ActiveUsersSection'; +import BusiestChatTimesSection from './BusiestChatTimesSection'; +import NewUsersSection from './NewUsersSection'; +import UsersByTimeOfTheDaySection from './UsersByTimeOfTheDaySection'; + +type UsersTabProps = { + timezone: 'utc' | 'local'; +}; + +const UsersTab = ({ timezone }: UsersTabProps): ReactElement => { + const isXxlScreen = useBreakpoints().includes('xxl'); + + return ( + <> + + + + + + + + + + + + + + + + ); +}; + +export default UsersTab; diff --git a/ee/client/views/admin/engagementDashboard/users/useActiveUsers.ts b/ee/client/views/admin/engagementDashboard/users/useActiveUsers.ts new file mode 100644 index 0000000000000..17bc28f4d8f07 --- /dev/null +++ b/ee/client/views/admin/engagementDashboard/users/useActiveUsers.ts @@ -0,0 +1,32 @@ +import moment from 'moment'; +import { useQuery } from 'react-query'; + +import { getFromRestApi } from '../../../../lib/getFromRestApi'; +import { getPeriodRange } from '../data/periods'; + +type UseActiveUsersOptions = { utc: boolean }; + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export const useActiveUsers = ({ utc }: UseActiveUsersOptions) => + useQuery( + ['admin/engagement-dashboard/users/active', { utc }], + async () => { + const { start, end } = getPeriodRange('last 30 days', utc); + + const response = await getFromRestApi('/v1/engagement-dashboard/users/active-users')({ + start: (utc ? moment.utc(start) : moment(start)).subtract(29, 'days').toISOString(), + end: end.toISOString(), + }); + + return response + ? { + ...response, + start, + end, + } + : undefined; + }, + { + refetchInterval: 5 * 60 * 1000, + }, + ); diff --git a/ee/client/views/admin/engagementDashboard/users/useHourlyChatActivity.ts b/ee/client/views/admin/engagementDashboard/users/useHourlyChatActivity.ts new file mode 100644 index 0000000000000..ab21f3d2f38d3 --- /dev/null +++ b/ee/client/views/admin/engagementDashboard/users/useHourlyChatActivity.ts @@ -0,0 +1,36 @@ +import moment from 'moment'; +import { useQuery } from 'react-query'; + +import { getFromRestApi } from '../../../../lib/getFromRestApi'; + +type UseHourlyChatActivityOptions = { + displacement: number; + utc: boolean; +}; + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export const useHourlyChatActivity = ({ displacement, utc }: UseHourlyChatActivityOptions) => + useQuery( + ['admin/engagement-dashboard/users/hourly-chat-activity', { displacement, utc }], + async () => { + const day = (utc ? moment.utc().endOf('day') : moment().endOf('day')) + .subtract(displacement, 'days') + .toDate(); + + const response = await getFromRestApi( + '/v1/engagement-dashboard/users/chat-busier/hourly-data', + )({ + start: day.toISOString(), + }); + + return response + ? { + ...response, + day, + } + : undefined; + }, + { + refetchInterval: 5 * 60 * 1000, + }, + ); diff --git a/ee/client/views/admin/engagementDashboard/users/useNewUsers.ts b/ee/client/views/admin/engagementDashboard/users/useNewUsers.ts new file mode 100644 index 0000000000000..4fcbe5afb2500 --- /dev/null +++ b/ee/client/views/admin/engagementDashboard/users/useNewUsers.ts @@ -0,0 +1,31 @@ +import { useQuery } from 'react-query'; + +import { getFromRestApi } from '../../../../lib/getFromRestApi'; +import { getPeriodRange, Period } from '../data/periods'; + +type UseNewUsersOptions = { period: Period['key']; utc: boolean }; + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export const useNewUsers = ({ period, utc }: UseNewUsersOptions) => + useQuery( + ['admin/engagement-dashboard/users/new', { period, utc }], + async () => { + const { start, end } = getPeriodRange(period, utc); + + const response = await getFromRestApi('/v1/engagement-dashboard/users/new-users')({ + start: start.toISOString(), + end: end.toISOString(), + }); + + return response + ? { + ...response, + start, + end, + } + : undefined; + }, + { + refetchInterval: 5 * 60 * 1000, + }, + ); diff --git a/ee/client/views/admin/engagementDashboard/users/useUsersByTimeOfTheDay.ts b/ee/client/views/admin/engagementDashboard/users/useUsersByTimeOfTheDay.ts new file mode 100644 index 0000000000000..91da8883a49e3 --- /dev/null +++ b/ee/client/views/admin/engagementDashboard/users/useUsersByTimeOfTheDay.ts @@ -0,0 +1,33 @@ +import { useQuery } from 'react-query'; + +import { getFromRestApi } from '../../../../lib/getFromRestApi'; +import { getPeriodRange, Period } from '../data/periods'; + +type UseUsersByTimeOfTheDayOptions = { period: Period['key']; utc: boolean }; + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export const useUsersByTimeOfTheDay = ({ period, utc }: UseUsersByTimeOfTheDayOptions) => + useQuery( + ['admin/engagement-dashboard/users/users-by-time-of-the-day', { period, utc }], + async () => { + const { start, end } = getPeriodRange(period, utc); + + const response = await getFromRestApi( + '/v1/engagement-dashboard/users/users-by-time-of-the-day-in-a-week', + )({ + start: start.toISOString(), + end: end.toISOString(), + }); + + return response + ? { + ...response, + start, + end, + } + : undefined; + }, + { + refetchInterval: 5 * 60 * 1000, + }, + ); diff --git a/ee/client/views/admin/engagementDashboard/users/useWeeklyChatActivity.ts b/ee/client/views/admin/engagementDashboard/users/useWeeklyChatActivity.ts new file mode 100644 index 0000000000000..b6327e61253c1 --- /dev/null +++ b/ee/client/views/admin/engagementDashboard/users/useWeeklyChatActivity.ts @@ -0,0 +1,36 @@ +import moment from 'moment'; +import { useQuery } from 'react-query'; + +import { getFromRestApi } from '../../../../lib/getFromRestApi'; + +type UseWeeklyChatActivityOptions = { + displacement: number; + utc: boolean; +}; + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export const useWeeklyChatActivity = ({ displacement, utc }: UseWeeklyChatActivityOptions) => + useQuery( + ['admin/engagement-dashboard/users/weekly-chat-activity', { displacement, utc }], + async () => { + const day = (utc ? moment.utc().endOf('day') : moment().endOf('day')) + .subtract(displacement, 'weeks') + .toDate(); + + const response = await getFromRestApi( + '/v1/engagement-dashboard/users/chat-busier/weekly-data', + )({ + start: day.toISOString(), + }); + + return response + ? { + ...response, + day, + } + : undefined; + }, + { + refetchInterval: 5 * 60 * 1000, + }, + ); diff --git a/ee/client/views/admin/users/SeatsCapUsage/SeatsCapUsage.stories.tsx b/ee/client/views/admin/users/SeatsCapUsage/SeatsCapUsage.stories.tsx index f229dd1bc3b01..3eb3405eb01ff 100644 --- a/ee/client/views/admin/users/SeatsCapUsage/SeatsCapUsage.stories.tsx +++ b/ee/client/views/admin/users/SeatsCapUsage/SeatsCapUsage.stories.tsx @@ -3,7 +3,7 @@ import React, { ReactElement } from 'react'; import SeatsCapUsage from './SeatsCapUsage'; export default { - title: 'ee/admin/users/SeatsCapUsage', + title: 'admin/users/SeatsCapUsage', component: SeatsCapUsage, }; diff --git a/ee/definition/.eslintrc.js b/ee/definition/.eslintrc.js new file mode 120000 index 0000000000000..4ce23c9428e75 --- /dev/null +++ b/ee/definition/.eslintrc.js @@ -0,0 +1 @@ +../../client/.eslintrc.js \ No newline at end of file diff --git a/ee/definition/.prettierrc b/ee/definition/.prettierrc new file mode 120000 index 0000000000000..9d5a1f5613c49 --- /dev/null +++ b/ee/definition/.prettierrc @@ -0,0 +1 @@ +../../client/.prettierrc \ No newline at end of file diff --git a/ee/definition/rest/v1/engagementDashboard.ts b/ee/definition/rest/v1/engagementDashboard.ts index f20ab3307f6e6..c45dd1f603b40 100644 --- a/ee/definition/rest/v1/engagementDashboard.ts +++ b/ee/definition/rest/v1/engagementDashboard.ts @@ -1,6 +1,5 @@ -import { IDirectMessageRoom, IRoom } from '../../../../definition/IRoom'; -import { IDailyActiveUsers } from '../../../../definition/IUser'; -import { Serialized } from '../../../../definition/Serialized'; +import type { IDirectMessageRoom, IRoom } from '../../../../definition/IRoom'; +import type { IUser } from '../../../../definition/IUser'; export type EngagementDashboardEndpoints = { '/v1/engagement-dashboard/channels/list': { @@ -23,7 +22,7 @@ export type EngagementDashboardEndpoints = { total: number; }; }; - 'engagement-dashboard/messages/origin': { + '/v1/engagement-dashboard/messages/origin': { GET: (params: { start: Date; end: Date }) => { origins: { t: IRoom['t']; @@ -31,7 +30,7 @@ export type EngagementDashboardEndpoints = { }[]; }; }; - 'engagement-dashboard/messages/top-five-popular-channels': { + '/v1/engagement-dashboard/messages/top-five-popular-channels': { GET: (params: { start: Date; end: Date }) => { channels: { t: IRoom['t']; @@ -41,7 +40,7 @@ export type EngagementDashboardEndpoints = { }[]; }; }; - 'engagement-dashboard/messages/messages-sent': { + '/v1/engagement-dashboard/messages/messages-sent': { GET: (params: { start: Date; end: Date }) => { days: { day: Date; messages: number }[]; period: { @@ -54,9 +53,57 @@ export type EngagementDashboardEndpoints = { }; }; }; - 'engagement-dashboard/users/active-users': { - GET: (params: { start: string; end: string }) => { - month: Serialized[]; + '/v1/engagement-dashboard/users/active-users': { + GET: (params: { start: Date; end: Date }) => { + month: { + day: number; + month: number; + year: number; + usersList: IUser['_id'][]; + users: number; + }[]; + }; + }; + '/v1/engagement-dashboard/users/chat-busier/weekly-data': { + GET: (params: { start: Date }) => { + month: { + users: number; + day: number; + month: number; + year: number; + }[]; + }; + }; + '/v1/engagement-dashboard/users/chat-busier/hourly-data': { + GET: (params: { start: Date }) => { + hours: { + users: number; + hour: number; + }[]; + }; + }; + '/v1/engagement-dashboard/users/users-by-time-of-the-day-in-a-week': { + GET: (params: { start: Date; end: Date }) => { + week: { + users: number; + hour: number; + day: number; + month: number; + year: number; + }[]; + }; + }; + '/v1/engagement-dashboard/users/new-users': { + GET: (params: { start: Date; end: Date }) => { + days: { day: Date; users: number }[]; + period: { + count: number; + variation: number; + }; + yesterday: { + count: number; + variation: number; + }; }; }; }; diff --git a/ee/definition/rest/v1/omnichannel/businessHours.ts b/ee/definition/rest/v1/omnichannel/businessHours.ts index 1211ccdb9de05..fa42bedf87468 100644 --- a/ee/definition/rest/v1/omnichannel/businessHours.ts +++ b/ee/definition/rest/v1/omnichannel/businessHours.ts @@ -14,4 +14,4 @@ export type OmnichannelBusinessHoursEndpoints = { total: number; }; }; -} +}; diff --git a/ee/definition/rest/v1/omnichannel/businessUnits.ts b/ee/definition/rest/v1/omnichannel/businessUnits.ts index 9b18f13865cfe..d8bb5ceb84e81 100644 --- a/ee/definition/rest/v1/omnichannel/businessUnits.ts +++ b/ee/definition/rest/v1/omnichannel/businessUnits.ts @@ -1,30 +1,21 @@ -import { IOmnichannelBusinessUnit } from '../../../../../definition/IOmnichannelBusinessUnit'; import { ILivechatMonitor } from '../../../../../definition/ILivechatMonitor'; +import { IOmnichannelBusinessUnit } from '../../../../../definition/IOmnichannelBusinessUnit'; import { PaginatedResult } from '../../../../../definition/rest/helpers/PaginatedResult'; export type OmnichannelBusinessUnitsEndpoints = { 'livechat/units.list': { - GET: (params: { - text: string; - }) => (PaginatedResult & { + GET: (params: { text: string }) => PaginatedResult & { units: IOmnichannelBusinessUnit[]; - }); + }; }; 'livechat/units.getOne': { - GET: (params: { - unitId: string; - }) => (IOmnichannelBusinessUnit); + GET: (params: { unitId: string }) => IOmnichannelBusinessUnit; }; 'livechat/unitMonitors.list': { - GET: (params: { - unitId: string; - }) => ({ monitors: ILivechatMonitor[] }); + GET: (params: { unitId: string }) => { monitors: ILivechatMonitor[] }; }; 'livechat/units': { - GET: (params: { - text: string; - }) => (PaginatedResult & - { units: IOmnichannelBusinessUnit[] }); + GET: (params: { text: string }) => PaginatedResult & { units: IOmnichannelBusinessUnit[] }; POST: (params: { unitData: string; unitMonitors: string; @@ -40,4 +31,4 @@ export type OmnichannelBusinessUnitsEndpoints = { }) => IOmnichannelBusinessUnit; DELETE: () => number; }; -} +}; diff --git a/ee/definition/rest/v1/omnichannel/cannedResponses.ts b/ee/definition/rest/v1/omnichannel/cannedResponses.ts index 9098604c12d69..f536aeb5163d4 100644 --- a/ee/definition/rest/v1/omnichannel/cannedResponses.ts +++ b/ee/definition/rest/v1/omnichannel/cannedResponses.ts @@ -30,7 +30,6 @@ export type OmnichannelCannedResponsesEndpoints = { DELETE: (params: { _id: IOmnichannelCannedResponse['_id'] }) => void; }; 'canned-responses/:_id': { - path: `canned-responses/${ string }`; GET: () => { cannedResponse: IOmnichannelCannedResponse; }; diff --git a/ee/definition/rest/v1/omnichannel/index.ts b/ee/definition/rest/v1/omnichannel/index.ts index 000e315633392..29cc5999c08ab 100644 --- a/ee/definition/rest/v1/omnichannel/index.ts +++ b/ee/definition/rest/v1/omnichannel/index.ts @@ -2,4 +2,6 @@ import type { OmnichannelBusinessHoursEndpoints } from './businessHours'; import type { OmnichannelBusinessUnitsEndpoints } from './businessUnits'; import { OmnichannelCannedResponsesEndpoints } from './cannedResponses'; -export type OmnichannelEndpoints = OmnichannelBusinessHoursEndpoints & OmnichannelBusinessUnitsEndpoints & OmnichannelCannedResponsesEndpoints; +export type OmnichannelEndpoints = OmnichannelBusinessHoursEndpoints & + OmnichannelBusinessUnitsEndpoints & + OmnichannelCannedResponsesEndpoints; diff --git a/ee/server/api/engagementDashboard/channels.ts b/ee/server/api/engagementDashboard/channels.ts new file mode 100644 index 0000000000000..dee0360765175 --- /dev/null +++ b/ee/server/api/engagementDashboard/channels.ts @@ -0,0 +1,33 @@ +import { check, Match } from 'meteor/check'; + +import { API } from '../../../../app/api/server'; +import { findAllChannelsWithNumberOfMessages } from '../../lib/engagementDashboard/channels'; +import { isDateISOString, mapDateForAPI } from '../../lib/engagementDashboard/date'; + +API.v1.addRoute('engagement-dashboard/channels/list', { + authRequired: true, + permissionsRequired: ['view-engagement-dashboard'], +}, { + async get() { + check(this.queryParams, Match.ObjectIncluding({ + start: Match.Where(isDateISOString), + end: Match.Where(isDateISOString), + })); + + const { start, end } = this.queryParams; + const { offset, count } = this.getPaginationItems(); + + const { channels, total } = await findAllChannelsWithNumberOfMessages({ + start: mapDateForAPI(start), + end: mapDateForAPI(end), + options: { offset, count }, + }); + + return API.v1.success({ + channels, + total, + offset, + count: channels.length, + }); + }, +}); diff --git a/ee/app/engagement-dashboard/server/api/index.js b/ee/server/api/engagementDashboard/index.ts similarity index 100% rename from ee/app/engagement-dashboard/server/api/index.js rename to ee/server/api/engagementDashboard/index.ts diff --git a/ee/server/api/engagementDashboard/messages.ts b/ee/server/api/engagementDashboard/messages.ts new file mode 100644 index 0000000000000..23457f87d2b38 --- /dev/null +++ b/ee/server/api/engagementDashboard/messages.ts @@ -0,0 +1,56 @@ +import { check, Match } from 'meteor/check'; + +import { API } from '../../../../app/api/server'; +import { findWeeklyMessagesSentData, findMessagesSentOrigin, findTopFivePopularChannelsByMessageSentQuantity } from '../../lib/engagementDashboard/messages'; +import { isDateISOString, transformDatesForAPI } from '../../lib/engagementDashboard/date'; + +API.v1.addRoute('engagement-dashboard/messages/messages-sent', { + authRequired: true, + permissionsRequired: ['view-engagement-dashboard'], +}, { + async get() { + check(this.queryParams, Match.ObjectIncluding({ + start: Match.Where(isDateISOString), + end: Match.Where(isDateISOString), + })); + + const { start, end } = this.queryParams; + + const data = await findWeeklyMessagesSentData(transformDatesForAPI(start, end)); + return API.v1.success(data); + }, +}); + +API.v1.addRoute('engagement-dashboard/messages/origin', { + authRequired: true, + permissionsRequired: ['view-engagement-dashboard'], +}, { + async get() { + check(this.queryParams, Match.ObjectIncluding({ + start: Match.Where(isDateISOString), + end: Match.Where(isDateISOString), + })); + + const { start, end } = this.queryParams; + + const data = await findMessagesSentOrigin(transformDatesForAPI(start, end)); + return API.v1.success(data); + }, +}); + +API.v1.addRoute('engagement-dashboard/messages/top-five-popular-channels', { + authRequired: true, + permissionsRequired: ['view-engagement-dashboard'], +}, { + async get() { + check(this.queryParams, Match.ObjectIncluding({ + start: Match.Where(isDateISOString), + end: Match.Where(isDateISOString), + })); + + const { start, end } = this.queryParams; + + const data = await findTopFivePopularChannelsByMessageSentQuantity(transformDatesForAPI(start, end)); + return API.v1.success(data); + }, +}); diff --git a/ee/server/api/engagementDashboard/users.ts b/ee/server/api/engagementDashboard/users.ts new file mode 100644 index 0000000000000..bd1c12f22f027 --- /dev/null +++ b/ee/server/api/engagementDashboard/users.ts @@ -0,0 +1,94 @@ +import { check, Match } from 'meteor/check'; + +import { API } from '../../../../app/api/server'; +import { + findWeeklyUsersRegisteredData, + findActiveUsersMonthlyData, + findBusiestsChatsInADayByHours, + findBusiestsChatsWithinAWeek, + findUserSessionsByHourWithinAWeek, +} from '../../lib/engagementDashboard/users'; +import { isDateISOString, transformDatesForAPI } from '../../lib/engagementDashboard/date'; + +API.v1.addRoute('engagement-dashboard/users/new-users', { + authRequired: true, + permissionsRequired: ['view-engagement-dashboard'], +}, { + async get() { + check(this.queryParams, Match.ObjectIncluding({ + start: Match.Where(isDateISOString), + end: Match.Where(isDateISOString), + })); + + const { start, end } = this.queryParams; + + const data = await findWeeklyUsersRegisteredData(transformDatesForAPI(start, end)); + return API.v1.success(data); + }, +}); + +API.v1.addRoute('engagement-dashboard/users/active-users', { + authRequired: true, + permissionsRequired: ['view-engagement-dashboard'], +}, { + async get() { + check(this.queryParams, Match.ObjectIncluding({ + start: Match.Where(isDateISOString), + end: Match.Where(isDateISOString), + })); + + const { start, end } = this.queryParams; + + const data = await findActiveUsersMonthlyData(transformDatesForAPI(start, end)); + return API.v1.success(data); + }, +}); + +API.v1.addRoute('engagement-dashboard/users/chat-busier/hourly-data', { + authRequired: true, + permissionsRequired: ['view-engagement-dashboard'], +}, { + async get() { + check(this.queryParams, Match.ObjectIncluding({ + start: Match.Where(isDateISOString), + })); + + const { start } = this.queryParams; + + const data = await findBusiestsChatsInADayByHours(transformDatesForAPI(start)); + return API.v1.success(data); + }, +}); + +API.v1.addRoute('engagement-dashboard/users/chat-busier/weekly-data', { + authRequired: true, + permissionsRequired: ['view-engagement-dashboard'], +}, { + async get() { + check(this.queryParams, Match.ObjectIncluding({ + start: Match.Where(isDateISOString), + })); + + const { start } = this.queryParams; + + const data = await findBusiestsChatsWithinAWeek(transformDatesForAPI(start)); + return API.v1.success(data); + }, +}); + +API.v1.addRoute('engagement-dashboard/users/users-by-time-of-the-day-in-a-week', { + authRequired: true, + permissionsRequired: ['view-engagement-dashboard'], +}, { + async get() { + check(this.queryParams, Match.ObjectIncluding({ + start: Match.Where(isDateISOString), + end: Match.Where(isDateISOString), + })); + + const { start, end } = this.queryParams; + + const data = await findUserSessionsByHourWithinAWeek(transformDatesForAPI(start, end)); + return API.v1.success(data); + }, +}); diff --git a/ee/server/index.ts b/ee/server/index.ts index b25b60748d88a..963e58fe6d97c 100644 --- a/ee/server/index.ts +++ b/ee/server/index.ts @@ -7,7 +7,6 @@ import '../app/api-enterprise/server/index'; import '../app/auditing/server/index'; import '../app/authorization/server/index'; import '../app/canned-responses/server/index'; -import '../app/engagement-dashboard/server/index'; import '../app/livechat-enterprise/server/index'; import '../app/settings/server/index'; import '../app/teams-mention/server/index'; diff --git a/ee/server/lib/engagementDashboard/channels.ts b/ee/server/lib/engagementDashboard/channels.ts new file mode 100644 index 0000000000000..d723bceb4b34b --- /dev/null +++ b/ee/server/lib/engagementDashboard/channels.ts @@ -0,0 +1,54 @@ +import moment from 'moment'; + +import { Rooms } from '../../../../app/models/server/raw'; +import { convertDateToInt, diffBetweenDaysInclusive } from './date'; +import { IDirectMessageRoom, IRoom } from '../../../../definition/IRoom'; + +export const findAllChannelsWithNumberOfMessages = async ({ start, end, options = {} }: { + start: Date; + end: Date; + options: { + offset?: number; + count?: number; + }; +}): Promise<{ + channels: { + room: { + _id: IRoom['_id']; + name: IRoom['name'] | IRoom['fname']; + ts: IRoom['ts']; + t: IRoom['t']; + _updatedAt: IRoom['_updatedAt']; + usernames?: IDirectMessageRoom['usernames']; + }; + messages: number; + lastWeekMessages: number; + diffFromLastWeek: number; + }[]; + total: number; +}> => { + const daysBetweenDates = diffBetweenDaysInclusive(end, start); + const endOfLastWeek = moment(start).subtract(1, 'days').toDate(); + const startOfLastWeek = moment(endOfLastWeek).subtract(daysBetweenDates, 'days').toDate(); + + const channels = await Rooms.findChannelsWithNumberOfMessagesBetweenDate({ + start: convertDateToInt(start), + end: convertDateToInt(end), + startOfLastWeek: convertDateToInt(startOfLastWeek), + endOfLastWeek: convertDateToInt(endOfLastWeek), + options, + }).toArray(); + + const total = (await Rooms.findChannelsWithNumberOfMessagesBetweenDate({ + start: convertDateToInt(start), + end: convertDateToInt(end), + startOfLastWeek: convertDateToInt(startOfLastWeek), + endOfLastWeek: convertDateToInt(endOfLastWeek), + onlyCount: true, + }).toArray())[0]?.total ?? 0; + + return { + channels, + total, + }; +}; diff --git a/ee/server/lib/engagementDashboard/date.ts b/ee/server/lib/engagementDashboard/date.ts new file mode 100644 index 0000000000000..9af9911ec0b7a --- /dev/null +++ b/ee/server/lib/engagementDashboard/date.ts @@ -0,0 +1,34 @@ +import mem from 'mem'; +import moment from 'moment'; + +export const isDateISOString = mem((input: string): input is string => { + const timestamp = Date.parse(input); + return !Number.isNaN(timestamp) && new Date(timestamp).toISOString() === input; +}, { maxAge: 10000 }); + +export const mapDateForAPI = (input: string): Date => { + if (!isDateISOString(input)) { + throw new Error('invalid ISO 8601 date'); + } + + return new Date(Date.parse(input)); +}; + +export const convertDateToInt = (date: Date): number => parseInt(moment(date).clone().format('YYYYMMDD'), 10); +export const convertIntToDate = (intValue: number): Date => moment(intValue, 'YYYYMMDD').clone().toDate(); +export const diffBetweenDays = (start: string | number | Date, end: string | number | Date): number => moment(new Date(start)).clone().diff(new Date(end), 'days'); +export const diffBetweenDaysInclusive = (start: string | number | Date, end: string | number | Date): number => diffBetweenDays(start, end) + 1; + +export const getTotalOfWeekItems = >(weekItems: T[], property: keyof T): number => weekItems.reduce((acc, item) => { + acc += item[property]; + return acc; +}, 0); + +export function transformDatesForAPI(start: string): { start: Date; end: undefined }; +export function transformDatesForAPI(start: string, end: string): { start: Date; end: Date }; +export function transformDatesForAPI(start: string, end?: string): { start: Date; end: Date | undefined } { + return { + start: mapDateForAPI(start), + end: end ? mapDateForAPI(end) : undefined, + }; +} diff --git a/ee/app/engagement-dashboard/server/lib/messages.js b/ee/server/lib/engagementDashboard/messages.ts similarity index 68% rename from ee/app/engagement-dashboard/server/lib/messages.js rename to ee/server/lib/engagementDashboard/messages.ts index f4208928fcbb2..645011a83ae80 100644 --- a/ee/app/engagement-dashboard/server/lib/messages.js +++ b/ee/server/lib/engagementDashboard/messages.ts @@ -1,10 +1,12 @@ import moment from 'moment'; -import { roomTypes } from '../../../../../app/utils'; -import { Messages, Analytics } from '../../../../../app/models/server/raw'; +import { roomTypes } from '../../../../app/utils/server'; +import { Messages, Analytics } from '../../../../app/models/server/raw'; import { convertDateToInt, diffBetweenDaysInclusive, convertIntToDate, getTotalOfWeekItems } from './date'; +import { IDirectMessageRoom, IRoom } from '../../../../definition/IRoom'; +import { IMessage } from '../../../../definition/IMessage'; -export const handleMessagesSent = (message, room) => { +export const handleMessagesSent = (message: IMessage, room: IRoom): IMessage => { const roomTypesToShow = roomTypes.getTypesToShowOnDashboard(); if (!roomTypesToShow.includes(room.t)) { return message; @@ -16,10 +18,10 @@ export const handleMessagesSent = (message, room) => { return message; }; -export const handleMessagesDeleted = (message, room) => { +export const handleMessagesDeleted = (message: IMessage, room: IRoom): IMessage => { const roomTypesToShow = roomTypes.getTypesToShowOnDashboard(); if (!roomTypesToShow.includes(room.t)) { - return; + return message; } Promise.await(Analytics.saveMessageDeleted({ date: convertDateToInt(message.ts), @@ -28,13 +30,13 @@ export const handleMessagesDeleted = (message, room) => { return message; }; -export const fillFirstDaysOfMessagesIfNeeded = async (date) => { +export const fillFirstDaysOfMessagesIfNeeded = async (date: Date): Promise => { const messagesFromAnalytics = await Analytics.findByTypeBeforeDate({ type: 'messages', date: convertDateToInt(date), }).toArray(); if (!messagesFromAnalytics.length) { - const startOfPeriod = moment(convertIntToDate(date)).subtract(90, 'days').toDate(); + const startOfPeriod = moment(date).subtract(90, 'days').toDate(); const messages = await Messages.getTotalOfMessagesSentByDate({ start: startOfPeriod, end: date, @@ -46,7 +48,17 @@ export const fillFirstDaysOfMessagesIfNeeded = async (date) => { } }; -export const findWeeklyMessagesSentData = async ({ start, end }) => { +export const findWeeklyMessagesSentData = async ({ start, end }: { start: Date; end: Date }): Promise<{ + days: { day: Date; messages: number }[]; + period: { + count: number; + variation: number; + }; + yesterday: { + count: number; + variation: number; + }; +}> => { const daysBetweenDates = diffBetweenDaysInclusive(end, start); const endOfLastWeek = moment(start).clone().subtract(1, 'days').toDate(); const startOfLastWeek = moment(endOfLastWeek).clone().subtract(daysBetweenDates, 'days').toDate(); @@ -79,21 +91,34 @@ export const findWeeklyMessagesSentData = async ({ start, end }) => { }; }; -export const findMessagesSentOrigin = async ({ start, end }) => { +export const findMessagesSentOrigin = async ({ start, end }: { start: Date; end: Date }): Promise<{ + origins: { + t: IRoom['t']; + messages: number; + }[]; +}> => { const origins = await Analytics.getMessagesOrigin({ start: convertDateToInt(start), end: convertDateToInt(end), }).toArray(); - const roomTypesToShow = roomTypes.getTypesToShowOnDashboard(); + const roomTypesToShow: IRoom['t'][] = roomTypes.getTypesToShowOnDashboard() as IRoom['t'][]; const responseTypes = origins.map((origin) => origin.t); - const missingTypes = roomTypesToShow.filter((type) => !responseTypes.includes(type)); + const missingTypes = roomTypesToShow.filter((type): type is IRoom['t'] => !responseTypes.includes(type)); if (missingTypes.length) { missingTypes.forEach((type) => origins.push({ messages: 0, t: type })); } + return { origins }; }; -export const findTopFivePopularChannelsByMessageSentQuantity = async ({ start, end }) => { +export const findTopFivePopularChannelsByMessageSentQuantity = async ({ start, end }: { start: Date; end: Date }): Promise<{ + channels: { + t: IRoom['t']; + messages: number; + name: IRoom['name'] | IRoom['fname']; + usernames?: IDirectMessageRoom['usernames']; + }[]; +}> => { const channels = await Analytics.getMostPopularChannelsByMessagesSentQuantity({ start: convertDateToInt(start), end: convertDateToInt(end), diff --git a/ee/server/lib/engagementDashboard/startup.ts b/ee/server/lib/engagementDashboard/startup.ts new file mode 100644 index 0000000000000..8aa81c16c4ac6 --- /dev/null +++ b/ee/server/lib/engagementDashboard/startup.ts @@ -0,0 +1,28 @@ +import { fillFirstDaysOfMessagesIfNeeded, handleMessagesDeleted, handleMessagesSent } from './messages'; +import { fillFirstDaysOfUsersIfNeeded, handleUserCreated } from './users'; +import { callbacks } from '../../../../app/callbacks/lib/callbacks'; +import { Permissions } from '../../../../app/models/server/raw'; + +export const attachCallbacks = (): void => { + callbacks.add('afterSaveMessage', handleMessagesSent, callbacks.priority.MEDIUM, 'engagementDashboard.afterSaveMessage'); + callbacks.add('afterDeleteMessage', handleMessagesDeleted, callbacks.priority.MEDIUM, 'engagementDashboard.afterDeleteMessage'); + callbacks.add('afterCreateUser', handleUserCreated, callbacks.priority.MEDIUM, 'engagementDashboard.afterCreateUser'); +}; + +export const detachCallbacks = (): void => { + callbacks.remove('afterSaveMessage', 'engagementDashboard.afterSaveMessage'); + callbacks.remove('afterDeleteMessage', 'engagementDashboard.afterDeleteMessage'); + callbacks.remove('afterCreateUser', 'engagementDashboard.afterCreateUser'); +}; + +export const prepareAnalytics = async (): Promise => { + const now = new Date(); + await Promise.all([ + fillFirstDaysOfUsersIfNeeded(now), + fillFirstDaysOfMessagesIfNeeded(now), + ]); +}; + +export const prepareAuthorization = async (): Promise => { + Permissions.create('view-engagement-dashboard', ['admin']); +}; diff --git a/ee/app/engagement-dashboard/server/lib/users.js b/ee/server/lib/engagementDashboard/users.ts similarity index 52% rename from ee/app/engagement-dashboard/server/lib/users.js rename to ee/server/lib/engagementDashboard/users.ts index b9d8738827c17..f5bde290d01bc 100644 --- a/ee/app/engagement-dashboard/server/lib/users.js +++ b/ee/server/lib/engagementDashboard/users.ts @@ -1,21 +1,22 @@ import moment from 'moment'; -import { Users, Analytics, Sessions } from '../../../../../app/models/server/raw'; +import { Users, Analytics, Sessions } from '../../../../app/models/server/raw'; import { convertDateToInt, diffBetweenDaysInclusive, getTotalOfWeekItems, convertIntToDate } from './date'; +import { IUser } from '../../../../definition/IUser'; -export const handleUserCreated = (user) => { +export const handleUserCreated = (user: IUser): IUser => { if (user.roles?.includes('anonymous')) { - return; + return user; } Promise.await(Analytics.saveUserData({ - date: convertDateToInt(user.ts), - user, + date: convertDateToInt(user.createdAt), })); + return user; }; -export const fillFirstDaysOfUsersIfNeeded = async (date) => { +export const fillFirstDaysOfUsersIfNeeded = async (date: Date): Promise => { const usersFromAnalytics = await Analytics.findByTypeBeforeDate({ type: 'users', date: convertDateToInt(date), @@ -33,7 +34,17 @@ export const fillFirstDaysOfUsersIfNeeded = async (date) => { } }; -export const findWeeklyUsersRegisteredData = async ({ start, end }) => { +export const findWeeklyUsersRegisteredData = async ({ start, end }: { start: Date; end: Date }): Promise<{ + days: { day: Date; users: number }[]; + period: { + count: number; + variation: number; + }; + yesterday: { + count: number; + variation: number; + }; +}> => { const daysBetweenDates = diffBetweenDaysInclusive(end, start); const endOfLastWeek = moment(start).clone().subtract(1, 'days').toDate(); const startOfLastWeek = moment(endOfLastWeek).clone().subtract(daysBetweenDates, 'days').toDate(); @@ -66,66 +77,73 @@ export const findWeeklyUsersRegisteredData = async ({ start, end }) => { }; }; -export const findActiveUsersMonthlyData = async ({ start, end }) => { - const startOfPeriod = moment(start); - const endOfPeriod = moment(end); +const createDestructuredDate = (input: moment.MomentInput): { + year: number; + month: number; + day: number; +} => { + const date = moment(input); return { - month: await Sessions.getActiveUsersOfPeriodByDayBetweenDates({ - start: { - year: startOfPeriod.year(), - month: startOfPeriod.month() + 1, - day: startOfPeriod.date(), - }, - end: { - year: endOfPeriod.year(), - month: endOfPeriod.month() + 1, - day: endOfPeriod.date(), - }, - }), + year: date.year(), + month: date.month() + 1, + day: date.date(), }; }; -export const findBusiestsChatsInADayByHours = async ({ start }) => { - const now = moment(start); - const yesterday = moment(now).clone().subtract(24, 'hours'); - return { - hours: await Sessions.getBusiestTimeWithinHoursPeriod({ - start: yesterday.toDate(), - end: now.toDate(), - groupSize: 2, - }), - }; -}; +export const findActiveUsersMonthlyData = async ({ start, end }: { start: Date; end: Date }): Promise<{ + month: { + day: number; + month: number; + year: number; + usersList: IUser['_id'][]; + users: number; + }[]; +}> => ({ + month: await Sessions.getActiveUsersOfPeriodByDayBetweenDates({ + start: createDestructuredDate(start), + end: createDestructuredDate(end), + }), +}); -export const findBusiestsChatsWithinAWeek = async ({ start }) => { - const today = moment(start); - const startOfCurrentWeek = moment(today).clone().subtract(7, 'days'); +export const findBusiestsChatsInADayByHours = async ({ start }: { start: Date }): Promise<{ + hours: { + hour: number; + users: number; + }[]; +}> => ({ + hours: await Sessions.getBusiestTimeWithinHoursPeriod({ + start: createDestructuredDate(moment(start).subtract(24, 'hours')), + end: createDestructuredDate(start), + groupSize: 2, + }), +}); - return { - month: await Sessions.getTotalOfSessionsByDayBetweenDates({ - start: { - year: startOfCurrentWeek.year(), - month: startOfCurrentWeek.month() + 1, - day: startOfCurrentWeek.date(), - }, - end: { - year: today.year(), - month: today.month() + 1, - day: today.date(), - }, - }), - }; -}; - -export const findUserSessionsByHourWithinAWeek = async ({ start, end }) => { - const startOfPeriod = moment(start); - const endOfPeriod = moment(end); +export const findBusiestsChatsWithinAWeek = async ({ start }: { start: Date }): Promise<{ + month: { + day: number; + month: number; + year: number; + users: number; + }[]; +}> => ({ + month: await Sessions.getTotalOfSessionsByDayBetweenDates({ + start: createDestructuredDate(moment(start).subtract(7, 'days')), + end: createDestructuredDate(start), + }), +}); - return { - week: await Sessions.getTotalOfSessionByHourAndDayBetweenDates({ - start: startOfPeriod.toDate(), - end: endOfPeriod.toDate(), - }), - }; -}; +export const findUserSessionsByHourWithinAWeek = async ({ start, end }: { start: Date; end: Date }): Promise<{ + week: { + hour: number; + day: number; + month: number; + year: number; + users: number; + }[]; +}> => ({ + week: await Sessions.getTotalOfSessionByHourAndDayBetweenDates({ + start: createDestructuredDate(start), + end: createDestructuredDate(end), + }), +}); diff --git a/ee/server/startup/engagementDashboard.ts b/ee/server/startup/engagementDashboard.ts new file mode 100644 index 0000000000000..13822f136e8d2 --- /dev/null +++ b/ee/server/startup/engagementDashboard.ts @@ -0,0 +1,21 @@ +import { Meteor } from 'meteor/meteor'; + +import { onToggledFeature } from '../../app/license/server/license'; + +onToggledFeature('engagement-dashboard', { + up: () => Meteor.startup(async () => { + const { + prepareAnalytics, + prepareAuthorization, + attachCallbacks, + } = await import('../lib/engagementDashboard/startup'); + await prepareAuthorization(); + await prepareAnalytics(); + attachCallbacks(); + await import('../api/engagementDashboard'); + }), + down: () => Meteor.startup(async () => { + const { detachCallbacks } = await import('../lib/engagementDashboard/startup'); + detachCallbacks(); + }), +}); diff --git a/ee/server/startup/index.ts b/ee/server/startup/index.ts index 2a58ffbd4b4ba..91cfb4839c7c5 100644 --- a/ee/server/startup/index.ts +++ b/ee/server/startup/index.ts @@ -1 +1,2 @@ +import './engagementDashboard'; import './seatsCap'; diff --git a/package-lock.json b/package-lock.json index d76d900ad1760..32f4df75ff890 100644 --- a/package-lock.json +++ b/package-lock.json @@ -222,12 +222,12 @@ } }, "@babel/generator": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.4.tgz", - "integrity": "sha512-d3itta0tu+UayjEORPNz6e1T3FtvWlP5N4V5M+lhp/CxT4oAA7/NcScnpRyspUMLK6tu9MNHmQHxRykuN2R7hw==", + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.8.tgz", + "integrity": "sha512-ECmAKstXbp1cvpTTZciZCgfOt6iN64lR0d+euv3UZisU5awfRawOvg07Utn/qBGuH4bRIEZKrA/4LzZyXhZr8g==", "dev": true, "requires": { - "@babel/types": "^7.15.4", + "@babel/types": "^7.15.6", "jsesc": "^2.5.1", "source-map": "^0.5.0" } @@ -1748,9 +1748,9 @@ } }, "@babel/parser": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.7.tgz", - "integrity": "sha512-rycZXvQ+xS9QyIcJ9HXeDWf1uxqlbVFAUq0Rq0dbc50Zb/+wUe/ehyfzGfm9KZZF0kBejYgxltBXocP+gKdL2g==", + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.8.tgz", + "integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==", "dev": true }, "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { @@ -1851,9 +1851,9 @@ } }, "@babel/plugin-proposal-decorators": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.15.4.tgz", - "integrity": "sha512-WNER+YLs7avvRukEddhu5PSfSaMMimX2xBFgLQS7Bw16yrUxJGWidO9nQp+yLy9MVybg5Ba3BlhAw+BkdhpDmg==", + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.15.8.tgz", + "integrity": "sha512-5n8+xGK7YDrXF+WAORg3P7LlCCdiaAyKLZi22eP2BwTy4kJ0kFUMMDCj4nQ8YrKyNZgjhU/9eRVqONnjB3us8g==", "dev": true, "requires": { "@babel/helper-create-class-features-plugin": "^7.15.4", @@ -2913,9 +2913,9 @@ } }, "@babel/plugin-transform-typescript": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.15.4.tgz", - "integrity": "sha512-sM1/FEjwYjXvMwu1PJStH11kJ154zd/lpY56NQJ5qH2D0mabMv1CAy/kdvS9RP4Xgfj9fBBA3JiSLdDHgXdzOA==", + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.15.8.tgz", + "integrity": "sha512-ZXIkJpbaf6/EsmjeTbiJN/yMxWPFWvlr7sEG1P95Xb4S4IBcrf2n7s/fItIhsAmOf8oSh3VJPDppO6ExfAfKRQ==", "dev": true, "requires": { "@babel/helper-create-class-features-plugin": "^7.15.4", @@ -3261,9 +3261,9 @@ }, "dependencies": { "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", + "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", "dev": true, "requires": { "@babel/highlight": "^7.14.5" @@ -3329,9 +3329,9 @@ }, "dependencies": { "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", + "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", "dev": true, "requires": { "@babel/highlight": "^7.14.5" @@ -3422,9 +3422,9 @@ } }, "@base2/pretty-print-object": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@base2/pretty-print-object/-/pretty-print-object-1.0.0.tgz", - "integrity": "sha512-4Th98KlMHr5+JkxfcoDT//6vY8vM+iSPrLNpHhRyLx2CFYi8e2RfqPLdpbnpo0Q5lQC5hNB79yes07zb02fvCw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@base2/pretty-print-object/-/pretty-print-object-1.0.1.tgz", + "integrity": "sha512-4iri8i1AqYHJE2DstZYkyEprg6Pq6sKx3xn5FpySk9sNhH7qN2LLlHJCfDTZRILNwQNPD7mATWM0TBui7uC1pA==", "dev": true }, "@bcoe/v8-coverage": { @@ -4432,9 +4432,9 @@ }, "dependencies": { "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", + "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", "dev": true, "requires": { "@babel/highlight": "^7.14.5" @@ -5692,17 +5692,17 @@ } }, "@storybook/addon-actions": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-6.3.9.tgz", - "integrity": "sha512-r02sSxkd2csKb69PVi8541e6hYGJ+FXOEBUnvLPyf5IULs7tRWPJekNd6cHxP1hoUBnmmj2f8CXmmbsXzr6rrQ==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-6.3.12.tgz", + "integrity": "sha512-mzuN4Ano4eyicwycM2PueGzzUCAEzt9/6vyptWEIVJu0sjK0J9KtBRlqFi1xGQxmCfimDR/n/vWBBkc7fp2uJA==", "dev": true, "requires": { - "@storybook/addons": "6.3.9", - "@storybook/api": "6.3.9", - "@storybook/client-api": "6.3.9", - "@storybook/components": "6.3.9", - "@storybook/core-events": "6.3.9", - "@storybook/theming": "6.3.9", + "@storybook/addons": "6.3.12", + "@storybook/api": "6.3.12", + "@storybook/client-api": "6.3.12", + "@storybook/components": "6.3.12", + "@storybook/core-events": "6.3.12", + "@storybook/theming": "6.3.12", "core-js": "^3.8.2", "fast-deep-equal": "^3.1.3", "global": "^4.4.0", @@ -5731,17 +5731,17 @@ } }, "@storybook/addon-backgrounds": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-6.3.9.tgz", - "integrity": "sha512-gqKLRZy/CdeZHcZz6ktz8SjzbV9WYOkhcgHb2kncLOp3OQ6St2VWHmBVitVpiN8V/ClL/lvLKPGKy7szKUwQRA==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-6.3.12.tgz", + "integrity": "sha512-51cHBx0HV7K/oRofJ/1pE05qti6sciIo8m4iPred1OezXIrJ/ckzP+gApdaUdzgcLAr6/MXQWLk0sJuImClQ6w==", "dev": true, "requires": { - "@storybook/addons": "6.3.9", - "@storybook/api": "6.3.9", - "@storybook/client-logger": "6.3.9", - "@storybook/components": "6.3.9", - "@storybook/core-events": "6.3.9", - "@storybook/theming": "6.3.9", + "@storybook/addons": "6.3.12", + "@storybook/api": "6.3.12", + "@storybook/client-logger": "6.3.12", + "@storybook/components": "6.3.12", + "@storybook/core-events": "6.3.12", + "@storybook/theming": "6.3.12", "core-js": "^3.8.2", "global": "^4.4.0", "memoizerific": "^1.11.3", @@ -5759,25 +5759,25 @@ } }, "@storybook/addon-controls": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-6.3.9.tgz", - "integrity": "sha512-B++fteuGzv1DUD3hKW3wTXtN4CBWpv4HzpLqng6L/xLfTZeOd39F73ooi/NmsoVO4vUl4ywrq0/QiHiXnp1kIQ==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-6.3.12.tgz", + "integrity": "sha512-WO/PbygE4sDg3BbstJ49q0uM3Xu5Nw4lnHR5N4hXSvRAulZt1d1nhphRTHjfX+CW+uBcfzkq9bksm6nKuwmOyw==", "dev": true, "requires": { - "@storybook/addons": "6.3.9", - "@storybook/api": "6.3.9", - "@storybook/client-api": "6.3.9", - "@storybook/components": "6.3.9", - "@storybook/node-logger": "6.3.9", - "@storybook/theming": "6.3.9", + "@storybook/addons": "6.3.12", + "@storybook/api": "6.3.12", + "@storybook/client-api": "6.3.12", + "@storybook/components": "6.3.12", + "@storybook/node-logger": "6.3.12", + "@storybook/theming": "6.3.12", "core-js": "^3.8.2", "ts-dedent": "^2.0.0" }, "dependencies": { "@storybook/node-logger": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-6.3.9.tgz", - "integrity": "sha512-tAo7sLNDGkL20NNGjwFgsywrl5rf/ImJaD6DnhSJDncaMcDy5xOA5Fn1IbkQuWUoKexE+nCkTiiynoP4Nzcp4w==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-6.3.12.tgz", + "integrity": "sha512-iktOem/Ls2+dsZY9PhPeC6T1QhX/y7OInP88neLsqEPEbB2UXca3Ydv7OZBhBVbvN25W45b05MRzbtNUxYLNRw==", "dev": true, "requires": { "@types/npmlog": "^4.1.2", @@ -5833,9 +5833,9 @@ } }, "@storybook/addon-docs": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-6.3.9.tgz", - "integrity": "sha512-UgujxJei5xejFpAfW40TdoDJnnfeKUPrp6QWMjMRcMG3+uW69VWviw4p3qzS9phO5vQJsINpRT8Bu0aGz2TpJw==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-6.3.12.tgz", + "integrity": "sha512-iUrqJBMTOn2PgN8AWNQkfxfIPkh8pEg27t8UndMgfOpeGK/VWGw2UEifnA82flvntcilT4McxmVbRHkeBY9K5A==", "dev": true, "requires": { "@babel/core": "^7.12.10", @@ -5847,20 +5847,20 @@ "@mdx-js/loader": "^1.6.22", "@mdx-js/mdx": "^1.6.22", "@mdx-js/react": "^1.6.22", - "@storybook/addons": "6.3.9", - "@storybook/api": "6.3.9", - "@storybook/builder-webpack4": "6.3.9", - "@storybook/client-api": "6.3.9", - "@storybook/client-logger": "6.3.9", - "@storybook/components": "6.3.9", - "@storybook/core": "6.3.9", - "@storybook/core-events": "6.3.9", + "@storybook/addons": "6.3.12", + "@storybook/api": "6.3.12", + "@storybook/builder-webpack4": "6.3.12", + "@storybook/client-api": "6.3.12", + "@storybook/client-logger": "6.3.12", + "@storybook/components": "6.3.12", + "@storybook/core": "6.3.12", + "@storybook/core-events": "6.3.12", "@storybook/csf": "0.0.1", - "@storybook/csf-tools": "6.3.9", - "@storybook/node-logger": "6.3.9", - "@storybook/postinstall": "6.3.9", - "@storybook/source-loader": "6.3.9", - "@storybook/theming": "6.3.9", + "@storybook/csf-tools": "6.3.12", + "@storybook/node-logger": "6.3.12", + "@storybook/postinstall": "6.3.12", + "@storybook/source-loader": "6.3.12", + "@storybook/theming": "6.3.12", "acorn": "^7.4.1", "acorn-jsx": "^5.3.1", "acorn-walk": "^7.2.0", @@ -5885,9 +5885,9 @@ }, "dependencies": { "@storybook/node-logger": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-6.3.9.tgz", - "integrity": "sha512-tAo7sLNDGkL20NNGjwFgsywrl5rf/ImJaD6DnhSJDncaMcDy5xOA5Fn1IbkQuWUoKexE+nCkTiiynoP4Nzcp4w==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-6.3.12.tgz", + "integrity": "sha512-iktOem/Ls2+dsZY9PhPeC6T1QhX/y7OInP88neLsqEPEbB2UXca3Ydv7OZBhBVbvN25W45b05MRzbtNUxYLNRw==", "dev": true, "requires": { "@types/npmlog": "^4.1.2", @@ -6017,21 +6017,21 @@ } }, "@storybook/addon-essentials": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-6.3.9.tgz", - "integrity": "sha512-F5FP9eqSSX++Dh7n/jAxVbfKGSh58EDIj7YdDVlCvf0qc9wLscV3dMS0kEcx/pP7z293JA4ADnztITOVSzf+qQ==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-6.3.12.tgz", + "integrity": "sha512-PK0pPE0xkq00kcbBcFwu/5JGHQTu4GvLIHfwwlEGx6GWNQ05l6Q+1Z4nE7xJGv2PSseSx3CKcjn8qykNLe6O6g==", "dev": true, "requires": { - "@storybook/addon-actions": "6.3.9", - "@storybook/addon-backgrounds": "6.3.9", - "@storybook/addon-controls": "6.3.9", - "@storybook/addon-docs": "6.3.9", + "@storybook/addon-actions": "6.3.12", + "@storybook/addon-backgrounds": "6.3.12", + "@storybook/addon-controls": "6.3.12", + "@storybook/addon-docs": "6.3.12", "@storybook/addon-measure": "^2.0.0", - "@storybook/addon-toolbars": "6.3.9", - "@storybook/addon-viewport": "6.3.9", - "@storybook/addons": "6.3.9", - "@storybook/api": "6.3.9", - "@storybook/node-logger": "6.3.9", + "@storybook/addon-toolbars": "6.3.12", + "@storybook/addon-viewport": "6.3.12", + "@storybook/addons": "6.3.12", + "@storybook/api": "6.3.12", + "@storybook/node-logger": "6.3.12", "core-js": "^3.8.2", "regenerator-runtime": "^0.13.7", "storybook-addon-outline": "^1.4.1", @@ -6039,9 +6039,9 @@ }, "dependencies": { "@storybook/node-logger": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-6.3.9.tgz", - "integrity": "sha512-tAo7sLNDGkL20NNGjwFgsywrl5rf/ImJaD6DnhSJDncaMcDy5xOA5Fn1IbkQuWUoKexE+nCkTiiynoP4Nzcp4w==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-6.3.12.tgz", + "integrity": "sha512-iktOem/Ls2+dsZY9PhPeC6T1QhX/y7OInP88neLsqEPEbB2UXca3Ydv7OZBhBVbvN25W45b05MRzbtNUxYLNRw==", "dev": true, "requires": { "@types/npmlog": "^4.1.2", @@ -6181,16 +6181,16 @@ } }, "@storybook/addon-toolbars": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-6.3.9.tgz", - "integrity": "sha512-9LgjmHtmOwv5++nKEruVKvHfqfqz23uHr7aht9b3tLf/6oNuRoCakddyIunoxhxw6SIW8rUObgjt8bnGxBSu1g==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-6.3.12.tgz", + "integrity": "sha512-8GvP6zmAfLPRnYRARSaIwLkQClLIRbflRh4HZoFk6IMjQLXZb4NL3JS5OLFKG+HRMMU2UQzfoSDqjI7k7ptyRw==", "dev": true, "requires": { - "@storybook/addons": "6.3.9", - "@storybook/api": "6.3.9", - "@storybook/client-api": "6.3.9", - "@storybook/components": "6.3.9", - "@storybook/theming": "6.3.9", + "@storybook/addons": "6.3.12", + "@storybook/api": "6.3.12", + "@storybook/client-api": "6.3.12", + "@storybook/components": "6.3.12", + "@storybook/theming": "6.3.12", "core-js": "^3.8.2", "regenerator-runtime": "^0.13.7" }, @@ -6204,17 +6204,17 @@ } }, "@storybook/addon-viewport": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-6.3.9.tgz", - "integrity": "sha512-zQe6i4LU1elSB/+ey7UfSTU64HEjQL8Lx+eYZx0xe80An+lMEhcDF4EGSTCDXFUwIX2LqEf3lc/vAfhA881Mrw==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-6.3.12.tgz", + "integrity": "sha512-TRjyfm85xouOPmXxeLdEIzXLfJZZ1ePQ7p/5yphDGBHdxMU4m4qiZr8wYpUaxHsRu/UB3dKfaOyGT+ivogbnbw==", "dev": true, "requires": { - "@storybook/addons": "6.3.9", - "@storybook/api": "6.3.9", - "@storybook/client-logger": "6.3.9", - "@storybook/components": "6.3.9", - "@storybook/core-events": "6.3.9", - "@storybook/theming": "6.3.9", + "@storybook/addons": "6.3.12", + "@storybook/api": "6.3.12", + "@storybook/client-logger": "6.3.12", + "@storybook/components": "6.3.12", + "@storybook/core-events": "6.3.12", + "@storybook/theming": "6.3.12", "core-js": "^3.8.2", "global": "^4.4.0", "memoizerific": "^1.11.3", @@ -6231,17 +6231,17 @@ } }, "@storybook/addons": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-6.3.9.tgz", - "integrity": "sha512-5tRkeHgdb/I/rp3GBkxonDLVsA45Vpgh/vFrsecrS/98wkSYfPEhqrDGLOosJHFrN3J2pznAuNFaA05158uBsw==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-6.3.12.tgz", + "integrity": "sha512-UgoMyr7Qr0FS3ezt8u6hMEcHgyynQS9ucr5mAwZky3wpXRPFyUTmMto9r4BBUdqyUvTUj/LRKIcmLBfj+/l0Fg==", "dev": true, "requires": { - "@storybook/api": "6.3.9", - "@storybook/channels": "6.3.9", - "@storybook/client-logger": "6.3.9", - "@storybook/core-events": "6.3.9", - "@storybook/router": "6.3.9", - "@storybook/theming": "6.3.9", + "@storybook/api": "6.3.12", + "@storybook/channels": "6.3.12", + "@storybook/client-logger": "6.3.12", + "@storybook/core-events": "6.3.12", + "@storybook/router": "6.3.12", + "@storybook/theming": "6.3.12", "core-js": "^3.8.2", "global": "^4.4.0", "regenerator-runtime": "^0.13.7" @@ -6256,19 +6256,19 @@ } }, "@storybook/api": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/api/-/api-6.3.9.tgz", - "integrity": "sha512-lwen3jcY4YbnD8spAZrmXcToed/pwad9QpxkG0GNf6ctcOumN6HIK93fKeJ0vvPYc3v/uq1qKeLyTZ3NrgHQRg==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/api/-/api-6.3.12.tgz", + "integrity": "sha512-LScRXUeCWEW/OP+jiooNMQICVdusv7azTmULxtm72fhkXFRiQs2CdRNTiqNg46JLLC9z95f1W+pGK66X6HiiQA==", "dev": true, "requires": { "@reach/router": "^1.3.4", - "@storybook/channels": "6.3.9", - "@storybook/client-logger": "6.3.9", - "@storybook/core-events": "6.3.9", + "@storybook/channels": "6.3.12", + "@storybook/client-logger": "6.3.12", + "@storybook/core-events": "6.3.12", "@storybook/csf": "0.0.1", - "@storybook/router": "6.3.9", + "@storybook/router": "6.3.12", "@storybook/semver": "^7.3.2", - "@storybook/theming": "6.3.9", + "@storybook/theming": "6.3.12", "@types/reach__router": "^1.3.7", "core-js": "^3.8.2", "fast-deep-equal": "^3.1.3", @@ -6360,9 +6360,9 @@ } }, "@storybook/builder-webpack4": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/builder-webpack4/-/builder-webpack4-6.3.9.tgz", - "integrity": "sha512-/Ff0f3vmoCCB62jDvTSKO1BVaZIYNfbDxrHOT0o2NKFmuHwH96qZDwqPhntoAxDYcjJZ6r4+tVo2ktyE+QAGVg==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/builder-webpack4/-/builder-webpack4-6.3.12.tgz", + "integrity": "sha512-Dlm5Fc1svqpFDnVPZdAaEBiM/IDZHMV3RfEGbUTY/ZC0q8b/Ug1czzp/w0aTIjOFRuBDcG6IcplikaqHL8CJLg==", "dev": true, "requires": { "@babel/core": "^7.12.10", @@ -6386,20 +6386,20 @@ "@babel/preset-env": "^7.12.11", "@babel/preset-react": "^7.12.10", "@babel/preset-typescript": "^7.12.7", - "@storybook/addons": "6.3.9", - "@storybook/api": "6.3.9", - "@storybook/channel-postmessage": "6.3.9", - "@storybook/channels": "6.3.9", - "@storybook/client-api": "6.3.9", - "@storybook/client-logger": "6.3.9", - "@storybook/components": "6.3.9", - "@storybook/core-common": "6.3.9", - "@storybook/core-events": "6.3.9", - "@storybook/node-logger": "6.3.9", - "@storybook/router": "6.3.9", + "@storybook/addons": "6.3.12", + "@storybook/api": "6.3.12", + "@storybook/channel-postmessage": "6.3.12", + "@storybook/channels": "6.3.12", + "@storybook/client-api": "6.3.12", + "@storybook/client-logger": "6.3.12", + "@storybook/components": "6.3.12", + "@storybook/core-common": "6.3.12", + "@storybook/core-events": "6.3.12", + "@storybook/node-logger": "6.3.12", + "@storybook/router": "6.3.12", "@storybook/semver": "^7.3.2", - "@storybook/theming": "6.3.9", - "@storybook/ui": "6.3.9", + "@storybook/theming": "6.3.12", + "@storybook/ui": "6.3.12", "@types/node": "^14.0.10", "@types/webpack": "^4.41.26", "autoprefixer": "^9.8.6", @@ -6462,9 +6462,9 @@ } }, "@storybook/node-logger": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-6.3.9.tgz", - "integrity": "sha512-tAo7sLNDGkL20NNGjwFgsywrl5rf/ImJaD6DnhSJDncaMcDy5xOA5Fn1IbkQuWUoKexE+nCkTiiynoP4Nzcp4w==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-6.3.12.tgz", + "integrity": "sha512-iktOem/Ls2+dsZY9PhPeC6T1QhX/y7OInP88neLsqEPEbB2UXca3Ydv7OZBhBVbvN25W45b05MRzbtNUxYLNRw==", "dev": true, "requires": { "@types/npmlog": "^4.1.2", @@ -6503,9 +6503,9 @@ "dev": true }, "@types/node": { - "version": "14.17.20", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.20.tgz", - "integrity": "sha512-gI5Sl30tmhXsqkNvopFydP7ASc4c2cLfGNQrVKN3X90ADFWFsPEsotm/8JHSUJQKTHbwowAHtcJPeyVhtKv0TQ==", + "version": "14.17.29", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.29.tgz", + "integrity": "sha512-sd4CHI9eTJXTH2vF3RGtGkqvWRwhsSSUFsXD4oG38GZzSZ0tNPbWikd2AbOAcKxCXhOg57fL8FPxjpfSzb2pIQ==", "dev": true }, "ajv": { @@ -6997,14 +6997,14 @@ } }, "@storybook/channel-postmessage": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/channel-postmessage/-/channel-postmessage-6.3.9.tgz", - "integrity": "sha512-1kyBpKuHDaohX8btXmD3hdkosYWJFcVy8VhOe8hVhBHScXwxSb+5Fycy38IlAQE/PSrcw5cII9x6vMvtzK/ojA==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/channel-postmessage/-/channel-postmessage-6.3.12.tgz", + "integrity": "sha512-Ou/2Ga3JRTZ/4sSv7ikMgUgLTeZMsXXWLXuscz4oaYhmOqAU9CrJw0G1NitwBgK/+qC83lEFSLujHkWcoQDOKg==", "dev": true, "requires": { - "@storybook/channels": "6.3.9", - "@storybook/client-logger": "6.3.9", - "@storybook/core-events": "6.3.9", + "@storybook/channels": "6.3.12", + "@storybook/client-logger": "6.3.12", + "@storybook/core-events": "6.3.12", "core-js": "^3.8.2", "global": "^4.4.0", "qs": "^6.10.0", @@ -7023,9 +7023,9 @@ } }, "@storybook/channels": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-6.3.9.tgz", - "integrity": "sha512-ZeHXLFJ43Wn6HJMiGgKUWUMtKcXDoWxL50Qr5Wwbsnmtp2BX7R8aak/Vw9TVT46J86QXkdI3CAKAEvb6esiLRQ==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-6.3.12.tgz", + "integrity": "sha512-l4sA+g1PdUV8YCbgs47fIKREdEQAKNdQIZw0b7BfTvY9t0x5yfBywgQhYON/lIeiNGz2OlIuD+VUtqYfCtNSyw==", "dev": true, "requires": { "core-js": "^3.8.2", @@ -7034,16 +7034,16 @@ } }, "@storybook/client-api": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/client-api/-/client-api-6.3.9.tgz", - "integrity": "sha512-epHqkyQu8BSNecuK5yLGBooCC+SoX5HhED2i5TS5o85sO8lB4ujPMrgKqEH3oSKwiy6gHgafewdgs0nczoP2Lw==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/client-api/-/client-api-6.3.12.tgz", + "integrity": "sha512-xnW+lKKK2T774z+rOr9Wopt1aYTStfb86PSs9p3Fpnc2Btcftln+C3NtiHZl8Ccqft8Mz/chLGgewRui6tNI8g==", "dev": true, "requires": { - "@storybook/addons": "6.3.9", - "@storybook/channel-postmessage": "6.3.9", - "@storybook/channels": "6.3.9", - "@storybook/client-logger": "6.3.9", - "@storybook/core-events": "6.3.9", + "@storybook/addons": "6.3.12", + "@storybook/channel-postmessage": "6.3.12", + "@storybook/channels": "6.3.12", + "@storybook/client-logger": "6.3.12", + "@storybook/core-events": "6.3.12", "@storybook/csf": "0.0.1", "@types/qs": "^6.9.5", "@types/webpack-env": "^1.16.0", @@ -7077,9 +7077,9 @@ } }, "@storybook/client-logger": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.3.9.tgz", - "integrity": "sha512-oww+P062SaOQfsTphAQBL6xe5DCv78Po/f/ROk7iYGAbV8HcCCscpzyJSeLfus2CunFYS2ngPcllbvEnqWk7dQ==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.3.12.tgz", + "integrity": "sha512-zNDsamZvHnuqLznDdP9dUeGgQ9TyFh4ray3t1VGO7ZqWVZ2xtVCCXjDvMnOXI2ifMpX5UsrOvshIPeE9fMBmiQ==", "dev": true, "requires": { "core-js": "^3.8.2", @@ -7087,15 +7087,15 @@ } }, "@storybook/components": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/components/-/components-6.3.9.tgz", - "integrity": "sha512-bArMbnzK9esdrgYHG/WAHC+NIMmEzgypvaTs0oEG4lK3q1LiBdrCrLRSCd31oR3RT5a8e06QXZ1rla3OhuZrfg==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/components/-/components-6.3.12.tgz", + "integrity": "sha512-kdQt8toUjynYAxDLrJzuG7YSNL6as1wJoyzNUaCfG06YPhvIAlKo7le9tS2mThVFN5e9nbKrW3N1V1sp6ypZXQ==", "dev": true, "requires": { "@popperjs/core": "^2.6.0", - "@storybook/client-logger": "6.3.9", + "@storybook/client-logger": "6.3.12", "@storybook/csf": "0.0.1", - "@storybook/theming": "6.3.9", + "@storybook/theming": "6.3.12", "@types/color-convert": "^2.0.0", "@types/overlayscrollbars": "^1.12.0", "@types/react-syntax-highlighter": "11.0.5", @@ -7142,28 +7142,28 @@ } }, "@storybook/core": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/core/-/core-6.3.9.tgz", - "integrity": "sha512-A4Vp0tmFBMUBn3U9QKGFDZr0166YjD3oaR7uvR/PWrDPXwnxNXtXQvWeZrFAW4edcNZB8WllatkBg6cWGVKbQg==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/core/-/core-6.3.12.tgz", + "integrity": "sha512-FJm2ns8wk85hXWKslLWiUWRWwS9KWRq7jlkN6M9p57ghFseSGr4W71Orcoab4P3M7jI97l5yqBfppbscinE74g==", "dev": true, "requires": { - "@storybook/core-client": "6.3.9", - "@storybook/core-server": "6.3.9" + "@storybook/core-client": "6.3.12", + "@storybook/core-server": "6.3.12" } }, "@storybook/core-client": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/core-client/-/core-client-6.3.9.tgz", - "integrity": "sha512-EKajuMFaFHJJW4WKfY9s1lMLG1mcg7hB634M/jw/bi1IwK6RI4T/RNp8ptlixTkjnlV5i1dA9DGRGx1P8ZxCHQ==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/core-client/-/core-client-6.3.12.tgz", + "integrity": "sha512-8Smd9BgZHJpAdevLKQYinwtjSyCZAuBMoetP4P5hnn53mWl0NFbrHFaAdT+yNchDLZQUbf7Y18VmIqEH+RCR5w==", "dev": true, "requires": { - "@storybook/addons": "6.3.9", - "@storybook/channel-postmessage": "6.3.9", - "@storybook/client-api": "6.3.9", - "@storybook/client-logger": "6.3.9", - "@storybook/core-events": "6.3.9", + "@storybook/addons": "6.3.12", + "@storybook/channel-postmessage": "6.3.12", + "@storybook/client-api": "6.3.12", + "@storybook/client-logger": "6.3.12", + "@storybook/core-events": "6.3.12", "@storybook/csf": "0.0.1", - "@storybook/ui": "6.3.9", + "@storybook/ui": "6.3.12", "airbnb-js-shims": "^2.2.1", "ansi-to-html": "^0.6.11", "core-js": "^3.8.2", @@ -7194,9 +7194,9 @@ } }, "@storybook/core-common": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-6.3.9.tgz", - "integrity": "sha512-1dStGSXKuABour3jXrfAvMVLb31rNgOQVMowxaROaPPkP0qyZexpUA2OmOAci+MTmincYgcMPWqi/9Cf1D80qQ==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-6.3.12.tgz", + "integrity": "sha512-xlHs2QXELq/moB4MuXjYOczaxU64BIseHsnFBLyboJYN6Yso3qihW5RB7cuJlGohkjb4JwY74dvfT4Ww66rkBA==", "dev": true, "requires": { "@babel/core": "^7.12.10", @@ -7220,7 +7220,7 @@ "@babel/preset-react": "^7.12.10", "@babel/preset-typescript": "^7.12.7", "@babel/register": "^7.12.1", - "@storybook/node-logger": "6.3.9", + "@storybook/node-logger": "6.3.12", "@storybook/semver": "^7.3.2", "@types/glob-base": "^0.3.0", "@types/micromatch": "^4.0.1", @@ -7337,9 +7337,9 @@ } }, "@storybook/node-logger": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-6.3.9.tgz", - "integrity": "sha512-tAo7sLNDGkL20NNGjwFgsywrl5rf/ImJaD6DnhSJDncaMcDy5xOA5Fn1IbkQuWUoKexE+nCkTiiynoP4Nzcp4w==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-6.3.12.tgz", + "integrity": "sha512-iktOem/Ls2+dsZY9PhPeC6T1QhX/y7OInP88neLsqEPEbB2UXca3Ydv7OZBhBVbvN25W45b05MRzbtNUxYLNRw==", "dev": true, "requires": { "@types/npmlog": "^4.1.2", @@ -7372,9 +7372,9 @@ } }, "@types/node": { - "version": "14.17.20", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.20.tgz", - "integrity": "sha512-gI5Sl30tmhXsqkNvopFydP7ASc4c2cLfGNQrVKN3X90ADFWFsPEsotm/8JHSUJQKTHbwowAHtcJPeyVhtKv0TQ==", + "version": "14.17.29", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.29.tgz", + "integrity": "sha512-sd4CHI9eTJXTH2vF3RGtGkqvWRwhsSSUFsXD4oG38GZzSZ0tNPbWikd2AbOAcKxCXhOg57fL8FPxjpfSzb2pIQ==", "dev": true }, "ansi-styles": { @@ -7506,9 +7506,9 @@ } }, "fork-ts-checker-webpack-plugin": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.3.3.tgz", - "integrity": "sha512-S3uMSg8IsIvs0H6VAfojtbf6RcnEXxEpDMT2Q41M2l0m20JO8eA1t4cCJybvrasC8SvvPEtK4B8ztxxfLljhNg==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.4.0.tgz", + "integrity": "sha512-3I3wFkc4DbzaUDPWEi96wdYGu4EKtxBafhZYm0o4mX51d9bphAY4P3mBl8K5mFXFJqVzHfmdbm9kLGnm7vwwBg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", @@ -7527,9 +7527,9 @@ }, "dependencies": { "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", + "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", "dev": true, "requires": { "@babel/highlight": "^7.14.5" @@ -7756,27 +7756,27 @@ } }, "@storybook/core-events": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.3.9.tgz", - "integrity": "sha512-6ELOkroH0Oz7+OR1SqGMKAC1+ufituqSxDp08AyvrHPSYqK/db+P2kSCJBdqyUXTvt8lPvqlCOidkRhGrNB/+A==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.3.12.tgz", + "integrity": "sha512-SXfD7xUUMazaeFkB92qOTUV8Y/RghE4SkEYe5slAdjeocSaH7Nz2WV0rqNEgChg0AQc+JUI66no8L9g0+lw4Gw==", "dev": true, "requires": { "core-js": "^3.8.2" } }, "@storybook/core-server": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/core-server/-/core-server-6.3.9.tgz", - "integrity": "sha512-iUMtB0RqdX3YW7FMlg8lScUNrkfcbtLurH3hC+2CkJEailTRUQ8AjlwLc0gjIh0QgbsxUexo1iQW1NFuHBxpDw==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/core-server/-/core-server-6.3.12.tgz", + "integrity": "sha512-T/Mdyi1FVkUycdyOnhXvoo3d9nYXLQFkmaJkltxBFLzAePAJUSgAsPL9odNC3+p8Nr2/UDsDzvu/Ow0IF0mzLQ==", "dev": true, "requires": { "@discoveryjs/json-ext": "^0.5.3", - "@storybook/builder-webpack4": "6.3.9", - "@storybook/core-client": "6.3.9", - "@storybook/core-common": "6.3.9", - "@storybook/csf-tools": "6.3.9", - "@storybook/manager-webpack4": "6.3.9", - "@storybook/node-logger": "6.3.9", + "@storybook/builder-webpack4": "6.3.12", + "@storybook/core-client": "6.3.12", + "@storybook/core-common": "6.3.12", + "@storybook/csf-tools": "6.3.12", + "@storybook/manager-webpack4": "6.3.12", + "@storybook/node-logger": "6.3.12", "@storybook/semver": "^7.3.2", "@types/node": "^14.0.10", "@types/node-fetch": "^2.5.7", @@ -7807,9 +7807,9 @@ }, "dependencies": { "@storybook/node-logger": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-6.3.9.tgz", - "integrity": "sha512-tAo7sLNDGkL20NNGjwFgsywrl5rf/ImJaD6DnhSJDncaMcDy5xOA5Fn1IbkQuWUoKexE+nCkTiiynoP4Nzcp4w==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-6.3.12.tgz", + "integrity": "sha512-iktOem/Ls2+dsZY9PhPeC6T1QhX/y7OInP88neLsqEPEbB2UXca3Ydv7OZBhBVbvN25W45b05MRzbtNUxYLNRw==", "dev": true, "requires": { "@types/npmlog": "^4.1.2", @@ -7830,9 +7830,9 @@ } }, "@types/node": { - "version": "14.17.20", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.20.tgz", - "integrity": "sha512-gI5Sl30tmhXsqkNvopFydP7ASc4c2cLfGNQrVKN3X90ADFWFsPEsotm/8JHSUJQKTHbwowAHtcJPeyVhtKv0TQ==", + "version": "14.17.29", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.29.tgz", + "integrity": "sha512-sd4CHI9eTJXTH2vF3RGtGkqvWRwhsSSUFsXD4oG38GZzSZ0tNPbWikd2AbOAcKxCXhOg57fL8FPxjpfSzb2pIQ==", "dev": true }, "ansi-regex": { @@ -8058,9 +8058,9 @@ } }, "@storybook/csf-tools": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-6.3.9.tgz", - "integrity": "sha512-rgAcq/3x8HEIRJdEqw21YT6zDdvEoCUZOMS2XYISJstFZp3weK+PbQr5tsMefAoVjpsNCi9ypYSXMT6dxnNbPw==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-6.3.12.tgz", + "integrity": "sha512-wNrX+99ajAXxLo0iRwrqw65MLvCV6SFC0XoPLYrtBvyKr+hXOOnzIhO2f5BNEii8velpC2gl2gcLKeacpVYLqA==", "dev": true, "requires": { "@babel/generator": "^7.12.11", @@ -8128,20 +8128,20 @@ } }, "@storybook/manager-webpack4": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/manager-webpack4/-/manager-webpack4-6.3.9.tgz", - "integrity": "sha512-I5ckE4p1m8c1GcRi8BjXxqvdfo4gxX2Lhf7fH8udiSRO/MTyLeYHqmVmdKXcBpQRxHpmJ14oBoBBP5usWXgCVw==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/manager-webpack4/-/manager-webpack4-6.3.12.tgz", + "integrity": "sha512-OkPYNrHXg2yZfKmEfTokP6iKx4OLTr0gdI5yehi/bLEuQCSHeruxBc70Dxm1GBk1Mrf821wD9WqMXNDjY5Qtug==", "dev": true, "requires": { "@babel/core": "^7.12.10", "@babel/plugin-transform-template-literals": "^7.12.1", "@babel/preset-react": "^7.12.10", - "@storybook/addons": "6.3.9", - "@storybook/core-client": "6.3.9", - "@storybook/core-common": "6.3.9", - "@storybook/node-logger": "6.3.9", - "@storybook/theming": "6.3.9", - "@storybook/ui": "6.3.9", + "@storybook/addons": "6.3.12", + "@storybook/core-client": "6.3.12", + "@storybook/core-common": "6.3.12", + "@storybook/node-logger": "6.3.12", + "@storybook/theming": "6.3.12", + "@storybook/ui": "6.3.12", "@types/node": "^14.0.10", "@types/webpack": "^4.41.26", "babel-loader": "^8.2.2", @@ -8173,9 +8173,9 @@ }, "dependencies": { "@storybook/node-logger": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-6.3.9.tgz", - "integrity": "sha512-tAo7sLNDGkL20NNGjwFgsywrl5rf/ImJaD6DnhSJDncaMcDy5xOA5Fn1IbkQuWUoKexE+nCkTiiynoP4Nzcp4w==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-6.3.12.tgz", + "integrity": "sha512-iktOem/Ls2+dsZY9PhPeC6T1QhX/y7OInP88neLsqEPEbB2UXca3Ydv7OZBhBVbvN25W45b05MRzbtNUxYLNRw==", "dev": true, "requires": { "@types/npmlog": "^4.1.2", @@ -8192,9 +8192,9 @@ "dev": true }, "@types/node": { - "version": "14.17.20", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.20.tgz", - "integrity": "sha512-gI5Sl30tmhXsqkNvopFydP7ASc4c2cLfGNQrVKN3X90ADFWFsPEsotm/8JHSUJQKTHbwowAHtcJPeyVhtKv0TQ==", + "version": "14.17.29", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.29.tgz", + "integrity": "sha512-sd4CHI9eTJXTH2vF3RGtGkqvWRwhsSSUFsXD4oG38GZzSZ0tNPbWikd2AbOAcKxCXhOg57fL8FPxjpfSzb2pIQ==", "dev": true }, "ajv": { @@ -8772,27 +8772,27 @@ } }, "@storybook/postinstall": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/postinstall/-/postinstall-6.3.9.tgz", - "integrity": "sha512-XamQmpR56n2foah0Y3AcT2jn9t8rUdt7u1VfY92dM9zmGBUgL+2u4nsBcYWzWoFllAShengUUqrs1Ci7+rKpjw==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/postinstall/-/postinstall-6.3.12.tgz", + "integrity": "sha512-HkZ+abtZ3W6JbGPS6K7OSnNXbwaTwNNd5R02kRs4gV9B29XsBPDtFT6vIwzM3tmVQC7ihL5a8ceWp2OvzaNOuw==", "dev": true, "requires": { "core-js": "^3.8.2" } }, "@storybook/react": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/react/-/react-6.3.9.tgz", - "integrity": "sha512-LYlAURnKvFsf3CZG2CDvQwvlF/vy5VsKuNiNmyTk6zggmkbitXrwkuT7KoElYrz4REkyhd/HA6Uw6lfkPsyuHA==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/react/-/react-6.3.12.tgz", + "integrity": "sha512-c1Y/3/eNzye+ZRwQ3BXJux6pUMVt3lhv1/M9Qagl9JItP3jDSj5Ed3JHCgwEqpprP8mvNNXwEJ8+M7vEQyDuHg==", "dev": true, "requires": { "@babel/preset-flow": "^7.12.1", "@babel/preset-react": "^7.12.10", "@pmmmwh/react-refresh-webpack-plugin": "^0.4.3", - "@storybook/addons": "6.3.9", - "@storybook/core": "6.3.9", - "@storybook/core-common": "6.3.9", - "@storybook/node-logger": "6.3.9", + "@storybook/addons": "6.3.12", + "@storybook/core": "6.3.12", + "@storybook/core-common": "6.3.12", + "@storybook/node-logger": "6.3.12", "@storybook/react-docgen-typescript-plugin": "1.0.2-canary.253f8c1.0", "@storybook/semver": "^7.3.2", "@types/webpack-env": "^1.16.0", @@ -8812,9 +8812,9 @@ }, "dependencies": { "@storybook/node-logger": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-6.3.9.tgz", - "integrity": "sha512-tAo7sLNDGkL20NNGjwFgsywrl5rf/ImJaD6DnhSJDncaMcDy5xOA5Fn1IbkQuWUoKexE+nCkTiiynoP4Nzcp4w==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-6.3.12.tgz", + "integrity": "sha512-iktOem/Ls2+dsZY9PhPeC6T1QhX/y7OInP88neLsqEPEbB2UXca3Ydv7OZBhBVbvN25W45b05MRzbtNUxYLNRw==", "dev": true, "requires": { "@types/npmlog": "^4.1.2", @@ -9137,13 +9137,13 @@ } }, "@storybook/router": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/router/-/router-6.3.9.tgz", - "integrity": "sha512-uXNrZS9tsZr6fStIv/MHQfy3xSsc7RLYWbY4wkgZH+y5K97RtuwXgtbx7uyEpsQwse1Z4PikKu/ejN46F0oPGQ==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/router/-/router-6.3.12.tgz", + "integrity": "sha512-G/pNGCnrJRetCwyEZulHPT+YOcqEj/vkPVDTUfii2qgqukup6K0cjwgd7IukAURnAnnzTi1gmgFuEKUi8GE/KA==", "dev": true, "requires": { "@reach/router": "^1.3.4", - "@storybook/client-logger": "6.3.9", + "@storybook/client-logger": "6.3.12", "@types/reach__router": "^1.3.7", "core-js": "^3.8.2", "fast-deep-equal": "^3.1.3", @@ -9172,13 +9172,13 @@ } }, "@storybook/source-loader": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/source-loader/-/source-loader-6.3.9.tgz", - "integrity": "sha512-H0W4DK3EyXLLdzx5ThoXkC9mPKBAJUb8qoDL3jcTA1sQUDSOsxdosQIrJ97Ljcu21eQJs6egnxh3yPa1CWXTGA==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/source-loader/-/source-loader-6.3.12.tgz", + "integrity": "sha512-Lfe0LOJGqAJYkZsCL8fhuQOeFSCgv8xwQCt4dkcBd0Rw5zT2xv0IXDOiIOXGaWBMDtrJUZt/qOXPEPlL81Oaqg==", "dev": true, "requires": { - "@storybook/addons": "6.3.9", - "@storybook/client-logger": "6.3.9", + "@storybook/addons": "6.3.12", + "@storybook/client-logger": "6.3.12", "@storybook/csf": "0.0.1", "core-js": "^3.8.2", "estraverse": "^5.2.0", @@ -9210,15 +9210,15 @@ } }, "@storybook/theming": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-6.3.9.tgz", - "integrity": "sha512-vyMSLvEXrTC4rnUdWLUNmBNeOdBCl0Nt3R6y/laY+LQZ9Ljz/poRTrIYTkmenYieq4N7787s9zHmxvym/ZvKtw==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-6.3.12.tgz", + "integrity": "sha512-wOJdTEa/VFyFB2UyoqyYGaZdym6EN7RALuQOAMT6zHA282FBmKw8nL5DETHEbctpnHdcrMC/391teK4nNSrdOA==", "dev": true, "requires": { "@emotion/core": "^10.1.1", "@emotion/is-prop-valid": "^0.8.6", "@emotion/styled": "^10.0.27", - "@storybook/client-logger": "6.3.9", + "@storybook/client-logger": "6.3.12", "core-js": "^3.8.2", "deep-object-diff": "^1.1.0", "emotion-theming": "^10.0.27", @@ -9230,21 +9230,21 @@ } }, "@storybook/ui": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/@storybook/ui/-/ui-6.3.9.tgz", - "integrity": "sha512-QyRwofApyHOvjWPXirNYFleSVsjluYl7QmZgkv+vT09sV6q0YS1M2YQiDjoPwSIG0OHvxNoY90yNHjx8aXo4gA==", + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@storybook/ui/-/ui-6.3.12.tgz", + "integrity": "sha512-PC2yEz4JMfarq7rUFbeA3hCA+31p5es7YPEtxLRvRwIZhtL0P4zQUfHpotb3KgWdoAIfZesAuoIQwMPQmEFYrw==", "dev": true, "requires": { "@emotion/core": "^10.1.1", - "@storybook/addons": "6.3.9", - "@storybook/api": "6.3.9", - "@storybook/channels": "6.3.9", - "@storybook/client-logger": "6.3.9", - "@storybook/components": "6.3.9", - "@storybook/core-events": "6.3.9", - "@storybook/router": "6.3.9", + "@storybook/addons": "6.3.12", + "@storybook/api": "6.3.12", + "@storybook/channels": "6.3.12", + "@storybook/client-logger": "6.3.12", + "@storybook/components": "6.3.12", + "@storybook/core-events": "6.3.12", + "@storybook/router": "6.3.12", "@storybook/semver": "^7.3.2", - "@storybook/theming": "6.3.9", + "@storybook/theming": "6.3.12", "@types/markdown-to-jsx": "^6.11.3", "copy-to-clipboard": "^3.3.1", "core-js": "^3.8.2", @@ -9694,9 +9694,9 @@ } }, "@types/ejson": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@types/ejson/-/ejson-2.1.2.tgz", - "integrity": "sha1-oMuiYNUAYxDch3kFRj2D1fPN8RI=", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@types/ejson/-/ejson-2.1.3.tgz", + "integrity": "sha512-Sd+XISmDWOypfDcsKeQkhykSMgYVAWJxhf7f0ySvfy2tYo+im26M/6FfqjCEiPSDAEugiuZKtA+wWeANKueWIg==", "dev": true }, "@types/elliptic": { @@ -9764,9 +9764,9 @@ } }, "@types/glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-w+LsMxKyYQm347Otw+IfBXOv9UWVjpHpCDdbBMt8Kz/xbvCYNjP+0qPh91Km3iKfSRLBB0P7fAMf0KHrPu+MyA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", "dev": true, "requires": { "@types/minimatch": "*", @@ -9851,9 +9851,9 @@ } }, "@types/jquery": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.1.tgz", - "integrity": "sha512-Tyctjh56U7eX2b9udu3wG853ASYP0uagChJcQJXLUXEU6C/JiW5qt5dl8ao01VRj1i5pgXPAf8f1mq4+FDLRQg==", + "version": "3.5.8", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.8.tgz", + "integrity": "sha512-cXk6NwqjDYg+UI9p2l3x0YmPa4m7RrXqmbK4IpVVpRJiYXU/QTo+UZrn54qfE1+9Gao4qpYqUnxm5ZCy2FTXAw==", "dev": true, "requires": { "@types/sizzle": "*" @@ -9907,9 +9907,9 @@ "dev": true }, "@types/lodash": { - "version": "4.14.171", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.171.tgz", - "integrity": "sha512-7eQ2xYLLI/LsicL2nejW9Wyko3lcpN6O/z0ZLHrEQsg280zIdCv1t/0m6UtBjUHokCGBQ3gYTbHzDkZ1xOBwwg==" + "version": "4.14.177", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.177.tgz", + "integrity": "sha512-0fDwydE2clKe9MNfvXHBHF9WEahRuj+msTuQqOmAApNORFvhMYZKNGGJdCzuhheVjMps/ti0Ak/iJPACMaevvw==" }, "@types/lodash.debounce": { "version": "4.0.6", @@ -10207,9 +10207,9 @@ } }, "@types/react": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.32.tgz", - "integrity": "sha512-hAm1pmwA3oZWbkB985RFwNvBRMG0F3KWSiC4/hNmanigKZMiKQoH5Q6etNw8HIDztTGfvXyOjPvdNnvBUCuaPg==", + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.35.tgz", + "integrity": "sha512-r3C8/TJuri/SLZiiwwxQoLAoavaczARfT9up9b4Jr65+ErAUX3MIkU0oMOQnrpfgHme8zIqZLX7O5nnjm5Wayw==", "dev": true, "requires": { "@types/prop-types": "*", @@ -10218,17 +10218,17 @@ }, "dependencies": { "csstype": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz", - "integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", + "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==", "dev": true } } }, "@types/react-dom": { - "version": "17.0.10", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.10.tgz", - "integrity": "sha512-8oz3NAUId2z/zQdFI09IMhQPNgIbiP8Lslhv39DIDamr846/0spjZK0vnrMak0iB8EKb9QFTTIdg2Wj2zH5a3g==", + "version": "17.0.11", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz", + "integrity": "sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==", "dev": true, "requires": { "@types/react": "*" @@ -10371,9 +10371,9 @@ "integrity": "sha512-7cTXwKP/HLOPVgjg+YhBdQ7bMiobGMuoBmrGmqwIWJv8elC6t1DfVc/mn4fD9UE1IjhwmhaQ5pGVXkmXbH0rhg==" }, "@types/toastr": { - "version": "2.1.38", - "resolved": "https://registry.npmjs.org/@types/toastr/-/toastr-2.1.38.tgz", - "integrity": "sha512-zKF+vbPVkkwBaMy0lm5NdI117mOoxWOQf2eXOuP/upQ5lHDSfNK/bVoo/x8/IN1hLzO81g+JvTpZQhqr0gKyYg==", + "version": "2.1.39", + "resolved": "https://registry.npmjs.org/@types/toastr/-/toastr-2.1.39.tgz", + "integrity": "sha512-jgbMLTjj7dsSY/EYYOoZXhK6OY/bR909Bn6YNnwL3Oq+f0AeFKbYa198XU/6bsmUqqCCWw5VvCi11FDN1/fetw==", "dev": true, "requires": { "@types/jquery": "*" @@ -10463,9 +10463,9 @@ } }, "@types/webpack-env": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.16.2.tgz", - "integrity": "sha512-vKx7WNQNZDyJveYcHAm9ZxhqSGLYwoyLhrHjLBOkw3a7cT76sTdjgtwyijhk1MaHyRIuSztcVwrUOO/NEu68Dw==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.16.3.tgz", + "integrity": "sha512-9gtOPPkfyNoEqCQgx4qJKkuNm/x0R2hKR7fdl7zvTJyHnIisuE/LfvXOsYWL0o3qq6uiBnKZNNNzi3l0y/X+xw==", "dev": true }, "@types/webpack-sources": { @@ -13308,15 +13308,15 @@ } }, "babel-plugin-istanbul": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", - "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-instrument": "^5.0.4", "test-exclude": "^6.0.0" } }, @@ -14001,6 +14001,21 @@ } } }, + "broadcast-channel": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz", + "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==", + "requires": { + "@babel/runtime": "^7.7.2", + "detect-node": "^2.1.0", + "js-sha3": "0.8.0", + "microseconds": "0.2.0", + "nano-time": "1.0.0", + "oblivious-set": "1.0.0", + "rimraf": "3.0.2", + "unload": "2.2.0" + } + }, "brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", @@ -14294,16 +14309,16 @@ "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" }, "c8": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/c8/-/c8-7.9.0.tgz", - "integrity": "sha512-aQ7dC8gASnKdBwHUuYuzsdKCEDrKnWr7ZuZUnf4CNAL81oyKloKrs7H7zYvcrmCtIrMToudBSUhq2q+LLBMvgg==", + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/c8/-/c8-7.10.0.tgz", + "integrity": "sha512-OAwfC5+emvA6R7pkYFVBTOtI5ruf9DahffGmIqUc9l6wEh0h7iAFP6dt/V9Ioqlr2zW5avX9U9/w1I4alTRHkA==", "dev": true, "requires": { "@bcoe/v8-coverage": "^0.2.3", "@istanbuljs/schema": "^0.1.2", "find-up": "^5.0.0", "foreground-child": "^2.0.0", - "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-coverage": "^3.0.1", "istanbul-lib-report": "^3.0.0", "istanbul-reports": "^3.0.2", "rimraf": "^3.0.0", @@ -14505,9 +14520,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001257", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001257.tgz", - "integrity": "sha512-JN49KplOgHSXpIsVSF+LUyhD8PUp6xPpAXeRrrcBh4KBeP7W864jHn6RvzJgDlrReyeVjMFJL3PLpPvKIxlIHA==" + "version": "1.0.30001271", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001271.tgz", + "integrity": "sha512-BBruZFWmt3HFdVPS8kceTBIguKxu4f99n5JNp06OlPD/luoAMIaIK5ieV5YjnBLH3Nysai9sxj9rpJj4ZisXOA==" }, "capital-case": { "version": "1.0.4", @@ -15060,9 +15075,9 @@ } }, "clean-css": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", - "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", + "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", "dev": true, "requires": { "source-map": "~0.6.0" @@ -15637,9 +15652,9 @@ } }, "core-js-pure": { - "version": "3.18.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.18.1.tgz", - "integrity": "sha512-kmW/k8MaSuqpvA1xm2l3TVlBuvW+XBkcaOroFUpO3D4lsTGQWBTb/tBDCf/PNkkPLrwgrkQRIYNPB0CeqGJWGQ==", + "version": "3.18.3", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.18.3.tgz", + "integrity": "sha512-qfskyO/KjtbYn09bn1IPkuhHl5PlJ6IzJ9s9sraJ1EqcuGyLGKzhSM1cY0zgyL9hx42eulQLZ6WaeK5ycJCkqw==", "dev": true }, "core-util-is": { @@ -16811,6 +16826,11 @@ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" }, + "detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" + }, "detect-port-alt": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", @@ -19591,9 +19611,9 @@ }, "dependencies": { "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", + "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", "dev": true, "requires": { "@babel/highlight": "^7.14.5" @@ -23271,18 +23291,19 @@ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "istanbul-lib-coverage": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.1.tgz", - "integrity": "sha512-GvCYYTxaCPqwMjobtVcVKvSHtAGe48MNhGjpK8LtVF8K0ISX7hCKl85LgtuaSneWVyQmaGcW3iXVV3GaZSLpmQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", "dev": true }, "istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.0.4.tgz", + "integrity": "sha512-W6jJF9rLGEISGoCyXRqa/JCGQGmmxPO10TMu7izaUTynxvBvTjqzAIIGCK9USBmIbQAaSWD6XJPrM9Pv5INknw==", "dev": true, "requires": { - "@babel/core": "^7.7.5", + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-coverage": "^3.0.0", "semver": "^6.3.0" @@ -23340,9 +23361,9 @@ } }, "istanbul-reports": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", - "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.5.tgz", + "integrity": "sha512-5+19PlhnGabNWB7kOFnuxT8H3T/iIyQzIbQMxXsURmmvKg86P2sbkrGOT77VnHw0Qr0gc2XzRaRfMZYYbSQCJQ==", "dev": true, "requires": { "html-escaper": "^2.0.0", @@ -23639,6 +23660,11 @@ "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==", "dev": true }, + "js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + }, "js-string-escape": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", @@ -25113,12 +25139,12 @@ "dev": true }, "makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", "dev": true, "requires": { - "tmpl": "1.0.x" + "tmpl": "1.0.5" } }, "map-age-cleaner": { @@ -25193,6 +25219,15 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==" }, + "match-sorter": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz", + "integrity": "sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==", + "requires": { + "@babel/runtime": "^7.12.5", + "remove-accents": "0.4.2" + } + }, "mathml-tag-names": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", @@ -26324,6 +26359,11 @@ "to-regex": "^3.0.2" } }, + "microseconds": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz", + "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==" + }, "miller-rabin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", @@ -27267,6 +27307,14 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "nano-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz", + "integrity": "sha1-sFVPaa2J4i0JB/ehKwmTpdlhN+8=", + "requires": { + "big-integer": "^1.6.16" + } + }, "nanoid": { "version": "3.1.23", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", @@ -28089,6 +28137,11 @@ "integrity": "sha512-eJJDYkhJFFbBBAxeh8xW+weHlkI28n2ZdQV/J/DNfWfSKlGEf2xcfAbZTv3riEXHAhL9SVOTs2pRmXiSTf78xg==", "dev": true }, + "oblivious-set": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz", + "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==" + }, "on-exit-leak-free": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-0.2.0.tgz", @@ -31283,9 +31336,9 @@ } }, "react-docgen-typescript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/react-docgen-typescript/-/react-docgen-typescript-2.1.0.tgz", - "integrity": "sha512-7kpzLsYzVxff//HUVz1sPWLCdoSNvHD3M8b/iQLdF8fgf7zp26eVysRrAUSxiAT4yQv2zl09zHjJEYSYNxQ8Jw==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/react-docgen-typescript/-/react-docgen-typescript-2.1.1.tgz", + "integrity": "sha512-XWe8bsYqVjxciKdpNoufaHiB7FgUHIOnVQgxUolRL3Zlof2zkdTzuQH6SU2n3Ek9kfy3O1c63ojMtNfpiuNeZQ==", "dev": true }, "react-dom": { @@ -31309,19 +31362,26 @@ } }, "react-element-to-jsx-string": { - "version": "14.3.2", - "resolved": "https://registry.npmjs.org/react-element-to-jsx-string/-/react-element-to-jsx-string-14.3.2.tgz", - "integrity": "sha512-WZbvG72cjLXAxV7VOuSzuHEaI3RHj10DZu8EcKQpkKcAj7+qAkG5XUeSdX5FXrA0vPrlx0QsnAzZEBJwzV0e+w==", + "version": "14.3.4", + "resolved": "https://registry.npmjs.org/react-element-to-jsx-string/-/react-element-to-jsx-string-14.3.4.tgz", + "integrity": "sha512-t4ZwvV6vwNxzujDQ+37bspnLwA4JlgUPWhLjBJWsNIDceAf6ZKUTCjdm08cN6WeZ5pTMKiCJkmAYnpmR4Bm+dg==", "dev": true, "requires": { - "@base2/pretty-print-object": "1.0.0", - "is-plain-object": "3.0.1" + "@base2/pretty-print-object": "1.0.1", + "is-plain-object": "5.0.0", + "react-is": "17.0.2" }, "dependencies": { "is-plain-object": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz", - "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true } } @@ -31429,6 +31489,16 @@ "react-popper": "^2.2.4" } }, + "react-query": { + "version": "3.33.1", + "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.33.1.tgz", + "integrity": "sha512-RtvQKhD4sJkoAGbyFpLmftGezz1KL19SeGdmjIOLUulyPTZuhcn3gemee96yjEiBk/9LFB5CuSiqywZY20Qj5Q==", + "requires": { + "@babel/runtime": "^7.5.5", + "broadcast-channel": "^3.4.1", + "match-sorter": "^6.0.2" + } + }, "react-refresh": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz", @@ -31686,22 +31756,14 @@ } }, "refractor": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.4.0.tgz", - "integrity": "sha512-dBeD02lC5eytm9Gld2Mx0cMcnR+zhSnsTfPpWqFaMgUMJfC9A6bcN3Br/NaXrnBJcuxnLFR90k1jrkaSyV8umg==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.5.0.tgz", + "integrity": "sha512-QwPJd3ferTZ4cSPPjdP5bsYHMytwWYnAN5EEnLtGvkqp/FCCnGsBgxrm9EuIDnjUC3Uc/kETtvVi7fSIVC74Dg==", "dev": true, "requires": { "hastscript": "^6.0.0", "parse-entities": "^2.0.0", - "prismjs": "~1.24.0" - }, - "dependencies": { - "prismjs": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.24.1.tgz", - "integrity": "sha512-mNPsedLuk90RVJioIky8ANZEwYm5w9LcvCXrxHlwf4fNVSn8jEipMybMkWUyyF0JhnC+C4VcOVSBuHRKs1L5Ow==", - "dev": true - } + "prismjs": "~1.25.0" } }, "regenerate": { @@ -31814,9 +31876,9 @@ }, "dependencies": { "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", + "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", "dev": true, "requires": { "@babel/highlight": "^7.14.5" @@ -31995,6 +32057,11 @@ "mdast-util-to-markdown": "^0.6.0" } }, + "remove-accents": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz", + "integrity": "sha1-CkPTqq4egNuRngeuJUsoXZ4ce7U=" + }, "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -32028,9 +32095,9 @@ } }, "css-what": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.0.1.tgz", - "integrity": "sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz", + "integrity": "sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw==", "dev": true }, "dom-serializer": { @@ -36369,6 +36436,15 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" }, + "unload": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz", + "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==", + "requires": { + "@babel/runtime": "^7.6.2", + "detect-node": "^2.0.4" + } + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -36827,12 +36903,12 @@ "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==" }, "walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "dev": true, "requires": { - "makeerror": "1.0.x" + "makeerror": "1.0.12" } }, "warning": { diff --git a/package.json b/package.json index 5a7bcf01fbf73..3fa78771ee1b6 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,8 @@ "set-version": "node .scripts/set-version.js", "release": "meteor npm run set-version --silent", "storybook": "cross-env NODE_OPTIONS=--max-old-space-size=8192 start-storybook -p 6006", - "build-storybook": "cross-env NODE_OPTIONS=--max-old-space-size=8192 build-storybook" + "storybook-ee": "cross-env NODE_OPTIONS=--max-old-space-size=8192 EE=true start-storybook -p 6006", + "build-storybook": "cross-env NODE_OPTIONS=--max-old-space-size=8192 EE=true build-storybook " }, "license": "MIT", "repository": { @@ -62,10 +63,10 @@ "@rocket.chat/eslint-config": "^0.4.0", "@rocket.chat/livechat": "^1.9.6", "@settlin/spacebars-loader": "^1.0.9", - "@storybook/addon-essentials": "^6.3.9", + "@storybook/addon-essentials": "^6.3.12", "@storybook/addon-postcss": "^2.0.0", - "@storybook/addons": "^6.3.9", - "@storybook/react": "^6.3.9", + "@storybook/addons": "^6.3.12", + "@storybook/react": "^6.3.12", "@testing-library/react": "^12.1.2", "@testing-library/user-event": "^13.5.0", "@types/adm-zip": "^0.4.34", @@ -79,7 +80,7 @@ "@types/chai-spies": "^1.0.3", "@types/clipboard": "^2.0.1", "@types/dompurify": "^2.2.2", - "@types/ejson": "^2.1.2", + "@types/ejson": "^2.1.3", "@types/express": "^4.17.12", "@types/fibers": "^3.1.0", "@types/imap": "^0.8.35", @@ -102,14 +103,14 @@ "@types/parseurl": "^1.3.1", "@types/photoswipe": "^4.1.2", "@types/psl": "^1.1.0", - "@types/react": "^17.0.32", - "@types/react-dom": "^17.0.10", + "@types/react": "^17.0.35", + "@types/react-dom": "^17.0.11", "@types/rewire": "^2.5.28", "@types/semver": "^7.3.6", "@types/sharp": "^0.28.3", "@types/string-strip-html": "^5.0.0", "@types/supertest": "^2.0.11", - "@types/toastr": "^2.1.38", + "@types/toastr": "^2.1.39", "@types/underscore.string": "0.0.38", "@types/use-subscription": "^1.0.0", "@types/uuid": "^8.3.1", @@ -187,7 +188,7 @@ "@rocket.chat/ui-kit": "^0.30.1", "@slack/client": "^4.12.0", "@types/cookie": "^0.4.1", - "@types/lodash": "^4.14.171", + "@types/lodash": "^4.14.177", "@types/lodash.debounce": "^4.0.6", "adm-zip": "0.4.14", "agenda": "github:RocketChat/agenda#3.1.2", @@ -286,6 +287,7 @@ "react": "^17.0.2", "react-dom": "^17.0.2", "react-keyed-flatten-children": "^1.3.0", + "react-query": "^3.33.1", "react-virtuoso": "^1.2.4", "semver": "^5.7.1", "sharp": "^0.22.1", diff --git a/server/sdk/types/ITeamService.ts b/server/sdk/types/ITeamService.ts index 1d464759397ab..511c42afc21fe 100644 --- a/server/sdk/types/ITeamService.ts +++ b/server/sdk/types/ITeamService.ts @@ -12,25 +12,25 @@ export interface ITeamCreateRoom extends Omit { export interface ITeamCreateParams { team: Pick; room: ITeamCreateRoom; - members?: Array; // list of user _ids - owner?: string; // the team owner. If not present, owner = requester + members?: Array | null; // list of user _ids + owner?: string | null; // the team owner. If not present, owner = requester } export interface ITeamMemberParams { userId: string; - roles?: Array; + roles?: Array | null; } export interface IUserInfo { _id: string; - username?: string; + username?: string | null; name: string; status: string; } export interface ITeamMemberInfo { user: IUserInfo; - roles?: string[]; + roles?: string[] | null; createdBy: Omit; createdAt: Date; } From 34cb351a15f09b4b97cf65bb98f088558f2b5992 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Mon, 22 Nov 2021 16:45:21 -0300 Subject: [PATCH 096/137] [FIX][ENTERPRISE] OAuth "Merge Roles" removes roles from users (#23588) * Fix OAuth 'Merge Roles' * Add setting to control which roles to sync Co-authored-by: pierre-lehnen-rc <55164754+pierre-lehnen-rc@users.noreply.github.com> --- .../server/custom_oauth_server.js | 4 +-- app/lib/server/functions/addOAuthService.ts | 12 +++++++++ app/lib/server/methods/removeOAuthService.ts | 1 + app/lib/server/startup/oAuthServicesUpdate.js | 3 +++ .../server/lib/getServicesStatistics.ts | 2 +- ee/server/configuration/oauth.ts | 4 ++- ee/server/lib/oauth/Manager.ts | 18 +++++-------- packages/rocketchat-i18n/i18n/en.i18n.json | 2 ++ server/startup/migrations/index.ts | 1 + server/startup/migrations/v247.ts | 27 +++++++++++++++++++ 10 files changed, 59 insertions(+), 15 deletions(-) create mode 100644 server/startup/migrations/v247.ts diff --git a/app/custom-oauth/server/custom_oauth_server.js b/app/custom-oauth/server/custom_oauth_server.js index 2be67a0925473..2e62be9647eb2 100644 --- a/app/custom-oauth/server/custom_oauth_server.js +++ b/app/custom-oauth/server/custom_oauth_server.js @@ -334,6 +334,8 @@ export class CustomOAuth { return; } + callbacks.run('afterProcessOAuthUser', { serviceName, serviceData, user }); + // User already created or merged and has identical name as before if (user.services && user.services[serviceName] && user.services[serviceName].id === serviceData.id && user.name === serviceData.name) { return; @@ -343,8 +345,6 @@ export class CustomOAuth { throw new Meteor.Error('CustomOAuth', `User with username ${ user.username } already exists`); } - callbacks.run('afterProcessOAuthUser', { serviceName, serviceData, user }); - const serviceIdKey = `services.${ serviceName }.id`; const update = { $set: { diff --git a/app/lib/server/functions/addOAuthService.ts b/app/lib/server/functions/addOAuthService.ts index a41bb2ee174e7..1a2a220fcddc7 100644 --- a/app/lib/server/functions/addOAuthService.ts +++ b/app/lib/server/functions/addOAuthService.ts @@ -57,6 +57,18 @@ export function addOAuthService(name: string, values: { [k: string]: string | bo enterprise: true, invalidValue: false, modules: ['oauth-enterprise'] }); + settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-roles_to_sync` , values.rolesToSync || '', { type: 'string', + group: 'OAuth', + section: `Custom OAuth: ${ name }`, + i18nLabel: 'Accounts_OAuth_Custom_Roles_To_Sync', + i18nDescription: 'Accounts_OAuth_Custom_Roles_To_Sync_Description', + enterprise: true, + enableQuery: { + _id: `Accounts_OAuth_Custom-${ name }-merge_roles`, + value: true, + }, + invalidValue: '', + modules: ['oauth-enterprise'] }); settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-merge_users`, values.mergeUsers || false, { type: 'boolean', group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Merge_Users', persistent: true }); settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-show_button` , values.showButton || true , { type: 'boolean', group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Show_Button_On_Login_Page', persistent: true }); settingsRegistry.add(`Accounts_OAuth_Custom-${ name }-groups_channel_map` , values.channelsMap || '{\n\t"rocket-admin": "admin",\n\t"tech-support": "support"\n}' , { type: 'code' , multiline: true, code: 'application/json', group: 'OAuth', section: `Custom OAuth: ${ name }`, i18nLabel: 'Accounts_OAuth_Custom_Channel_Map', persistent: true }); diff --git a/app/lib/server/methods/removeOAuthService.ts b/app/lib/server/methods/removeOAuthService.ts index 6b3f1ff535aec..4aecbcdc53df1 100644 --- a/app/lib/server/methods/removeOAuthService.ts +++ b/app/lib/server/methods/removeOAuthService.ts @@ -43,6 +43,7 @@ Meteor.methods({ Settings.removeById(`Accounts_OAuth_Custom-${ name }-avatar_field`), Settings.removeById(`Accounts_OAuth_Custom-${ name }-roles_claim`), Settings.removeById(`Accounts_OAuth_Custom-${ name }-merge_roles`), + Settings.removeById(`Accounts_OAuth_Custom-${ name }-roles_to_sync`), Settings.removeById(`Accounts_OAuth_Custom-${ name }-merge_users`), Settings.removeById(`Accounts_OAuth_Custom-${ name }-show_button`), Settings.removeById(`Accounts_OAuth_Custom-${ name }-groups_claim`), diff --git a/app/lib/server/startup/oAuthServicesUpdate.js b/app/lib/server/startup/oAuthServicesUpdate.js index f3206824e2771..c0a4d6f46b588 100644 --- a/app/lib/server/startup/oAuthServicesUpdate.js +++ b/app/lib/server/startup/oAuthServicesUpdate.js @@ -55,6 +55,7 @@ function _OAuthServicesUpdate() { data.mergeUsers = settings.get(`${ key }-merge_users`); data.mapChannels = settings.get(`${ key }-map_channels`); data.mergeRoles = settings.get(`${ key }-merge_roles`); + data.rolesToSync = settings.get(`${ key }-roles_to_sync`); data.showButton = settings.get(`${ key }-show_button`); new CustomOAuth(serviceName.toLowerCase(), { @@ -78,6 +79,7 @@ function _OAuthServicesUpdate() { channelsAdmin: data.channelsAdmin, mergeUsers: data.mergeUsers, mergeRoles: data.mergeRoles, + rolesToSync: data.rolesToSync, accessTokenParam: data.accessTokenParam, showButton: data.showButton, }); @@ -184,6 +186,7 @@ function customOAuthServicesInit() { mergeUsers: process.env[`${ serviceKey }_merge_users`] === 'true', mapChannels: process.env[`${ serviceKey }_map_channels`], mergeRoles: process.env[`${ serviceKey }_merge_roles`] === 'true', + rolesToSync: process.env[`${ serviceKey }_roles_to_sync`], showButton: process.env[`${ serviceKey }_show_button`] === 'true', avatarField: process.env[`${ serviceKey }_avatar_field`], }; diff --git a/app/statistics/server/lib/getServicesStatistics.ts b/app/statistics/server/lib/getServicesStatistics.ts index faf9a07928c36..0eb444940b3ad 100644 --- a/app/statistics/server/lib/getServicesStatistics.ts +++ b/app/statistics/server/lib/getServicesStatistics.ts @@ -11,7 +11,7 @@ function getCustomOAuthServices(): Record(`Accounts_OAuth_Custom-${ name }-merge_roles`), users: Users.countActiveUsersByService(name), }]; })); diff --git a/ee/server/configuration/oauth.ts b/ee/server/configuration/oauth.ts index 438393280891b..92c15da8aa551 100644 --- a/ee/server/configuration/oauth.ts +++ b/ee/server/configuration/oauth.ts @@ -21,6 +21,7 @@ interface IOAuthUserIdentity { interface IOAuthSettings { mapChannels: string; mergeRoles: string; + rolesToSync: string; rolesClaim: string; groupsClaim: string; channelsAdmin: string; @@ -33,6 +34,7 @@ function getOAuthSettings(serviceName: string): IOAuthSettings { return { mapChannels: settings.get(`Accounts_OAuth_Custom-${ serviceName }-map_channels`) as string, mergeRoles: settings.get(`Accounts_OAuth_Custom-${ serviceName }-merge_roles`) as string, + rolesToSync: settings.get(`Accounts_OAuth_Custom-${ serviceName }-roles_to_sync`) as string, rolesClaim: settings.get(`Accounts_OAuth_Custom-${ serviceName }-roles_claim`) as string, groupsClaim: settings.get(`Accounts_OAuth_Custom-${ serviceName }-groups_claim`) as string, channelsAdmin: settings.get(`Accounts_OAuth_Custom-${ serviceName }-channels_admin`) as string, @@ -61,7 +63,7 @@ onLicense('oauth-enterprise', () => { } if (settings.mergeRoles) { - OAuthEEManager.updateRolesFromSSO(auth.user, auth.serviceData, settings.rolesClaim); + OAuthEEManager.updateRolesFromSSO(auth.user, auth.serviceData, settings.rolesClaim, settings.rolesToSync.split(',').map((role) => role.trim())); } }); diff --git a/ee/server/lib/oauth/Manager.ts b/ee/server/lib/oauth/Manager.ts index bce7a3e34b7e9..b53b1de90db53 100644 --- a/ee/server/lib/oauth/Manager.ts +++ b/ee/server/lib/oauth/Manager.ts @@ -35,7 +35,7 @@ export class OAuthEEManager { } } - static updateRolesFromSSO(user: Record, identity: Record, roleClaimName: string): void { + static updateRolesFromSSO(user: Record, identity: Record, roleClaimName: string, rolesToSync: string[]): void { if (user && identity && roleClaimName) { const rolesFromSSO = this.mapRolesFromSSO(identity, roleClaimName); @@ -43,19 +43,15 @@ export class OAuthEEManager { user.roles = []; } - const toRemove = user.roles.filter((val: any) => !rolesFromSSO.includes(val)); + const toRemove = user.roles.filter((val: any) => !rolesFromSSO.includes(val) && rolesToSync.includes(val)); - // loop through roles that user has that sso doesnt have and remove each one - toRemove.forEach(function(role: any) { - removeUserFromRoles(user._id, role); - }); + // remove all roles that the user has, but sso doesnt + removeUserFromRoles(user._id, toRemove); - const toAdd = rolesFromSSO.filter((val: any) => !user.roles.includes(val)); + const toAdd = rolesFromSSO.filter((val: any) => !user.roles.includes(val) && (!rolesToSync.length || rolesToSync.includes(val))); - // loop through sso roles and add the new ones - toAdd.forEach(function(role: any) { - addUserRoles(user._id, role); - }); + // add all roles that sso has, but the user doesnt + addUserRoles(user._id, toAdd); } } diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 32c282b5728a7..4d88b97e53fb2 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -114,6 +114,8 @@ "Accounts_OAuth_Custom_Merge_Users": "Merge users", "Accounts_OAuth_Custom_Name_Field": "Name field", "Accounts_OAuth_Custom_Roles_Claim": "Roles/Groups field name", + "Accounts_OAuth_Custom_Roles_To_Sync": "Roles to Sync", + "Accounts_OAuth_Custom_Roles_To_Sync_Description": "OAuth Roles to sync on user login and creation (comma-separated).", "Accounts_OAuth_Custom_Scope": "Scope", "Accounts_OAuth_Custom_Secret": "Secret", "Accounts_OAuth_Custom_Show_Button_On_Login_Page": "Show Button on Login Page", diff --git a/server/startup/migrations/index.ts b/server/startup/migrations/index.ts index e8159aad4db8f..319024966076c 100644 --- a/server/startup/migrations/index.ts +++ b/server/startup/migrations/index.ts @@ -70,4 +70,5 @@ import './v243'; import './v244'; import './v245'; import './v246'; +import './v247'; import './xrun'; diff --git a/server/startup/migrations/v247.ts b/server/startup/migrations/v247.ts new file mode 100644 index 0000000000000..29d67ae6413da --- /dev/null +++ b/server/startup/migrations/v247.ts @@ -0,0 +1,27 @@ +import { settings, settingsRegistry } from '../../../app/settings/server'; +import { addMigration } from '../../lib/migrations'; + +addMigration({ + version: 247, + up() { + const customOauthServices = settings.getByRegexp(/Accounts_OAuth_Custom-[^-]+$/mi); + const serviceNames = customOauthServices.map(([key]) => key.replace('Accounts_OAuth_Custom-', '')); + + serviceNames.forEach((serviceName) => { + settingsRegistry.add(`Accounts_OAuth_Custom-${ serviceName }-roles_to_sync`, '', { + type: 'string', + group: 'OAuth', + section: `Custom OAuth: ${ serviceName }`, + i18nLabel: 'Accounts_OAuth_Custom_Roles_To_Sync', + i18nDescription: 'Accounts_OAuth_Custom_Roles_To_Sync_Description', + enterprise: true, + enableQuery: { + _id: `Accounts_OAuth_Custom-${ serviceName }-merge_roles`, + value: true, + }, + invalidValue: '', + modules: ['oauth-enterprise'], + }); + }); + }, +}); From 7c8b4e5b116504a280d4acdb474791ec9f5fefce Mon Sep 17 00:00:00 2001 From: Tiago Evangelista Pinto Date: Mon, 22 Nov 2021 17:02:33 -0300 Subject: [PATCH 097/137] [FIX] New specific endpoint for contactChatHistoryMessages with right permissions (#23533) Co-authored-by: Kevin Aleman Co-authored-by: Guilherme Gazzo --- .../tabbar/contactChatHistoryMessages.html | 28 +++++--- .../app/tabbar/contactChatHistoryMessages.js | 24 +++++-- app/livechat/imports/server/rest/visitors.ts | 47 ++++++++++++ app/livechat/server/api.js | 1 + app/livechat/server/api/lib/visitors.js | 1 + app/models/server/raw/Messages.js | 13 ++++ .../Omnichannel/hooks/useAgentsList.ts | 2 +- .../Omnichannel/hooks/useDepartmentsList.ts | 2 +- .../views/admin/customEmoji/CustomEmoji.tsx | 2 +- definition/rest/helpers/PaginatedRequest.ts | 9 +-- definition/rest/helpers/PaginatedResult.ts | 4 +- definition/rest/v1/emojiCustom.ts | 6 +- definition/rest/v1/omnichannel.ts | 72 ++++++++++--------- .../server/api/departments.js | 10 +-- .../authorization/canAccessRoomLivechat.ts | 1 + 15 files changed, 153 insertions(+), 69 deletions(-) create mode 100644 app/livechat/imports/server/rest/visitors.ts diff --git a/app/livechat/client/views/app/tabbar/contactChatHistoryMessages.html b/app/livechat/client/views/app/tabbar/contactChatHistoryMessages.html index 1b5e7ec2b104e..7b83a9db8de7f 100644 --- a/app/livechat/client/views/app/tabbar/contactChatHistoryMessages.html +++ b/app/livechat/client/views/app/tabbar/contactChatHistoryMessages.html @@ -33,17 +33,25 @@

    {{_ "No_results_found"}}

    {{else}} -
    -
      - {{# with messageContext}} - {{#each msg in messages}}{{> message msg=msg room=room subscription=subscription settings=settings u=u}}{{/each}} - {{/with}} + {{#if hasError}} +
      +
      +

      {{_ "Not_found_or_not_allowed"}}

      +
      +
      + {{else}} +
      +
        + {{# with messageContext}} + {{#each msg in messages}}{{> message msg=msg room=room subscription=subscription settings=settings u=u}}{{/each}} + {{/with}} - {{#if isLoading}} - {{> loading}} - {{/if}} -
      -
      + {{#if isLoading}} + {{> loading}} + {{/if}} +
    +
    + {{/if}} {{/if}}
    diff --git a/app/livechat/client/views/app/tabbar/contactChatHistoryMessages.js b/app/livechat/client/views/app/tabbar/contactChatHistoryMessages.js index c73136c07a8f6..cb3171f67c65b 100644 --- a/app/livechat/client/views/app/tabbar/contactChatHistoryMessages.js +++ b/app/livechat/client/views/app/tabbar/contactChatHistoryMessages.js @@ -36,6 +36,12 @@ Template.contactChatHistoryMessages.helpers({ empty() { return Template.instance().messages.get().length === 0; }, + hasError() { + return Template.instance().hasError.get(); + }, + error() { + return Template.instance().error.get(); + }, }); Template.contactChatHistoryMessages.events({ @@ -72,15 +78,23 @@ Template.contactChatHistoryMessages.onCreated(function() { this.searchTerm = new ReactiveVar(''); this.isLoading = new ReactiveVar(true); this.limit = new ReactiveVar(MESSAGES_LIMIT); + this.hasError = new ReactiveVar(false); + this.error = new ReactiveVar(null); this.loadMessages = async (url) => { this.isLoading.set(true); const offset = this.offset.get(); - const { messages, total } = await APIClient.v1.get(url); - this.messages.set(offset === 0 ? messages : this.messages.get().concat(messages)); - this.hasMore.set(total > this.messages.get().length); - this.isLoading.set(false); + try { + const { messages, total } = await APIClient.v1.get(url); + this.messages.set(offset === 0 ? messages : this.messages.get().concat(messages)); + this.hasMore.set(total > this.messages.get().length); + } catch (e) { + this.hasError.set(true); + this.error.set(e); + } finally { + this.isLoading.set(false); + } }; this.autorun(() => { @@ -92,7 +106,7 @@ Template.contactChatHistoryMessages.onCreated(function() { return this.loadMessages(`chat.search/?roomId=${ this.rid }&searchText=${ searchTerm }&count=${ limit }&offset=${ offset }&sort={"ts": 1}`); } - this.loadMessages(`channels.messages/?roomId=${ this.rid }&count=${ limit }&offset=${ offset }&sort={"ts": 1}&query={"$or": [ {"t": {"$exists": false} }, {"t": "livechat-close"} ] }`); + this.loadMessages(`livechat/${ this.rid }/messages?count=${ limit }&offset=${ offset }&sort={"ts": 1}`); }); this.autorun(() => { diff --git a/app/livechat/imports/server/rest/visitors.ts b/app/livechat/imports/server/rest/visitors.ts new file mode 100644 index 0000000000000..e75d4a955a2ad --- /dev/null +++ b/app/livechat/imports/server/rest/visitors.ts @@ -0,0 +1,47 @@ + +import { check } from 'meteor/check'; + +import { API } from '../../../../api/server'; +import { LivechatRooms } from '../../../../models/server'; +import { Messages } from '../../../../models/server/raw'; +import { normalizeMessagesForUser } from '../../../../utils/server/lib/normalizeMessagesForUser'; +import { canAccessRoom } from '../../../../authorization/server'; +import { IMessage } from '../../../../../definition/IMessage'; + +API.v1.addRoute('livechat/:rid/messages', { authRequired: true, permissionsRequired: ['view-l-room'] }, { + async get() { + check(this.urlParams, { + rid: String, + }); + + const { offset, count } = this.getPaginationItems(); + const { sort } = this.parseJsonQuery(); + + const room = LivechatRooms.findOneById(this.urlParams.rid); + + if (!room) { + throw new Error('invalid-room'); + } + + if (!canAccessRoom(room, this.user)) { + throw new Error('not-allowed'); + } + + const cursor = Messages.findLivechatClosedMessages(this.urlParams.rid, { + sort: sort || { ts: -1 }, + skip: offset, + limit: count, + }); + + const total = await cursor.count(); + + const messages = await cursor.toArray() as IMessage[]; + + return API.v1.success({ + messages: normalizeMessagesForUser(messages, this.userId), + offset, + count, + total, + }); + }, +}); diff --git a/app/livechat/server/api.js b/app/livechat/server/api.js index 6a13dddc86bf8..7aa0ee39c4a37 100644 --- a/app/livechat/server/api.js +++ b/app/livechat/server/api.js @@ -11,6 +11,7 @@ import '../imports/server/rest/triggers.js'; import '../imports/server/rest/integrations.js'; import '../imports/server/rest/messages.js'; import '../imports/server/rest/visitors.js'; +import '../imports/server/rest/visitors.ts'; import '../imports/server/rest/dashboards.js'; import '../imports/server/rest/queue.js'; import '../imports/server/rest/officeHour.js'; diff --git a/app/livechat/server/api/lib/visitors.js b/app/livechat/server/api/lib/visitors.js index aec58e18db428..d03566d6da998 100644 --- a/app/livechat/server/api/lib/visitors.js +++ b/app/livechat/server/api/lib/visitors.js @@ -72,6 +72,7 @@ export async function findChatHistory({ userId, roomId, visitorId, pagination: { total, }; } + export async function searchChats({ userId, roomId, visitorId, searchText, closedChatsOnly, servedChatsOnly: served, pagination: { offset, count, sort } }) { if (!await hasPermissionAsync(userId, 'view-l-room')) { throw new Error('error-not-authorized'); diff --git a/app/models/server/raw/Messages.js b/app/models/server/raw/Messages.js index 06addaca1cdbc..f3704d6a53b79 100644 --- a/app/models/server/raw/Messages.js +++ b/app/models/server/raw/Messages.js @@ -184,4 +184,17 @@ export class MessagesRaw extends BaseRaw { } return this.col.aggregate(params).toArray(); } + + findLivechatClosedMessages(rid, options) { + return this.find( + { + rid, + $or: [ + { t: { $exists: false } }, + { t: 'livechat-close' }, + ], + }, + options, + ); + } } diff --git a/client/components/Omnichannel/hooks/useAgentsList.ts b/client/components/Omnichannel/hooks/useAgentsList.ts index 4b3b894ebc54f..5c01de2c89670 100644 --- a/client/components/Omnichannel/hooks/useAgentsList.ts +++ b/client/components/Omnichannel/hooks/useAgentsList.ts @@ -37,7 +37,7 @@ export const useAgentsList = ( ...(options.text && { text: options.text }), offset: start, count: end + start, - sort: JSON.stringify({ name: 1 }), + sort: `{ name: 1 }`, }); const items = agents.map((agent: any) => { diff --git a/client/components/Omnichannel/hooks/useDepartmentsList.ts b/client/components/Omnichannel/hooks/useDepartmentsList.ts index 5447c37d1877f..9b6b8e46a2556 100644 --- a/client/components/Omnichannel/hooks/useDepartmentsList.ts +++ b/client/components/Omnichannel/hooks/useDepartmentsList.ts @@ -40,7 +40,7 @@ export const useDepartmentsList = ( text: options.filter, offset: start, count: end + start, - sort: JSON.stringify({ name: 1 }), + sort: `{ name: 1 }`, }); const items = departments diff --git a/client/views/admin/customEmoji/CustomEmoji.tsx b/client/views/admin/customEmoji/CustomEmoji.tsx index 28db8f4b4cec9..c8b40a477d54b 100644 --- a/client/views/admin/customEmoji/CustomEmoji.tsx +++ b/client/views/admin/customEmoji/CustomEmoji.tsx @@ -42,7 +42,7 @@ const CustomEmoji: FC = function CustomEmoji({ onClick, reload useMemo( () => ({ query: JSON.stringify({ name: { $regex: text || '', $options: 'i' } }), - sort: JSON.stringify({ [sortBy]: sortDirection === 'asc' ? 1 : -1 }), + sort: `{ ${sortBy}: ${sortDirection === 'asc' ? 1 : -1} }`, count: itemsPerPage, offset: current, }), diff --git a/definition/rest/helpers/PaginatedRequest.ts b/definition/rest/helpers/PaginatedRequest.ts index 44962dea20628..edaa045a5590d 100644 --- a/definition/rest/helpers/PaginatedRequest.ts +++ b/definition/rest/helpers/PaginatedRequest.ts @@ -1,4 +1,5 @@ -export type PaginatedRequest = { - count: number; - offset: number; -}; +export type PaginatedRequest = { + count?: number; + offset?: number; + sort?: `{ ${S}: ${1 | -1} }` | string; +} & T; diff --git a/definition/rest/helpers/PaginatedResult.ts b/definition/rest/helpers/PaginatedResult.ts index e78980c0d1e70..ea153093cee2a 100644 --- a/definition/rest/helpers/PaginatedResult.ts +++ b/definition/rest/helpers/PaginatedResult.ts @@ -1,5 +1,5 @@ -export type PaginatedResult = { +export type PaginatedResult = { count: number; offset: number; total: number; -}; +} & T; diff --git a/definition/rest/v1/emojiCustom.ts b/definition/rest/v1/emojiCustom.ts index 89963edc4295a..8ef956e5acd1f 100644 --- a/definition/rest/v1/emojiCustom.ts +++ b/definition/rest/v1/emojiCustom.ts @@ -4,11 +4,7 @@ import { PaginatedResult } from '../helpers/PaginatedResult'; export type EmojiCustomEndpoints = { 'emoji-custom.all': { - GET: ( - params: { query: string } & PaginatedRequest & { - sort: string; // {name: 'asc' | 'desc';}>; - }, - ) => { + GET: (params: PaginatedRequest<{ query: string }, 'name'>) => { emojis: ICustomEmojiDescriptor[]; } & PaginatedResult; }; diff --git a/definition/rest/v1/omnichannel.ts b/definition/rest/v1/omnichannel.ts index a2d43915ab09b..1624a4be26a8a 100644 --- a/definition/rest/v1/omnichannel.ts +++ b/definition/rest/v1/omnichannel.ts @@ -1,8 +1,11 @@ import { ILivechatDepartment } from '../../ILivechatDepartment'; import { ILivechatMonitor } from '../../ILivechatMonitor'; import { ILivechatTag } from '../../ILivechatTag'; +import { IMessage } from '../../IMessage'; import { IOmnichannelRoom, IRoom } from '../../IRoom'; import { ISetting } from '../../ISetting'; +import { PaginatedRequest } from '../helpers/PaginatedRequest'; +import { PaginatedResult } from '../helpers/PaginatedResult'; export type OmnichannelEndpoints = { 'livechat/appearance': { @@ -23,55 +26,55 @@ export type OmnichannelEndpoints = { POST: (params: { roomId: IRoom['_id'] }) => void; }; 'livechat/monitors.list': { - GET: (params: { text: string; offset: number; count: number }) => { + GET: (params: PaginatedRequest<{ text: string }>) => PaginatedResult<{ monitors: ILivechatMonitor[]; - total: number; - }; + }>; }; 'livechat/tags.list': { - GET: (params: { text: string; offset: number; count: number }) => { + GET: (params: PaginatedRequest<{ text: string }, 'name'>) => PaginatedResult<{ tags: ILivechatTag[]; - total: number; - }; + }>; }; 'livechat/department': { - GET: (params: { - text: string; - offset?: number; - count?: number; - sort?: string; - onlyMyDepartments?: boolean; - }) => { + GET: ( + params: PaginatedRequest<{ + text: string; + onlyMyDepartments?: boolean; + }>, + ) => PaginatedResult<{ departments: ILivechatDepartment[]; - total: number; - }; + }>; }; 'livechat/department/:_id': { GET: () => { department: ILivechatDepartment; }; }; - 'livechat/departments.by-unit/:id': { - GET: (params: { text: string; offset: number; count: number }) => { + 'livechat/departments.available-by-unit/:id': { + GET: (params: PaginatedRequest<{ text: string }>) => PaginatedResult<{ departments: ILivechatDepartment[]; - total: number; - }; + }>; }; - 'livechat/departments.available-by-unit/:id': { - GET: (params: { text: string; offset: number; count: number }) => { + 'livechat/departments.by-unit/': { + GET: (params: PaginatedRequest<{ text: string }>) => PaginatedResult<{ departments: ILivechatDepartment[]; - total: number; - }; + }>; + }; + + 'livechat/departments.by-unit/:id': { + GET: (params: PaginatedRequest<{ text: string }>) => PaginatedResult<{ + departments: ILivechatDepartment[]; + }>; }; 'livechat/custom-fields': { - GET: () => { + GET: (params: PaginatedRequest<{ text: string }>) => PaginatedResult<{ customFields: [ { _id: string; label: string; }, ]; - }; + }>; }; 'livechat/rooms': { GET: (params: { @@ -86,15 +89,17 @@ export type OmnichannelEndpoints = { current: number; itemsPerPage: number; tags: string[]; - }) => { + }) => PaginatedResult<{ rooms: IOmnichannelRoom[]; - count: number; - offset: number; - total: number; - }; + }>; + }; + 'livechat/:rid/messages': { + GET: (params: PaginatedRequest<{ query: string }>) => PaginatedResult<{ + messages: IMessage[]; + }>; }; 'livechat/users/agent': { - GET: (params: { text?: string; offset?: number; count?: number; sort?: string }) => { + GET: (params: PaginatedRequest<{ text?: string }>) => PaginatedResult<{ users: { _id: string; emails: { @@ -109,9 +114,6 @@ export type OmnichannelEndpoints = { maxNumberSimultaneousChat: number; }; }[]; - count: number; - offset: number; - total: number; - }; + }>; }; }; diff --git a/ee/app/livechat-enterprise/server/api/departments.js b/ee/app/livechat-enterprise/server/api/departments.js index 49c9e840d3e9c..07c7c5a6c6887 100644 --- a/ee/app/livechat-enterprise/server/api/departments.js +++ b/ee/app/livechat-enterprise/server/api/departments.js @@ -344,15 +344,15 @@ API.v1.addRoute('livechat/departments.available-by-unit/:unitId', { authRequired }, }); -API.v1.addRoute('livechat/departments.by-unit/:unitId', { authRequired: true }, { - get() { +API.v1.addRoute('livechat/departments.by-unit/:id', { authRequired: true }, { + async get() { check(this.urlParams, { - unitId: String, + id: String, }); const { offset, count } = this.getPaginationItems(); - const { unitId } = this.urlParams; + const { id } = this.urlParams; - const { departments, total } = Promise.await(findAllDepartmentsByUnit(unitId, offset, count)); + const { departments, total } = await findAllDepartmentsByUnit(id, offset, count); return API.v1.success({ departments, diff --git a/server/services/authorization/canAccessRoomLivechat.ts b/server/services/authorization/canAccessRoomLivechat.ts index a0dd92acbb17f..0418d10562412 100644 --- a/server/services/authorization/canAccessRoomLivechat.ts +++ b/server/services/authorization/canAccessRoomLivechat.ts @@ -9,6 +9,7 @@ export const canAccessRoomLivechat: RoomAccessValidator = async (room, user, ext // room can be sent as `null` but in that case a `rid` is also sent on extraData // this is the case for file uploads const livechatRoom = room || (extraData?.rid && await Rooms.findOneById(extraData?.rid)); + if (livechatRoom?.t !== 'l') { return false; } From ecac14d35dae8b20f433f052838c19d52762a35e Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Mon, 22 Nov 2021 17:45:07 -0300 Subject: [PATCH 098/137] [IMPROVE] Improve the add user drop down for add a user in create channel modal for UserAutoCompleteMultiple (#23766) * Adding the new UI * Correcting * Adding the key * Adding the listenners * Lint * Lint * Lint * adding a eslint-disable * lint * lint Co-authored-by: Jean <45966964+Jeanstaquet@users.noreply.github.com> Co-authored-by: Jeanstaquet --- .../UserAutoCompleteMultiple.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultiple.js b/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultiple.js index 11c383e86f8f1..b34a35aed4de8 100644 --- a/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultiple.js +++ b/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultiple.js @@ -4,7 +4,6 @@ import React, { memo, useMemo, useState } from 'react'; import { useEndpointData } from '../../hooks/useEndpointData'; import UserAvatar from '../avatar/UserAvatar'; -import Avatar from './Avatar'; const query = (term = '') => ({ selector: JSON.stringify({ term }) }); @@ -40,8 +39,15 @@ const UserAutoCompleteMultiple = (props) => { )) } - renderItem={({ value, ...props }) => ( - )} options={options} /> From 3843e4b443790a53076fe7839d829f4a3d6dc340 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 22 Nov 2021 15:14:21 -0600 Subject: [PATCH 099/137] Chore: Bump livechat to 1.10 (#23768) --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 32f4df75ff890..b8b7fcb55dd69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5423,13 +5423,13 @@ "integrity": "sha512-XZi0UitfOnzQOUnk0flqyhqWtRqpoPToZzBvhzIla1r+7bLH7dBxjHlbGjT1EOQ1Xso0hGrbZKLxGnRmakJx8g==" }, "@rocket.chat/livechat": { - "version": "1.9.6", - "resolved": "https://registry.npmjs.org/@rocket.chat/livechat/-/livechat-1.9.6.tgz", - "integrity": "sha512-JKS07hypHjEwzcdvCwxPwq6mRkYL6Citotsra9iAPf0YXauZVTYTU6L4q4BWMssMv+jodCmQpM2bmiJKckSwWQ==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@rocket.chat/livechat/-/livechat-1.10.0.tgz", + "integrity": "sha512-pN3p20GoBeISigjNT+F3lmzA1XRhsADGNmgmOQavzzUWZl6borbcRQLoaqGKToGGnZNJRRRa/ArxCmwI9ylJMQ==", "dev": true, "requires": { "@kossnocorp/desvg": "^0.2.0", - "@rocket.chat/sdk": "^1.0.0-alpha.41", + "@rocket.chat/sdk": "^1.0.0-alpha.42", "@rocket.chat/ui-kit": "^0.14.1", "css-vars-ponyfill": "^2.3.2", "date-fns": "^2.15.0", @@ -21955,9 +21955,9 @@ "integrity": "sha512-a5bFyofd/BHCX52/8i8uJkjr9DYwXIPnM/plwI6W7ezItLGqzt7X2G2nXuYSfsIJdkwwj/g9DG1LkcGJI/dDoA==" }, "history": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/history/-/history-5.0.1.tgz", - "integrity": "sha512-5qC/tFUKfVci5kzgRxZxN5Mf1CV8NmJx9ByaPX0YTLx5Vz3Svh7NYp6eA4CpDq4iA9D0C1t8BNIfvQIrUI3mVw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/history/-/history-5.1.0.tgz", + "integrity": "sha512-zPuQgPacm2vH2xdORvGGz1wQMuHSIB56yNAy5FnLuwOwgSYyPKptJtcMm6Ev+hRGeS+GzhbmRacHzvlESbFwDg==", "dev": true, "requires": { "@babel/runtime": "^7.7.6" diff --git a/package.json b/package.json index 3fa78771ee1b6..f0b4a73e58e1d 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "@babel/preset-react": "^7.14.5", "@babel/register": "^7.14.5", "@rocket.chat/eslint-config": "^0.4.0", - "@rocket.chat/livechat": "^1.9.6", + "@rocket.chat/livechat": "^1.10.0", "@settlin/spacebars-loader": "^1.0.9", "@storybook/addon-essentials": "^6.3.12", "@storybook/addon-postcss": "^2.0.0", From 9305434ba138f6e9d06f42636a94d75733bbad7e Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 22 Nov 2021 15:21:54 -0600 Subject: [PATCH 100/137] Chore: add index on appId + associations for apps_persistence collection (#23675) --- app/models/server/models/apps-persistence-model.js | 2 +- server/startup/migrations/index.ts | 1 + server/startup/migrations/v248.ts | 12 ++++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 server/startup/migrations/v248.ts diff --git a/app/models/server/models/apps-persistence-model.js b/app/models/server/models/apps-persistence-model.js index da178a390c327..dd01197abbc94 100644 --- a/app/models/server/models/apps-persistence-model.js +++ b/app/models/server/models/apps-persistence-model.js @@ -4,7 +4,7 @@ export class AppsPersistenceModel extends Base { constructor() { super('apps_persistence'); - this.tryEnsureIndex({ appId: 1 }); + this.tryEnsureIndex({ appId: 1, associations: 1 }); } // Bypass trash collection diff --git a/server/startup/migrations/index.ts b/server/startup/migrations/index.ts index 319024966076c..7b7ccf8282baa 100644 --- a/server/startup/migrations/index.ts +++ b/server/startup/migrations/index.ts @@ -71,4 +71,5 @@ import './v244'; import './v245'; import './v246'; import './v247'; +import './v248'; import './xrun'; diff --git a/server/startup/migrations/v248.ts b/server/startup/migrations/v248.ts new file mode 100644 index 0000000000000..d8670ecd540c2 --- /dev/null +++ b/server/startup/migrations/v248.ts @@ -0,0 +1,12 @@ +import { addMigration } from '../../lib/migrations'; +import { Apps } from '../../../app/apps/server/orchestrator'; + +addMigration({ + version: 248, + up() { + // we now have a compound index on appId + associations + // so we can use the index prefix instead of a separate index on appId + Apps.initialize(); + return Apps._persistModel?.tryDropIndex({ appId: 1 }); + }, +}); From 0cf8e8b7e86caf72ac727052a318f0067f0031a2 Mon Sep 17 00:00:00 2001 From: Leonardo Ostjen Couto Date: Mon, 22 Nov 2021 18:25:10 -0300 Subject: [PATCH 101/137] [NEW] Rate limiting for user registering (#23732) * users.register setting + rate limiting * refact rate limiting strategy * remove empty line * method rate limiting + description + i18n * update + settings.watch on rest api * removed duplicated setting * fixed update setting value on rest api * refresh registerUser method on setting update + i18n * removed useless object * removed empty method * fixed exception in server launch * renamed setting * updated numRequestsAllowed in users.register * i18n update * lint * moved settings.watch to endpoint * created a new settings.watch for methods + improved rate limiter dictionary method --- app/api/server/api.js | 11 ++++++++++- app/api/server/v1/users.js | 13 ++++++++++++- app/lib/server/startup/settings.ts | 8 ++++++-- packages/rocketchat-i18n/i18n/en.i18n.json | 5 +++++ packages/rocketchat-i18n/i18n/pt-BR.i18n.json | 7 ++++++- server/methods/registerUser.js | 18 +++++++++++++++++- 6 files changed, 56 insertions(+), 6 deletions(-) diff --git a/app/api/server/api.js b/app/api/server/api.js index 01a71effe8a34..6a94630edd5cc 100644 --- a/app/api/server/api.js +++ b/app/api/server/api.js @@ -4,8 +4,8 @@ import { DDPCommon } from 'meteor/ddp-common'; import { DDP } from 'meteor/ddp'; import { Accounts } from 'meteor/accounts-base'; import { Restivus } from 'meteor/nimble:restivus'; -import { RateLimiter } from 'meteor/rate-limit'; import _ from 'underscore'; +import { RateLimiter } from 'meteor/rate-limit'; import { Logger } from '../../../server/lib/logger/Logger'; import { getRestPayload } from '../../../server/lib/logger/logPayloads'; @@ -447,6 +447,14 @@ export class APIClass extends Restivus { }); } + updateRateLimiterDictionaryForRoute(route, numRequestsAllowed, intervalTimeInMS) { + if (rateLimiterDictionary[route]) { + rateLimiterDictionary[route].options.numRequestsAllowed = numRequestsAllowed ?? rateLimiterDictionary[route].options.numRequestsAllowed; + rateLimiterDictionary[route].options.intervalTimeInMS = intervalTimeInMS ?? rateLimiterDictionary[route].options.intervalTimeInMS; + API.v1.reloadRoutesToRefreshRateLimiter(); + } + } + _initAuth() { const loginCompatibility = (bodyParams, request) => { // Grab the username or email that the user is logging in with @@ -771,6 +779,7 @@ settings.watch('API_Enable_Rate_Limiter_Limit_Calls_Default', (value) => { API.v1.reloadRoutesToRefreshRateLimiter(); }); + settings.watch('Prometheus_API_User_Agent', (value) => { prometheusAPIUserAgent = value; }); diff --git a/app/api/server/v1/users.js b/app/api/server/v1/users.js index 1f3f22e38e6ce..a6c514a7c8eca 100644 --- a/app/api/server/v1/users.js +++ b/app/api/server/v1/users.js @@ -27,6 +27,7 @@ import { setUserStatus } from '../../../../imports/users-presence/server/activeU import { resetTOTP } from '../../../2fa/server/functions/resetTOTP'; import { Team } from '../../../../server/sdk'; + API.v1.addRoute('users.create', { authRequired: true }, { post() { check(this.bodyParams, { @@ -283,7 +284,11 @@ API.v1.addRoute('users.list', { authRequired: true }, { }, }); -API.v1.addRoute('users.register', { authRequired: false }, { +API.v1.addRoute('users.register', { authRequired: false, + rateLimiterOptions: { + numRequestsAllowed: settings.get('Rate_Limiter_Limit_RegisterUser'), + intervalTimeInMS: settings.get('API_Enable_Rate_Limiter_Limit_Time_Default'), + } }, { post() { if (this.userId) { return API.v1.failure('Logged in users can not register again.'); @@ -944,3 +949,9 @@ API.v1.addRoute('users.logout', { authRequired: true }, { }); }, }); + +settings.watch('Rate_Limiter_Limit_RegisterUser', (value) => { + const userRegisterRoute = '/api/v1/users.registerpost'; + + API.v1.updateRateLimiterDictionaryForRoute(userRegisterRoute, value); +}); diff --git a/app/lib/server/startup/settings.ts b/app/lib/server/startup/settings.ts index b8c3309b5cf28..68e644eb120d2 100644 --- a/app/lib/server/startup/settings.ts +++ b/app/lib/server/startup/settings.ts @@ -2977,7 +2977,7 @@ settingsRegistry.addGroup('Setup_Wizard', function() { }); settingsRegistry.addGroup('Rate Limiter', function() { - this.section('DDP Rate Limiter', function() { + this.section('DDP_Rate_Limiter', function() { this.add('DDP_Rate_Limit_IP_Enabled', true, { type: 'boolean' }); this.add('DDP_Rate_Limit_IP_Requests_Allowed', 120000, { type: 'int', enableQuery: { _id: 'DDP_Rate_Limit_IP_Enabled', value: true } }); this.add('DDP_Rate_Limit_IP_Interval_Time', 60000, { type: 'int', enableQuery: { _id: 'DDP_Rate_Limit_IP_Enabled', value: true } }); @@ -2999,12 +2999,16 @@ settingsRegistry.addGroup('Rate Limiter', function() { this.add('DDP_Rate_Limit_Connection_By_Method_Interval_Time', 10000, { type: 'int', enableQuery: { _id: 'DDP_Rate_Limit_Connection_By_Method_Enabled', value: true } }); }); - this.section('API Rate Limiter', function() { + this.section('API_Rate_Limiter', function() { this.add('API_Enable_Rate_Limiter', true, { type: 'boolean' }); this.add('API_Enable_Rate_Limiter_Dev', true, { type: 'boolean', enableQuery: { _id: 'API_Enable_Rate_Limiter', value: true } }); this.add('API_Enable_Rate_Limiter_Limit_Calls_Default', 10, { type: 'int', enableQuery: { _id: 'API_Enable_Rate_Limiter', value: true } }); this.add('API_Enable_Rate_Limiter_Limit_Time_Default', 60000, { type: 'int', enableQuery: { _id: 'API_Enable_Rate_Limiter', value: true } }); }); + + this.section('Feature_Limiting', function() { + this.add('Rate_Limiter_Limit_RegisterUser', 1, { type: 'int', enableQuery: { _id: 'API_Enable_Rate_Limiter', value: true } }); + }); }); settingsRegistry.addGroup('Troubleshoot', function() { diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 4d88b97e53fb2..7e4aa128245d2 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -372,6 +372,8 @@ "API_Enable_Rate_Limiter_Dev": "Enable Rate Limiter in development", "API_Enable_Rate_Limiter_Dev_Description": "Should limit the amount of calls to the endpoints in the development environment?", "API_Enable_Rate_Limiter_Limit_Calls_Default": "Default number calls to the rate limiter", + "Rate_Limiter_Limit_RegisterUser": "Default number calls to the rate limiter for registering a user", + "Rate_Limiter_Limit_RegisterUser_Description": "Number of default calls for user registering endpoints(REST and real-time API's), allowed within the time range defined in the API Rate Limiter section.", "API_Enable_Rate_Limiter_Limit_Calls_Default_Description": "Number of default calls for each endpoint of the REST API, allowed within the time range defined below", "API_Enable_Rate_Limiter_Limit_Time_Default": "Default time limit for the rate limiter (in ms)", "API_Enable_Rate_Limiter_Limit_Time_Default_Description": "Default timeout to limit the number of calls at each endpoint of the REST API(in ms)", @@ -387,6 +389,7 @@ "API_Personal_Access_Tokens_Regenerate_Modal": "If you lost or forgot your token, you can regenerate it, but remember that all applications that use this token should be updated", "API_Personal_Access_Tokens_Remove_Modal": "Are you sure you wish to remove this personal access token?", "API_Personal_Access_Tokens_To_REST_API": "Personal access tokens to REST API", + "API_Rate_Limiter": "API Rate Limiter", "API_Shield_Types": "Shield Types", "API_Shield_Types_Description": "Types of shields to enable as a comma separated list, choose from `online`, `channel` or `*` for all", "API_Shield_user_require_auth": "Require authentication for users shields", @@ -1345,6 +1348,7 @@ "Days": "Days", "DB_Migration": "Database Migration", "DB_Migration_Date": "Database Migration Date", + "DDP_Rate_Limit": "DDP Rate Limit", "DDP_Rate_Limit_Connection_By_Method_Enabled": "Limit by Connection per Method: enabled", "DDP_Rate_Limit_Connection_By_Method_Interval_Time": "Limit by Connection per Method: interval time", "DDP_Rate_Limit_Connection_By_Method_Requests_Allowed": "Limit by Connection per Method: requests allowed", @@ -1845,6 +1849,7 @@ "Favorites": "Favorites", "Feature_depends_on_selected_call_provider_to_be_enabled_from_administration_settings": "This feature depends on the above selected call provider to be enabled from the administration settings.", "Feature_Depends_on_Livechat_Visitor_navigation_as_a_message_to_be_enabled": "This feature depends on \"Send Visitor Navigation History as a Message\" to be enabled.", + "Feature_Limiting": "Feature Limiting", "Features": "Features", "Features_Enabled": "Features Enabled", "Feature_Disabled": "Feature Disabled", diff --git a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json index 10a52bf43458b..fd7d9fadd791a 100644 --- a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json +++ b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json @@ -370,7 +370,9 @@ "API_Enable_Rate_Limiter_Dev": "Ativar limitador de taxa em desenvolvimento", "API_Enable_Rate_Limiter_Dev_Description": "Deve limitar a quantidade de chamadas para os endpoints no ambiente de desenvolvimento?", "API_Enable_Rate_Limiter_Limit_Calls_Default": "Número padrão de chamadas para o limitador de taxa", - "API_Enable_Rate_Limiter_Limit_Calls_Default_Description": "Número de chamadas padrão para cada endpoint da API REST, permitido dentro do intervalo de tempo definido abaixo", + "Rate_Limiter_Limit_RegisterUser": "Número de chamadas para as endpoint de registro de usuário", + "Rate_Limiter_Limit_RegisterUser_Description": "Número de chamadas padrão para as endpoints de registro de usuário(API REST e real-time), permitido dentro do intervalo de tempo definido abaixo", + "API_Enable_Rate_Limiter_Limit_Calls_Default_Description": "Número de chamadas padrão para cada endpoint da API REST, permitido dentro do intervalo de tempo definido na seção de limitação de taxa.", "API_Enable_Rate_Limiter_Limit_Time_Default": "Limite de tempo padrão para o limitador de taxa (em ms)", "API_Enable_Rate_Limiter_Limit_Time_Default_Description": "Tempo limite padrão para limitar o número de chamadas em cada endpoint da API REST (em ms)", "API_Enable_Shields": "Ativar Protetores", @@ -385,6 +387,7 @@ "API_Personal_Access_Tokens_Regenerate_Modal": "Se perdeu ou esqueceu o seu código, pode recuperá-lo, mas lembre-se de que todos os aplicativos que usam esse código devem ser atualizados", "API_Personal_Access_Tokens_Remove_Modal": "Tem certeza de que deseja remover este código de acesso pessoal?", "API_Personal_Access_Tokens_To_REST_API": "Código de acesso pessoal para API REST", + "API_Rate_Limiter": "Limitação de Taxa de API", "API_Shield_Types": "Tipos de escudo", "API_Shield_Types_Description": "Tipos de escudos para habilitar como uma lista separada por vírgulas, escolha entre `online`, `channel` ou `*` para todos", "API_Shield_user_require_auth": "Exigir autenticaçāo para escudos de usuários", @@ -1340,6 +1343,7 @@ "Days": "Dias", "DB_Migration": "Migração de Banco de Dados", "DB_Migration_Date": "Data da migração do banco de dados", + "DDP_Rate_Limit": "Limitação de Taxa de DDP", "DDP_Rate_Limit_Connection_By_Method_Enabled": "Limite por Conexão por Método: Habilitado", "DDP_Rate_Limit_Connection_By_Method_Interval_Time": "Limite por Conexão por Método: Intervalo de tempo", "DDP_Rate_Limit_Connection_By_Method_Requests_Allowed": "Limite por Conexão por Método: Solicitações permitidas", @@ -1838,6 +1842,7 @@ "Favorites": "Favoritos", "Feature_Depends_on_Livechat_Visitor_navigation_as_a_message_to_be_enabled": "Esse recurso depende de \"Enviar histórico de navegação do visitante como uma mensagem\" para ser ativado.", "Features": "Funcionalidades", + "Feature_Limiting": "Limitação de funcionalidades", "Features_Enabled": "Funcionalidades habilitadas", "Feature_Disabled": "Funcionalidade desabilitada", "Federation": "Federação", diff --git a/server/methods/registerUser.js b/server/methods/registerUser.js index 802f271efc408..5dd2675bc384a 100644 --- a/server/methods/registerUser.js +++ b/server/methods/registerUser.js @@ -2,10 +2,11 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; import { Accounts } from 'meteor/accounts-base'; import s from 'underscore.string'; +import { DDPRateLimiter } from 'meteor/ddp-rate-limiter'; import { Users } from '../../app/models'; import { settings } from '../../app/settings'; -import { validateEmailDomain, passwordPolicy } from '../../app/lib'; +import { validateEmailDomain, passwordPolicy, RateLimiter } from '../../app/lib'; import { validateInviteToken } from '../../app/invites/server/functions/validateInviteToken'; Meteor.methods({ @@ -97,3 +98,18 @@ Meteor.methods({ return userId; }, }); + +let registerUserRuleId = RateLimiter.limitMethod('registerUser', + settings.get('Rate_Limiter_Limit_RegisterUser'), + settings.get('API_Enable_Rate_Limiter_Limit_Time_Default'), { + userId() { return true; }, + }); + + +settings.watch('Rate_Limiter_Limit_RegisterUser', (value) => { + // remove old DDP rate limiter rule and create a new one with the updated setting value + DDPRateLimiter.removeRule(registerUserRuleId); + registerUserRuleId = RateLimiter.limitMethod('registerUser', value, settings.get('API_Enable_Rate_Limiter_Limit_Time_Default'), { + userId() { return true; }, + }); +}); From debcaf76d77bd1a0ed541677606d0edb9df18ba1 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 22 Nov 2021 21:18:19 -0300 Subject: [PATCH 102/137] [NEW] Enable LDAP manual sync to deployments without EE license (#23761) * [NEW] Enable LDAP manual sync to deployments without EE license * Small code refactor * Update api.ts * Create api.ts * Fix Review Co-authored-by: Guilherme Gazzo --- app/api/server/api.d.ts | 22 +++++++++++- app/api/server/api.js | 4 +-- client/lib/2fa/utils.ts | 11 +++--- .../admin/settings/groups/LDAPGroupPage.tsx | 13 +++---- ee/server/api/api.ts | 34 +++++++++++++++++++ ee/server/api/index.ts | 1 + ee/server/api/ldap.ts | 7 +--- 7 files changed, 71 insertions(+), 21 deletions(-) create mode 100644 ee/server/api/api.ts diff --git a/app/api/server/api.d.ts b/app/api/server/api.d.ts index 1edac24cae4cb..c1762747bb0af 100644 --- a/app/api/server/api.d.ts +++ b/app/api/server/api.d.ts @@ -1,5 +1,7 @@ import type { JoinPathPattern, Method, MethodOf, OperationParams, OperationResult, PathPattern, UrlParams } from '../../../definition/rest'; import type { IUser } from '../../../definition/IUser'; +import { IMethodConnection } from '../../../definition/IMethodThisType'; +import { ITwoFactorOptions } from '../../2fa/server/code'; type SuccessResult = { statusCode: 200; @@ -39,11 +41,27 @@ type UnauthorizedResult = { }; } +export type NonEnterpriseTwoFactorOptions = { + authRequired: true; + twoFactorRequiredNonEnterprise: true; + twoFactorRequired: true; + permissionsRequired?: string[]; + twoFactorOptions: ITwoFactorOptions; +} + type Options = { permissionsRequired?: string[]; - twoFactorOptions?: unknown; + twoFactorOptions?: ITwoFactorOptions; twoFactorRequired?: boolean; authRequired?: boolean; + twoFactorRequiredNonEnterprise?: true; +}; + +type Request = { + method: 'GET' | 'POST' | 'PUT' | 'DELETE'; + url: string; + headers: Record; + body: any; } type ActionThis = { @@ -93,6 +111,8 @@ type Operations }; declare class APIClass { + processTwoFactor({ userId, request, invocation, options, connection }: { userId: string; request: Request; invocation: {twoFactorChecked: boolean}; options?: Options; connection: IMethodConnection }): void; + addRoute< TSubPathPattern extends string >(subpath: TSubPathPattern, operations: Operations>): void; diff --git a/app/api/server/api.js b/app/api/server/api.js index 6a94630edd5cc..e22c3eae711ca 100644 --- a/app/api/server/api.js +++ b/app/api/server/api.js @@ -276,7 +276,7 @@ export class APIClass extends Restivus { const code = request.headers['x-2fa-code']; const method = request.headers['x-2fa-method']; - checkCodeForUser({ user: userId, code, method, options, connection }); + checkCodeForUser({ user: userId, code, method, options: options.twoFactorOptions, connection }); invocation.twoFactorChecked = true; } @@ -400,7 +400,7 @@ export class APIClass extends Restivus { Accounts._setAccountData(connection.id, 'loginToken', this.token); if (_options.twoFactorRequired) { - api.processTwoFactor({ userId: this.userId, request: this.request, invocation, options: _options.twoFactorOptions, connection }); + api.processTwoFactor({ userId: this.userId, request: this.request, invocation, options: _options, connection }); } result = DDP._CurrentInvocation.withValue(invocation, () => Promise.await(originalAction.apply(this))) || API.v1.success(); diff --git a/client/lib/2fa/utils.ts b/client/lib/2fa/utils.ts index 8331bcd0d4abe..148b10683c2db 100644 --- a/client/lib/2fa/utils.ts +++ b/client/lib/2fa/utils.ts @@ -3,13 +3,16 @@ import { Meteor } from 'meteor/meteor'; export const isTotpRequiredError = ( error: unknown, -): error is Meteor.Error & { error: 'totp-required' } => - (error as { error?: unknown } | undefined)?.error === 'totp-required'; +): error is Meteor.Error & ({ error: 'totp-required' } | { errorType: 'totp-required' }) => + typeof error === 'object' && + ((error as { error?: unknown } | undefined)?.error === 'totp-required' || + (error as { errorType?: unknown } | undefined)?.errorType === 'totp-required'); export const isTotpInvalidError = ( error: unknown, -): error is Meteor.Error & { error: 'totp-invalid' } => - (error as { error?: unknown } | undefined)?.error === 'totp-invalid'; +): error is Meteor.Error & ({ error: 'totp-invalid' } | { errorType: 'totp-invalid' }) => + (error as { error?: unknown } | undefined)?.error === 'totp-invalid' || + (error as { errorType?: unknown } | undefined)?.errorType === 'totp-invalid'; export const isLoginCancelledError = (error: unknown): error is Meteor.Error => error instanceof Meteor.Error && error.error === Accounts.LoginCancelledError.numericError; diff --git a/client/views/admin/settings/groups/LDAPGroupPage.tsx b/client/views/admin/settings/groups/LDAPGroupPage.tsx index f466c56d3d17c..94cc03e0e8ff4 100644 --- a/client/views/admin/settings/groups/LDAPGroupPage.tsx +++ b/client/views/admin/settings/groups/LDAPGroupPage.tsx @@ -19,7 +19,6 @@ function LDAPGroupPage({ _id, ...group }: ISetting): JSX.Element { const syncNow = useEndpoint('POST', 'ldap.syncNow'); const testSearch = useEndpoint('POST', 'ldap.testSearch'); const ldapEnabled = useSetting('LDAP_Enable'); - const ldapSyncEnabled = useSetting('LDAP_Background_Sync') && ldapEnabled; const setModal = useSetModal(); const closeModal = useMutableCallback(() => setModal()); @@ -135,13 +134,11 @@ function LDAPGroupPage({ _id, ...group }: ISetting): JSX.Element { disabled={!ldapEnabled || changed} onClick={handleSearchTestButtonClick} /> - {ldapSyncEnabled && ( - diff --git a/ee/server/api/api.ts b/ee/server/api/api.ts new file mode 100644 index 0000000000000..fd00bcc28c2e2 --- /dev/null +++ b/ee/server/api/api.ts @@ -0,0 +1,34 @@ +import { API, Options, NonEnterpriseTwoFactorOptions } from '../../../app/api/server/api'; +import { use } from '../../../app/settings/server/Middleware'; +import { isEnterprise } from '../../app/license/server/license'; + +// Overwrites two factor method to enforce 2FA check for enterprise APIs when +// no license was provided to prevent abuse on enterprise APIs. + +export const isNonEnterpriseTwoFactorOptions = (options?: Options): + options is NonEnterpriseTwoFactorOptions => !!options + && 'twoFactorRequiredNonEnterprise' in options + && Boolean(options.twoFactorRequiredNonEnterprise); + +API.v1.processTwoFactor = use(API.v1.processTwoFactor, function([params, ...context], next) { + if (isNonEnterpriseTwoFactorOptions(params.options) && !isEnterprise()) { + const options: NonEnterpriseTwoFactorOptions = { + + ...params.options, + twoFactorOptions: { + disableRememberMe: true, + requireSecondFactor: true, + disablePasswordFallback: false, + }, + twoFactorRequired: true, + authRequired: true, + }; + + return next({ + ...params, + options, + }, ...context); + } + + return next(params, ...context); +}); diff --git a/ee/server/api/index.ts b/ee/server/api/index.ts index a0007bcae7c64..e0ccd58c5f4f6 100644 --- a/ee/server/api/index.ts +++ b/ee/server/api/index.ts @@ -1,2 +1,3 @@ +import './api'; import './ldap'; import './licenses'; diff --git a/ee/server/api/ldap.ts b/ee/server/api/ldap.ts index c51d21e588a49..997397f82a8bf 100644 --- a/ee/server/api/ldap.ts +++ b/ee/server/api/ldap.ts @@ -2,9 +2,8 @@ import { hasRole } from '../../../app/authorization/server'; import { settings } from '../../../app/settings/server'; import { API } from '../../../app/api/server/api'; import { LDAPEE } from '../sdk'; -import { hasLicense } from '../../app/license/server/license'; -API.v1.addRoute('ldap.syncNow', { authRequired: true }, { +API.v1.addRoute('ldap.syncNow', { authRequired: true, twoFactorRequiredNonEnterprise: true }, { async post() { if (!this.userId) { throw new Error('error-invalid-user'); @@ -14,10 +13,6 @@ API.v1.addRoute('ldap.syncNow', { authRequired: true }, { throw new Error('error-not-authorized'); } - if (!hasLicense('ldap-enterprise')) { - throw new Error('error-not-authorized'); - } - if (settings.get('LDAP_Enable') !== true) { throw new Error('LDAP_disabled'); } From ed2b4f91236d057a1ac0e58bbb772bea8951e653 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Mon, 22 Nov 2021 21:51:32 -0300 Subject: [PATCH 103/137] Chore: Remove duplicated 'name' key from rate limiter logs (#23771) --- app/lib/server/startup/rateLimiter.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/lib/server/startup/rateLimiter.js b/app/lib/server/startup/rateLimiter.js index 464404b88048e..e10921f3e8dea 100644 --- a/app/lib/server/startup/rateLimiter.js +++ b/app/lib/server/startup/rateLimiter.js @@ -108,10 +108,9 @@ const checkNameForStream = (name) => name && !names.has(name) && name.startsWith const ruleIds = {}; -const callback = (message, name) => (reply, input) => { +const callback = (msg, name) => (reply, input) => { if (reply.allowed === false) { - logger.info('DDP RATE LIMIT:', message); - logger.info({ ...reply, ...input }); + logger.info({ msg, reply, input }); metrics.ddpRateLimitExceeded.inc({ limit_name: name, user_id: input.userId, From f9469fcb38dfe591d819472d05e8b27e1a3774ca Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Mon, 22 Nov 2021 21:53:05 -0300 Subject: [PATCH 104/137] Regression: Fix sendMessagesToAdmins not in Fiber (#23770) --- app/models/server/models/Users.js | 17 --------- app/models/server/raw/Users.js | 17 +++++++++ server/lib/sendMessagesToAdmins.d.ts | 8 ---- server/lib/sendMessagesToAdmins.js | 45 ---------------------- server/lib/sendMessagesToAdmins.ts | 57 ++++++++++++++++++++++++++++ server/services/nps/notification.ts | 2 +- 6 files changed, 75 insertions(+), 71 deletions(-) delete mode 100644 server/lib/sendMessagesToAdmins.d.ts delete mode 100644 server/lib/sendMessagesToAdmins.js create mode 100644 server/lib/sendMessagesToAdmins.ts diff --git a/app/models/server/models/Users.js b/app/models/server/models/Users.js index 6b755bd01a02d..f33198de0abe6 100644 --- a/app/models/server/models/Users.js +++ b/app/models/server/models/Users.js @@ -1436,23 +1436,6 @@ export class Users extends Base { return this.find(query).count() !== 0; } - addBannerById(_id, banner) { - const query = { - _id, - [`banners.${ banner.id }.read`]: { - $ne: true, - }, - }; - - const update = { - $set: { - [`banners.${ banner.id }`]: banner, - }, - }; - - return this.update(query, update); - } - setBannerReadById(_id, bannerId) { const update = { $set: { diff --git a/app/models/server/raw/Users.js b/app/models/server/raw/Users.js index 0d6ce77ed78ca..dc2cab7b6232b 100644 --- a/app/models/server/raw/Users.js +++ b/app/models/server/raw/Users.js @@ -751,4 +751,21 @@ export class UsersRaw extends BaseRaw { const found = await this.findOne(query, options); return !!found; } + + addBannerById(_id, banner) { + const query = { + _id, + [`banners.${ banner.id }.read`]: { + $ne: true, + }, + }; + + const update = { + $set: { + [`banners.${ banner.id }`]: banner, + }, + }; + + return this.updateOne(query, update); + } } diff --git a/server/lib/sendMessagesToAdmins.d.ts b/server/lib/sendMessagesToAdmins.d.ts deleted file mode 100644 index 6c418291f9c46..0000000000000 --- a/server/lib/sendMessagesToAdmins.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -type MessageToAdmin = { - fromId?: string; - checkFrom?: boolean; - msgs?: any[] | Function; - banners?: any[] | Function; -}; - -export declare function sendMessagesToAdmins(config: MessageToAdmin): void; diff --git a/server/lib/sendMessagesToAdmins.js b/server/lib/sendMessagesToAdmins.js deleted file mode 100644 index 08af72647e963..0000000000000 --- a/server/lib/sendMessagesToAdmins.js +++ /dev/null @@ -1,45 +0,0 @@ - -import { Meteor } from 'meteor/meteor'; - -import { SystemLogger } from './logger/system'; -import { Users } from '../../app/models/server'; -import { Roles } from '../../app/models/server/raw'; - -export async function sendMessagesToAdmins(message) { - const fromUser = message.checkFrom ? Users.findOneById(message.fromId, { fields: { _id: 1 } }) : true; - const users = await Roles.findUsersInRole('admin'); - - users.forEach((adminUser) => { - if (fromUser) { - try { - Meteor.runAsUser(message.fromId, () => Meteor.call('createDirectMessage', adminUser.username)); - - const rid = [adminUser._id, message.fromId].sort().join(''); - - if (typeof message.msgs === 'function') { - message.msgs = message.msgs({ adminUser }); - } - - if (!Array.isArray(message.msgs)) { - message.msgs = [message.msgs]; - } - - if (typeof message.banners === 'function') { - message.banners = message.banners({ adminUser }); - } - - if (!Array.isArray(message.banners)) { - message.banners = [message.banners]; - } - - Meteor.runAsUser(message.fromId, () => { - message.msgs.forEach((msg) => Meteor.call('sendMessage', Object.assign({ rid }, msg))); - }); - } catch (e) { - SystemLogger.error(e); - } - } - - message.banners.forEach((banner) => Users.addBannerById(adminUser._id, banner)); - }); -} diff --git a/server/lib/sendMessagesToAdmins.ts b/server/lib/sendMessagesToAdmins.ts new file mode 100644 index 0000000000000..eed6ad798a654 --- /dev/null +++ b/server/lib/sendMessagesToAdmins.ts @@ -0,0 +1,57 @@ +import { SystemLogger } from './logger/system'; +import { Roles, Users } from '../../app/models/server/raw'; +import { executeSendMessage } from '../../app/lib/server/methods/sendMessage'; +import { createDirectMessage } from '../methods/createDirectMessage'; +import { IUser } from '../../definition/IUser'; +import { IMessage } from '../../definition/IMessage'; + +type Banner = { + id: string; + priority: number; + title: string; + text: string; + textArguments?: string[]; + modifiers: string[]; + link: string; +}; + +const getData = (param: T[] | Function, adminUser: IUser): T[] => { + const result = typeof param === 'function' ? param({ adminUser }) : param; + + if (!Array.isArray(result)) { + return [result]; + } + + return result; +}; + +export async function sendMessagesToAdmins({ + fromId = 'rocket.cat', + checkFrom = true, + msgs = [], + banners = [], +}: { + fromId?: string; + checkFrom?: boolean; + msgs?: Partial[] | Function; + banners?: Banner[] | Function; +}): Promise { + const fromUser = checkFrom ? await Users.findOneById(fromId, { projection: { _id: 1 } }) : true; + + const users = await (await Roles.findUsersInRole('admin')).toArray(); + + for await (const adminUser of users) { + if (fromUser) { + try { + const { rid } = createDirectMessage([adminUser.username], fromId); + + getData>(msgs, adminUser) + .forEach((msg) => executeSendMessage(fromId, Object.assign({ rid }, msg))); + } catch (error) { + SystemLogger.error(error); + } + } + + await Promise.all(getData(banners, adminUser).map((banner) => Users.addBannerById(adminUser._id, banner))); + } +} diff --git a/server/services/nps/notification.ts b/server/services/nps/notification.ts index af48327810875..0c65301812a5c 100644 --- a/server/services/nps/notification.ts +++ b/server/services/nps/notification.ts @@ -6,7 +6,7 @@ import moment from 'moment'; import { settings } from '../../../app/settings/server'; import { IBanner, BannerPlatform } from '../../../definition/IBanner'; -import { sendMessagesToAdmins } from '../../lib/sendMessagesToAdmins.js'; +import { sendMessagesToAdmins } from '../../lib/sendMessagesToAdmins'; export const getBannerForAdmins = Meteor.bindEnvironment((expireAt: Date): Omit => { const lng = settings.get('Language') || 'en'; From 2f442805f34d18b40c2a9450770cae4eb143f715 Mon Sep 17 00:00:00 2001 From: Leonardo Ostjen Couto Date: Mon, 22 Nov 2021 22:30:20 -0300 Subject: [PATCH 105/137] [FIX] Registration not possible when any user is blocked for multiple failed logins (#23565) --- app/authentication/server/lib/restrictLoginAttempts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/authentication/server/lib/restrictLoginAttempts.ts b/app/authentication/server/lib/restrictLoginAttempts.ts index 6561f3d3f12c5..2b63656d87e13 100644 --- a/app/authentication/server/lib/restrictLoginAttempts.ts +++ b/app/authentication/server/lib/restrictLoginAttempts.ts @@ -90,7 +90,7 @@ export const isValidAttemptByUser = async (login: ILoginAttempt): Promise Date: Tue, 23 Nov 2021 00:12:19 -0300 Subject: [PATCH 106/137] Chore: Update settings.ts (#23769) Co-authored-by: Diego Sampaio --- app/lib/server/startup/settings.ts | 4 ---- server/startup/migrations/index.ts | 1 + server/startup/migrations/v249.ts | 16 ++++++++++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 server/startup/migrations/v249.ts diff --git a/app/lib/server/startup/settings.ts b/app/lib/server/startup/settings.ts index 68e644eb120d2..1562634a24ee9 100644 --- a/app/lib/server/startup/settings.ts +++ b/app/lib/server/startup/settings.ts @@ -1675,10 +1675,6 @@ settingsRegistry.addGroup('Setup_Wizard', function() { key: 'aerospaceDefense', i18nLabel: 'Aerospace_and_Defense', }, - { - key: 'blockchain', - i18nLabel: 'Blockchain', - }, { key: 'consulting', i18nLabel: 'Consulting', diff --git a/server/startup/migrations/index.ts b/server/startup/migrations/index.ts index 7b7ccf8282baa..c3b3ff9fca6c4 100644 --- a/server/startup/migrations/index.ts +++ b/server/startup/migrations/index.ts @@ -72,4 +72,5 @@ import './v245'; import './v246'; import './v247'; import './v248'; +import './v249'; import './xrun'; diff --git a/server/startup/migrations/v249.ts b/server/startup/migrations/v249.ts new file mode 100644 index 0000000000000..d0aa0df199458 --- /dev/null +++ b/server/startup/migrations/v249.ts @@ -0,0 +1,16 @@ +import { Settings } from '../../../app/models/server/raw'; +import { addMigration } from '../../lib/migrations'; + +addMigration({ + version: 249, + async up() { + await Settings.updateOne({ + _id: 'Industry', + value: 'blockchain', + }, { + $set: { + value: 'other', + }, + }); + }, +}); From bfa43b8b35838dde2a2f37c66d2795a79ce39563 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 23 Nov 2021 00:13:07 -0300 Subject: [PATCH 107/137] Bump version to 4.2.0-rc.0 --- .docker/Dockerfile.rhel | 2 +- .github/history.json | 548 +++++++++++++++++++++++++ .snapcraft/resources/prepareRocketChat | 2 +- .snapcraft/snap/snapcraft.yaml | 2 +- HISTORY.md | 262 +++++++++++- app/utils/rocketchat.info | 2 +- package-lock.json | 2 +- package.json | 2 +- 8 files changed, 815 insertions(+), 7 deletions(-) diff --git a/.docker/Dockerfile.rhel b/.docker/Dockerfile.rhel index 098af5ace225d..689338bf59fb6 100644 --- a/.docker/Dockerfile.rhel +++ b/.docker/Dockerfile.rhel @@ -1,6 +1,6 @@ FROM registry.access.redhat.com/ubi8/nodejs-12 -ENV RC_VERSION 4.2.0-develop +ENV RC_VERSION 4.2.0-rc.0 MAINTAINER buildmaster@rocket.chat diff --git a/.github/history.json b/.github/history.json index 5f3b0c6a39508..9520cb124e7e0 100644 --- a/.github/history.json +++ b/.github/history.json @@ -67142,6 +67142,554 @@ ] } ] + }, + "4.2.0-rc.0": { + "node_version": "12.22.1", + "npm_version": "6.14.1", + "apps_engine_version": "1.28.1", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "23769", + "title": "Chore: Update settings.ts", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo", + "web-flow", + "sampaiodiego" + ] + }, + { + "pr": "23565", + "title": "[FIX] Registration not possible when any user is blocked for multiple failed logins", + "userLogin": "ostjen", + "contributors": [ + "ostjen" + ] + }, + { + "pr": "23770", + "title": "Regression: Fix sendMessagesToAdmins not in Fiber", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "23771", + "title": "Chore: Remove duplicated 'name' key from rate limiter logs", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "23761", + "title": "[NEW] Enable LDAP manual sync to deployments without EE license", + "userLogin": "rodrigok", + "description": "Open the Enterprise LDAP API that executes background sync to be used without any Enterprise License and enforce 2FA requirements.", + "milestone": "4.2.0", + "contributors": [ + "rodrigok", + "ggazzo", + "web-flow" + ] + }, + { + "pr": "23732", + "title": "[NEW] Rate limiting for user registering", + "userLogin": "ostjen", + "milestone": "4.2.0", + "contributors": [ + "ostjen" + ] + }, + { + "pr": "23675", + "title": "Chore: add index on appId + associations for apps_persistence collection", + "userLogin": "KevLehman", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "23768", + "title": "Chore: Bump Rocket.Chat@livechat to 1.10", + "userLogin": "KevLehman", + "milestone": "4.2.0", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "23766", + "title": "[IMPROVE] Improve the add user drop down for add a user in create channel modal for UserAutoCompleteMultiple", + "userLogin": "dougfabris", + "description": "Seeing only the name of the person you are not adding is not practical in my opinion because two people can have the same name. Moreover, you can't see the username of the person you want to add in the dropdown. So I changed that and created another selection of users to show the username as well. I made this change so that it would appear in the key place for creating a room and adding a user.\r\n\r\nBefore:\r\n\r\nhttps://user-images.githubusercontent.com/45966964/115287805-faac8d00-a150-11eb-871f-147ab011ced0.mp4\r\n\r\n\r\nAfter:\r\n\r\nhttps://user-images.githubusercontent.com/45966964/115287664-d2249300-a150-11eb-8cf6-0e04730b425d.mp4", + "milestone": "4.2.0", + "contributors": [ + "Jeanstaquet", + "web-flow", + "dougfabris" + ] + }, + { + "pr": "23533", + "title": "[FIX] New specific endpoint for contactChatHistoryMessages with right permissions", + "userLogin": "tiagoevanp", + "description": "Anyone with 'View Omnichannel Rooms' permission can see the History Messages.", + "milestone": "4.2.0", + "contributors": [ + "tiagoevanp", + "web-flow", + "KevLehman", + "ggazzo" + ] + }, + { + "pr": "23588", + "title": "[FIX][ENTERPRISE] OAuth \"Merge Roles\" removes roles from users", + "userLogin": "matheusbsilva137", + "description": "- Fix OAuth \"Merge Roles\": the \"Merge Roles\" option now synchronize only the roles described in the \"**Roles to Sync**\" setting available in each Custom OAuth settings' group (instead of replacing users' roles by their OAuth roles);\r\n- Fix \"Merge Roles\" and \"Channel Mapping\" not being performed/updated on OAuth login.", + "contributors": [ + "matheusbsilva137", + "web-flow" + ] + }, + { + "pr": "23547", + "title": "[IMPROVE] Engagement Dashboard", + "userLogin": "tassoevan", + "description": "- Adds helpers `onToggledFeature` for server and client code to handle license activation/deactivation without server restart;\r\n- Replaces usage of `useEndpointData` with `useQuery` (from [React Query](https://react-query.tanstack.com/));\r\n- Introduces `view-engagement-dashboard` permission.", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "23004", + "title": "[NEW] Audio and Video calling in Livechat", + "userLogin": "murtaza98", + "contributors": [ + "dhruvjain99", + "murtaza98", + "Deepak-learner" + ] + }, + { + "pr": "23758", + "title": "Chore: Type omnichannel models", + "userLogin": "KevLehman", + "contributors": [ + "KevLehman", + "ggazzo" + ] + }, + { + "pr": "23737", + "title": "[NEW] Allow registering by REG_TOKEN environment variable", + "userLogin": "geekgonecrazy", + "description": "You can provide the REG_TOKEN environment variable containing a registration token and it will automatically register to your cloud account. This simplifies the registration flow", + "contributors": [ + "geekgonecrazy" + ] + }, + { + "pr": "23686", + "title": "[NEW] Permission for download/uploading files on mobile", + "userLogin": "ostjen", + "contributors": [ + "ostjen" + ] + }, + { + "pr": "23735", + "title": "[IMPROVE] Stricter API types", + "userLogin": "tassoevan", + "description": "It:\r\n- Adds stricter types for `API`;\r\n- Enables types for `urlParams`;\r\n- Removes mandatory passage of `undefined` payload on client;\r\n- Corrects some regressions;\r\n- Reassures my belief in TypeScript supremacy.", + "contributors": [ + "tassoevan", + "ggazzo" + ] + }, + { + "pr": "23757", + "title": "Regression: Units endpoint to TS", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "23750", + "title": "[NEW] REST endpoints to manage Omnichannel Business Units", + "userLogin": "KevLehman", + "description": "Basic documentation about endpoints can be found at https://www.postman.com/kaleman960/workspace/rocketchat-public-api/request/3865466-71502450-8c8f-42b4-8954-1cd3d01fcb0c", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "23738", + "title": "[FIX] Autofocus on search input in admin", + "userLogin": "gabriellsh", + "description": "Removed \"generic\" autofocus on sidenav template.", + "contributors": [ + "gabriellsh" + ] + }, + { + "pr": "23745", + "title": "Chore: Generic Table ", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "23739", + "title": "[FIX] Await promise to handle error when attempting to transfer a room", + "userLogin": "KevLehman", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "23673", + "title": "[FIX][ENTERPRISE] Private rooms and discussions can't be audited", + "userLogin": "matheusbsilva137", + "description": "- Add Private rooms (groups) and Discussions to the Message Auditing (Channels) autocomplete;\r\n- Update \"Channels\" tab name to \"Rooms\".", + "contributors": [ + "matheusbsilva137", + "gabriellsh" + ] + }, + { + "pr": "23734", + "title": "[FIX] Missing user roles in edit user tab", + "userLogin": "dougfabris", + "contributors": [ + "dougfabris" + ] + }, + { + "pr": "23733", + "title": "[FIX] Discussions created inside discussions", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "23694", + "title": "[NEW] Allow Omnichannel statistics to be collected.", + "userLogin": "cauefcr", + "description": "This PR adds the possibility for business stakeholders to see what is actually being used of the Omnichannel integrations.", + "contributors": [ + null, + "cauefcr", + "web-flow" + ] + }, + { + "pr": "23725", + "title": "[IMPROVE] Re-naming department query param for Twilio", + "userLogin": "murtaza98", + "description": "Since the endpoint supports both, department ID and department Name, so we're renaming it to reflect the same. `departmentName` -> `department`", + "contributors": [ + "murtaza98" + ] + }, + { + "pr": "23468", + "title": "[FIX] Fixed E2E default room settings not being honoured", + "userLogin": "ostjen", + "contributors": [ + "ostjen", + "TheDigitalEagle", + "web-flow", + "tassoevan" + ] + }, + { + "pr": "23659", + "title": "[FIX] broken avatar preview when changing avatar", + "userLogin": "Aman-Maheshwari", + "contributors": [ + "Aman-Maheshwari" + ] + }, + { + "pr": "23705", + "title": "[FIX] Prevent UserAction.addStream without Subscription", + "userLogin": "tiagoevanp", + "description": "When you take an Omnichannel chat from queue, the guest's typing information will appear.", + "contributors": [ + "ggazzo", + "tiagoevanp" + ] + }, + { + "pr": "23499", + "title": "[FIX] PhotoSwipe crashing on show", + "userLogin": "tassoevan", + "description": "Waits for initial content to load before showing it.", + "contributors": [ + "tassoevan", + "dougfabris" + ] + }, + { + "pr": "23695", + "title": "Chore: add `no-bidi` rule", + "userLogin": "KevLehman", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "23711", + "title": "[FIX] Fix typo in FR translation", + "userLogin": "Cormoran96", + "contributors": [ + "Cormoran96" + ] + }, + { + "pr": "23706", + "title": "Chore: Mocha testing configuration", + "userLogin": "tassoevan", + "description": "We've been writing integration tests for the REST API quite regularly, but we can't say the same for UI-related modules. This PR is based on the assumption that _improving the developer experience on writing tests_ would increase our coverage and promote the adoption even for newcomers.\r\n\r\nHere as summary of the proposal:\r\n\r\n- Change Mocha configuration files:\r\n - Add a base configuration (`.mocharc.base.json`);\r\n - Rename the configuration for REST API tests (`mocha_end_to_end.opts.js -> .mocharc.api.js`);\r\n - Add a configuration for client modules (`.mocharc.client.js`);\r\n - Enable ESLint for them.\r\n- Add a Mocha test command exclusive for client modules (`npm run testunit-client`);\r\n- Enable fast watch mode:\r\n - Configure `ts-node` to only transpile code (skip type checking);\r\n - Define a list of files to be watched.\r\n- Configure `mocha` environment on ESLint only for test files (required when using Mocha's globals);\r\n- Adopt Chai as our assertion library:\r\n - Unify the setup of Chai plugins (`chai-spies`, `chai-datetime`, `chai-dom`);\r\n - Replace `assert` with `chai`;\r\n - Replace `chai.expect` with `expect`.\r\n- Enable integration tests with React components:\r\n - Enable JSX support on our default Babel configuration;\r\n - Adopt [testing library](https://testing-library.com/).", + "contributors": [ + "tassoevan", + "KevLehman", + "ggazzo" + ] + }, + { + "pr": "23701", + "title": "Chore: Api definitions", + "userLogin": "ggazzo", + "contributors": [ + "tassoevan", + "ggazzo", + "web-flow" + ] + }, + { + "pr": "23703", + "title": "[FIX][ENTERPRISE] Replace all occurrences of a placeholder on string instead of just first one", + "userLogin": "KevLehman", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "23641", + "title": "[FIX] Omnichannel webhooks can't be saved", + "userLogin": "Aman-Maheshwari", + "contributors": [ + "Aman-Maheshwari" + ] + }, + { + "pr": "23595", + "title": "[FIX] Omnichannel business hours page breaking navigation", + "userLogin": "Aman-Maheshwari", + "contributors": [ + "Aman-Maheshwari", + "tiagoevanp", + "web-flow" + ] + }, + { + "pr": "23626", + "title": "[IMPROVE] Allow override of default department for SMS Livechat sessions", + "userLogin": "bhardwajaditya", + "contributors": [ + "bhardwajaditya" + ] + }, + { + "pr": "23691", + "title": "[FIX] Omnichannel contact center navigation", + "userLogin": "tiagoevanp", + "description": "Derives from: https://github.com/RocketChat/Rocket.Chat/pull/23656\r\n\r\nThis PR includes a different approach to solving navigation problems following the same code structure and UI definitions of other \"ActionButtons\" components in Sidebar.", + "contributors": [ + "tiagoevanp" + ] + }, + { + "pr": "23692", + "title": "Regression: Improve AggregationCursor types", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "23696", + "title": "Chore: Remove useCallbacks", + "userLogin": "tassoevan", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "23387", + "title": "[IMPROVE] Reduce complexity in some functions", + "userLogin": "tassoevan", + "description": "Overhauls all places where eslint's `complexity` rule is disabled.", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "23633", + "title": "Chore: Convert Fiber models to async Step 1", + "userLogin": "rodrigok", + "contributors": [ + "rodrigok", + "sampaiodiego" + ] + }, + { + "pr": "23389", + "title": "[NEW] Permissions for interacting with Omnichannel Contact Center", + "userLogin": "cauefcr", + "description": "Adds a new permission, one that allows for control over user access to Omnichannel Contact Center,", + "contributors": [ + null, + "cauefcr", + "web-flow" + ] + }, + { + "pr": "23587", + "title": "[FIX] Omnichannel status being changed on page refresh", + "userLogin": "KevLehman", + "milestone": "4.1.2", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "23661", + "title": "[FIX] Performance issues when running Omnichannel job queue dispatcher", + "userLogin": "renatobecker", + "milestone": "4.1.2", + "contributors": [ + "renatobecker" + ] + }, + { + "pr": "23608", + "title": "[FIX] Advanced LDAP Sync Features", + "userLogin": "pierre-lehnen-rc", + "milestone": "4.1.1", + "contributors": [ + "pierre-lehnen-rc", + "web-flow" + ] + }, + { + "pr": "23627", + "title": "[FIX] LDAP users not being re-activated on login", + "userLogin": "pierre-lehnen-rc", + "milestone": "4.1.1", + "contributors": [ + "pierre-lehnen-rc" + ] + }, + { + "pr": "23576", + "title": "[FIX] \"to users\" not working in export message", + "userLogin": "ostjen", + "contributors": [ + "ostjen", + "web-flow" + ] + }, + { + "pr": "23607", + "title": "[FIX] App update flow failing in HA setups", + "userLogin": "d-gubert", + "description": "The flow for app updates is broken in specific scenarios with HA setups. Here we change the method calls in the Apps-Engine to avoid race conditions", + "milestone": "4.1.1", + "contributors": [ + "d-gubert" + ] + }, + { + "pr": "23566", + "title": "[FIX] Apps scheduler \"losing\" jobs after server restart", + "userLogin": "d-gubert", + "description": "If a job is scheduled and the server restarted, said job won't be executed, giving the impression it's been lost.\r\n\r\nWhat happens is that the scheduler is only started when some app tries to schedule an app - if that happens, all jobs that are \"late\" will be executed; if that doesn't happen, no job will run.\r\n\r\nThis PR starts the apps scheduler right after all apps have been loaded", + "contributors": [ + "d-gubert" + ] + }, + { + "pr": "23603", + "title": "i18n: Language update from LingoHub 🤖 on 2021-11-01Z", + "userLogin": "lingohub[bot]", + "contributors": [ + null, + "sampaiodiego" + ] + }, + { + "pr": "23498", + "title": "[NEW] Show on-hold metrics on analytics pages and current chats", + "userLogin": "KevLehman", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "23452", + "title": "Chore: Rearrange module typings", + "userLogin": "tassoevan", + "description": "- Move all external module declarations (definitions and augmentations) to `/definition/externals`;\r\n- ~Symlink some modules on `/definition/externals` to `/ee/server/services/definition/externals`~ Share types with `/ee/server/services`;\r\n- Use TypeScript as server code entrypoint.", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "23487", + "title": "[FIX] Notifications are not being filtered", + "userLogin": "matheusbsilva137", + "description": "- Add a migration to update the `Accounts_Default_User_Preferences_pushNotifications` setting's value to the `Accounts_Default_User_Preferences_mobileNotifications` setting's value;\r\n - Remove the `Accounts_Default_User_Preferences_mobileNotifications` setting (replaced by `Accounts_Default_User_Preferences_pushNotifications`);\r\n - Rename 'mobileNotifications' user's preference to 'pushNotifications'.", + "milestone": "4.1.2", + "contributors": [ + "matheusbsilva137" + ] + }, + { + "pr": "23542", + "title": "[IMPROVE] MKP12 - New UI - Merge Apps and Marketplace Tabs and Content", + "userLogin": "rique223", + "description": "Merged the Marketplace and Apps page into a single page with a tabs component that changes between Markeplace and installed apps.\r\n![page merging](https://user-images.githubusercontent.com/43561537/138516558-f86d62e6-1a5c-4817-a229-a1b876323960.gif)", + "contributors": [ + "ggazzo", + "dougfabris" + ] + }, + { + "pr": "23586", + "title": "Merge master into develop & Set version to 4.2.0-develop", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego", + "web-flow" + ] + } + ] } } } \ No newline at end of file diff --git a/.snapcraft/resources/prepareRocketChat b/.snapcraft/resources/prepareRocketChat index 51dced777dab7..804bc1f1f9318 100755 --- a/.snapcraft/resources/prepareRocketChat +++ b/.snapcraft/resources/prepareRocketChat @@ -1,6 +1,6 @@ #!/bin/bash -curl -SLf "https://releases.rocket.chat/4.2.0-develop/download/" -o rocket.chat.tgz +curl -SLf "https://releases.rocket.chat/4.2.0-rc.0/download/" -o rocket.chat.tgz tar xf rocket.chat.tgz --strip 1 diff --git a/.snapcraft/snap/snapcraft.yaml b/.snapcraft/snap/snapcraft.yaml index bee2a499cbc4b..9c40d5bbcdb68 100644 --- a/.snapcraft/snap/snapcraft.yaml +++ b/.snapcraft/snap/snapcraft.yaml @@ -7,7 +7,7 @@ # 5. `snapcraft snap` name: rocketchat-server -version: 4.2.0-develop +version: 4.2.0-rc.0 summary: Rocket.Chat server description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ confinement: strict diff --git a/HISTORY.md b/HISTORY.md index 7ee96dd1faa2f..dd96ad8533d4f 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,4 +1,264 @@ +# 4.2.0 (Under Release Candidate Process) + +## 4.2.0-rc.0 +`2021-11-23 · 9 🎉 · 7 🚀 · 25 🐛 · 17 🔍 · 23 👩‍💻👨‍💻` + +### 🎉 New features + + +- Allow Omnichannel statistics to be collected. ([#23694](https://github.com/RocketChat/Rocket.Chat/pull/23694)) + + This PR adds the possibility for business stakeholders to see what is actually being used of the Omnichannel integrations. + +- Allow registering by REG_TOKEN environment variable ([#23737](https://github.com/RocketChat/Rocket.Chat/pull/23737)) + + You can provide the REG_TOKEN environment variable containing a registration token and it will automatically register to your cloud account. This simplifies the registration flow + +- Audio and Video calling in Livechat ([#23004](https://github.com/RocketChat/Rocket.Chat/pull/23004) by [@Deepak-learner](https://github.com/Deepak-learner) & [@dhruvjain99](https://github.com/dhruvjain99)) + +- Enable LDAP manual sync to deployments without EE license ([#23761](https://github.com/RocketChat/Rocket.Chat/pull/23761)) + + Open the Enterprise LDAP API that executes background sync to be used without any Enterprise License and enforce 2FA requirements. + +- Permission for download/uploading files on mobile ([#23686](https://github.com/RocketChat/Rocket.Chat/pull/23686)) + +- Permissions for interacting with Omnichannel Contact Center ([#23389](https://github.com/RocketChat/Rocket.Chat/pull/23389)) + + Adds a new permission, one that allows for control over user access to Omnichannel Contact Center, + +- Rate limiting for user registering ([#23732](https://github.com/RocketChat/Rocket.Chat/pull/23732)) + +- REST endpoints to manage Omnichannel Business Units ([#23750](https://github.com/RocketChat/Rocket.Chat/pull/23750)) + + Basic documentation about endpoints can be found at https://www.postman.com/kaleman960/workspace/rocketchat-public-api/request/3865466-71502450-8c8f-42b4-8954-1cd3d01fcb0c + +- Show on-hold metrics on analytics pages and current chats ([#23498](https://github.com/RocketChat/Rocket.Chat/pull/23498)) + +### 🚀 Improvements + + +- Allow override of default department for SMS Livechat sessions ([#23626](https://github.com/RocketChat/Rocket.Chat/pull/23626) by [@bhardwajaditya](https://github.com/bhardwajaditya)) + +- Engagement Dashboard ([#23547](https://github.com/RocketChat/Rocket.Chat/pull/23547)) + + - Adds helpers `onToggledFeature` for server and client code to handle license activation/deactivation without server restart; + - Replaces usage of `useEndpointData` with `useQuery` (from [React Query](https://react-query.tanstack.com/)); + - Introduces `view-engagement-dashboard` permission. + +- Improve the add user drop down for add a user in create channel modal for UserAutoCompleteMultiple ([#23766](https://github.com/RocketChat/Rocket.Chat/pull/23766) by [@Jeanstaquet](https://github.com/Jeanstaquet)) + + Seeing only the name of the person you are not adding is not practical in my opinion because two people can have the same name. Moreover, you can't see the username of the person you want to add in the dropdown. So I changed that and created another selection of users to show the username as well. I made this change so that it would appear in the key place for creating a room and adding a user. + + Before: + + https://user-images.githubusercontent.com/45966964/115287805-faac8d00-a150-11eb-871f-147ab011ced0.mp4 + + + After: + + https://user-images.githubusercontent.com/45966964/115287664-d2249300-a150-11eb-8cf6-0e04730b425d.mp4 + +- MKP12 - New UI - Merge Apps and Marketplace Tabs and Content ([#23542](https://github.com/RocketChat/Rocket.Chat/pull/23542)) + + Merged the Marketplace and Apps page into a single page with a tabs component that changes between Markeplace and installed apps. + ![page merging](https://user-images.githubusercontent.com/43561537/138516558-f86d62e6-1a5c-4817-a229-a1b876323960.gif) + +- Re-naming department query param for Twilio ([#23725](https://github.com/RocketChat/Rocket.Chat/pull/23725)) + + Since the endpoint supports both, department ID and department Name, so we're renaming it to reflect the same. `departmentName` -> `department` + +- Reduce complexity in some functions ([#23387](https://github.com/RocketChat/Rocket.Chat/pull/23387)) + + Overhauls all places where eslint's `complexity` rule is disabled. + +- Stricter API types ([#23735](https://github.com/RocketChat/Rocket.Chat/pull/23735)) + + It: + - Adds stricter types for `API`; + - Enables types for `urlParams`; + - Removes mandatory passage of `undefined` payload on client; + - Corrects some regressions; + - Reassures my belief in TypeScript supremacy. + +### 🐛 Bug fixes + + +- "to users" not working in export message ([#23576](https://github.com/RocketChat/Rocket.Chat/pull/23576)) + +- **ENTERPRISE:** OAuth "Merge Roles" removes roles from users ([#23588](https://github.com/RocketChat/Rocket.Chat/pull/23588)) + + - Fix OAuth "Merge Roles": the "Merge Roles" option now synchronize only the roles described in the "**Roles to Sync**" setting available in each Custom OAuth settings' group (instead of replacing users' roles by their OAuth roles); + - Fix "Merge Roles" and "Channel Mapping" not being performed/updated on OAuth login. + +- **ENTERPRISE:** Private rooms and discussions can't be audited ([#23673](https://github.com/RocketChat/Rocket.Chat/pull/23673)) + + - Add Private rooms (groups) and Discussions to the Message Auditing (Channels) autocomplete; + - Update "Channels" tab name to "Rooms". + +- **ENTERPRISE:** Replace all occurrences of a placeholder on string instead of just first one ([#23703](https://github.com/RocketChat/Rocket.Chat/pull/23703)) + +- Advanced LDAP Sync Features ([#23608](https://github.com/RocketChat/Rocket.Chat/pull/23608)) + +- App update flow failing in HA setups ([#23607](https://github.com/RocketChat/Rocket.Chat/pull/23607)) + + The flow for app updates is broken in specific scenarios with HA setups. Here we change the method calls in the Apps-Engine to avoid race conditions + +- Apps scheduler "losing" jobs after server restart ([#23566](https://github.com/RocketChat/Rocket.Chat/pull/23566)) + + If a job is scheduled and the server restarted, said job won't be executed, giving the impression it's been lost. + + What happens is that the scheduler is only started when some app tries to schedule an app - if that happens, all jobs that are "late" will be executed; if that doesn't happen, no job will run. + + This PR starts the apps scheduler right after all apps have been loaded + +- Autofocus on search input in admin ([#23738](https://github.com/RocketChat/Rocket.Chat/pull/23738)) + + Removed "generic" autofocus on sidenav template. + +- Await promise to handle error when attempting to transfer a room ([#23739](https://github.com/RocketChat/Rocket.Chat/pull/23739)) + +- broken avatar preview when changing avatar ([#23659](https://github.com/RocketChat/Rocket.Chat/pull/23659) by [@Aman-Maheshwari](https://github.com/Aman-Maheshwari)) + +- Discussions created inside discussions ([#23733](https://github.com/RocketChat/Rocket.Chat/pull/23733)) + +- Fix typo in FR translation ([#23711](https://github.com/RocketChat/Rocket.Chat/pull/23711) by [@Cormoran96](https://github.com/Cormoran96)) + +- Fixed E2E default room settings not being honoured ([#23468](https://github.com/RocketChat/Rocket.Chat/pull/23468) by [@TheDigitalEagle](https://github.com/TheDigitalEagle)) + +- LDAP users not being re-activated on login ([#23627](https://github.com/RocketChat/Rocket.Chat/pull/23627)) + +- Missing user roles in edit user tab ([#23734](https://github.com/RocketChat/Rocket.Chat/pull/23734)) + +- New specific endpoint for contactChatHistoryMessages with right permissions ([#23533](https://github.com/RocketChat/Rocket.Chat/pull/23533)) + + Anyone with 'View Omnichannel Rooms' permission can see the History Messages. + +- Notifications are not being filtered ([#23487](https://github.com/RocketChat/Rocket.Chat/pull/23487)) + + - Add a migration to update the `Accounts_Default_User_Preferences_pushNotifications` setting's value to the `Accounts_Default_User_Preferences_mobileNotifications` setting's value; + - Remove the `Accounts_Default_User_Preferences_mobileNotifications` setting (replaced by `Accounts_Default_User_Preferences_pushNotifications`); + - Rename 'mobileNotifications' user's preference to 'pushNotifications'. + +- Omnichannel business hours page breaking navigation ([#23595](https://github.com/RocketChat/Rocket.Chat/pull/23595) by [@Aman-Maheshwari](https://github.com/Aman-Maheshwari)) + +- Omnichannel contact center navigation ([#23691](https://github.com/RocketChat/Rocket.Chat/pull/23691)) + + Derives from: https://github.com/RocketChat/Rocket.Chat/pull/23656 + + This PR includes a different approach to solving navigation problems following the same code structure and UI definitions of other "ActionButtons" components in Sidebar. + +- Omnichannel status being changed on page refresh ([#23587](https://github.com/RocketChat/Rocket.Chat/pull/23587)) + +- Omnichannel webhooks can't be saved ([#23641](https://github.com/RocketChat/Rocket.Chat/pull/23641) by [@Aman-Maheshwari](https://github.com/Aman-Maheshwari)) + +- Performance issues when running Omnichannel job queue dispatcher ([#23661](https://github.com/RocketChat/Rocket.Chat/pull/23661)) + +- PhotoSwipe crashing on show ([#23499](https://github.com/RocketChat/Rocket.Chat/pull/23499)) + + Waits for initial content to load before showing it. + +- Prevent UserAction.addStream without Subscription ([#23705](https://github.com/RocketChat/Rocket.Chat/pull/23705)) + + When you take an Omnichannel chat from queue, the guest's typing information will appear. + +- Registration not possible when any user is blocked for multiple failed logins ([#23565](https://github.com/RocketChat/Rocket.Chat/pull/23565)) + +
    +🔍 Minor changes + + +- Chore: add `no-bidi` rule ([#23695](https://github.com/RocketChat/Rocket.Chat/pull/23695)) + +- Chore: add index on appId + associations for apps_persistence collection ([#23675](https://github.com/RocketChat/Rocket.Chat/pull/23675)) + +- Chore: Api definitions ([#23701](https://github.com/RocketChat/Rocket.Chat/pull/23701)) + +- Chore: Bump Rocket.Chat@livechat to 1.10 ([#23768](https://github.com/RocketChat/Rocket.Chat/pull/23768)) + +- Chore: Convert Fiber models to async Step 1 ([#23633](https://github.com/RocketChat/Rocket.Chat/pull/23633)) + +- Chore: Generic Table ([#23745](https://github.com/RocketChat/Rocket.Chat/pull/23745)) + +- Chore: Mocha testing configuration ([#23706](https://github.com/RocketChat/Rocket.Chat/pull/23706)) + + We've been writing integration tests for the REST API quite regularly, but we can't say the same for UI-related modules. This PR is based on the assumption that _improving the developer experience on writing tests_ would increase our coverage and promote the adoption even for newcomers. + + Here as summary of the proposal: + + - Change Mocha configuration files: + - Add a base configuration (`.mocharc.base.json`); + - Rename the configuration for REST API tests (`mocha_end_to_end.opts.js -> .mocharc.api.js`); + - Add a configuration for client modules (`.mocharc.client.js`); + - Enable ESLint for them. + - Add a Mocha test command exclusive for client modules (`npm run testunit-client`); + - Enable fast watch mode: + - Configure `ts-node` to only transpile code (skip type checking); + - Define a list of files to be watched. + - Configure `mocha` environment on ESLint only for test files (required when using Mocha's globals); + - Adopt Chai as our assertion library: + - Unify the setup of Chai plugins (`chai-spies`, `chai-datetime`, `chai-dom`); + - Replace `assert` with `chai`; + - Replace `chai.expect` with `expect`. + - Enable integration tests with React components: + - Enable JSX support on our default Babel configuration; + - Adopt [testing library](https://testing-library.com/). + +- Chore: Rearrange module typings ([#23452](https://github.com/RocketChat/Rocket.Chat/pull/23452)) + + - Move all external module declarations (definitions and augmentations) to `/definition/externals`; + - ~Symlink some modules on `/definition/externals` to `/ee/server/services/definition/externals`~ Share types with `/ee/server/services`; + - Use TypeScript as server code entrypoint. + +- Chore: Remove duplicated 'name' key from rate limiter logs ([#23771](https://github.com/RocketChat/Rocket.Chat/pull/23771)) + +- Chore: Remove useCallbacks ([#23696](https://github.com/RocketChat/Rocket.Chat/pull/23696)) + +- Chore: Type omnichannel models ([#23758](https://github.com/RocketChat/Rocket.Chat/pull/23758)) + +- Chore: Update settings.ts ([#23769](https://github.com/RocketChat/Rocket.Chat/pull/23769)) + +- i18n: Language update from LingoHub 🤖 on 2021-11-01Z ([#23603](https://github.com/RocketChat/Rocket.Chat/pull/23603)) + +- Merge master into develop & Set version to 4.2.0-develop ([#23586](https://github.com/RocketChat/Rocket.Chat/pull/23586)) + +- Regression: Units endpoint to TS ([#23757](https://github.com/RocketChat/Rocket.Chat/pull/23757)) + +- Regression: Fix sendMessagesToAdmins not in Fiber ([#23770](https://github.com/RocketChat/Rocket.Chat/pull/23770)) + +- Regression: Improve AggregationCursor types ([#23692](https://github.com/RocketChat/Rocket.Chat/pull/23692)) + +
    + +### 👩‍💻👨‍💻 Contributors 😍 + +- [@Aman-Maheshwari](https://github.com/Aman-Maheshwari) +- [@Cormoran96](https://github.com/Cormoran96) +- [@Deepak-learner](https://github.com/Deepak-learner) +- [@Jeanstaquet](https://github.com/Jeanstaquet) +- [@TheDigitalEagle](https://github.com/TheDigitalEagle) +- [@bhardwajaditya](https://github.com/bhardwajaditya) +- [@dhruvjain99](https://github.com/dhruvjain99) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@KevLehman](https://github.com/KevLehman) +- [@cauefcr](https://github.com/cauefcr) +- [@d-gubert](https://github.com/d-gubert) +- [@dougfabris](https://github.com/dougfabris) +- [@gabriellsh](https://github.com/gabriellsh) +- [@geekgonecrazy](https://github.com/geekgonecrazy) +- [@ggazzo](https://github.com/ggazzo) +- [@matheusbsilva137](https://github.com/matheusbsilva137) +- [@murtaza98](https://github.com/murtaza98) +- [@ostjen](https://github.com/ostjen) +- [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) +- [@renatobecker](https://github.com/renatobecker) +- [@rodrigok](https://github.com/rodrigok) +- [@sampaiodiego](https://github.com/sampaiodiego) +- [@tassoevan](https://github.com/tassoevan) +- [@tiagoevanp](https://github.com/tiagoevanp) + # 4.1.2 `2021-11-08 · 3 🐛 · 3 👩‍💻👨‍💻` @@ -6401,7 +6661,7 @@ - [@sampaiodiego](https://github.com/sampaiodiego) # 3.8.0 -`2020-11-13 · 14 🎉 · 4 🚀 · 40 🐛 · 54 🔍 · 30 👩‍💻👨‍💻` +`2020-11-14 · 14 🎉 · 4 🚀 · 40 🐛 · 54 🔍 · 30 👩‍💻👨‍💻` ### Engine versions - Node: `12.18.4` diff --git a/app/utils/rocketchat.info b/app/utils/rocketchat.info index 643b57be50040..ce95521600029 100644 --- a/app/utils/rocketchat.info +++ b/app/utils/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "4.2.0-develop" + "version": "4.2.0-rc.0" } diff --git a/package-lock.json b/package-lock.json index b8b7fcb55dd69..c1ba19d8f2c1a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "Rocket.Chat", - "version": "4.2.0-develop", + "version": "4.2.0-rc.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index f0b4a73e58e1d..78699baad6a0f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Rocket.Chat", "description": "The Ultimate Open Source WebChat Platform", - "version": "4.2.0-develop", + "version": "4.2.0-rc.0", "author": { "name": "Rocket.Chat", "url": "https://rocket.chat/" From f9b20702f774ca2365542a84697e3b609a5602eb Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 23 Nov 2021 12:25:16 -0300 Subject: [PATCH 108/137] Regression: Fix LDAP sync route (#23775) Co-authored-by: Diego Sampaio --- .mocharc.api.js | 1 + app/api/server/api.d.ts | 12 +++++++----- app/api/server/api.js | 7 ++++--- ee/server/api/api.ts | 4 ++-- ee/server/api/ldap.ts | 6 +++++- tests/end-to-end/api/26-LDAP.ts | 24 ++++++++++++++++++++++++ 6 files changed, 43 insertions(+), 11 deletions(-) create mode 100644 tests/end-to-end/api/26-LDAP.ts diff --git a/.mocharc.api.js b/.mocharc.api.js index b8a96c749c9fc..cef49fb74933a 100644 --- a/.mocharc.api.js +++ b/.mocharc.api.js @@ -11,6 +11,7 @@ module.exports = { file: 'tests/end-to-end/teardown.js', spec: [ 'tests/end-to-end/api/*.js', + 'tests/end-to-end/api/*.ts', 'tests/end-to-end/apps/*.js', ], }; diff --git a/app/api/server/api.d.ts b/app/api/server/api.d.ts index c1762747bb0af..b66da40e43947 100644 --- a/app/api/server/api.d.ts +++ b/app/api/server/api.d.ts @@ -43,7 +43,7 @@ type UnauthorizedResult = { export type NonEnterpriseTwoFactorOptions = { authRequired: true; - twoFactorRequiredNonEnterprise: true; + forceTwoFactorAuthenticationForNonEnterprise: true; twoFactorRequired: true; permissionsRequired?: string[]; twoFactorOptions: ITwoFactorOptions; @@ -51,11 +51,13 @@ export type NonEnterpriseTwoFactorOptions = { type Options = { permissionsRequired?: string[]; - twoFactorOptions?: ITwoFactorOptions; - twoFactorRequired?: boolean; authRequired?: boolean; - twoFactorRequiredNonEnterprise?: true; -}; + forceTwoFactorAuthenticationForNonEnterprise?: boolean; +} | { + authRequired: true; + twoFactorRequired: true; + twoFactorOptions?: ITwoFactorOptions; +} type Request = { method: 'GET' | 'POST' | 'PUT' | 'DELETE'; diff --git a/app/api/server/api.js b/app/api/server/api.js index e22c3eae711ca..43241eda28219 100644 --- a/app/api/server/api.js +++ b/app/api/server/api.js @@ -273,6 +273,9 @@ export class APIClass extends Restivus { } processTwoFactor({ userId, request, invocation, options, connection }) { + if (!options.twoFactorRequired) { + return; + } const code = request.headers['x-2fa-code']; const method = request.headers['x-2fa-method']; @@ -399,9 +402,7 @@ export class APIClass extends Restivus { }; Accounts._setAccountData(connection.id, 'loginToken', this.token); - if (_options.twoFactorRequired) { - api.processTwoFactor({ userId: this.userId, request: this.request, invocation, options: _options, connection }); - } + api.processTwoFactor({ userId: this.userId, request: this.request, invocation, options: _options, connection }); result = DDP._CurrentInvocation.withValue(invocation, () => Promise.await(originalAction.apply(this))) || API.v1.success(); diff --git a/ee/server/api/api.ts b/ee/server/api/api.ts index fd00bcc28c2e2..59ce2ae7db313 100644 --- a/ee/server/api/api.ts +++ b/ee/server/api/api.ts @@ -7,8 +7,8 @@ import { isEnterprise } from '../../app/license/server/license'; export const isNonEnterpriseTwoFactorOptions = (options?: Options): options is NonEnterpriseTwoFactorOptions => !!options - && 'twoFactorRequiredNonEnterprise' in options - && Boolean(options.twoFactorRequiredNonEnterprise); + && 'forceTwoFactorAuthenticationForNonEnterprise' in options + && Boolean(options.forceTwoFactorAuthenticationForNonEnterprise); API.v1.processTwoFactor = use(API.v1.processTwoFactor, function([params, ...context], next) { if (isNonEnterpriseTwoFactorOptions(params.options) && !isEnterprise()) { diff --git a/ee/server/api/ldap.ts b/ee/server/api/ldap.ts index 997397f82a8bf..0dd3d652857f0 100644 --- a/ee/server/api/ldap.ts +++ b/ee/server/api/ldap.ts @@ -3,7 +3,11 @@ import { settings } from '../../../app/settings/server'; import { API } from '../../../app/api/server/api'; import { LDAPEE } from '../sdk'; -API.v1.addRoute('ldap.syncNow', { authRequired: true, twoFactorRequiredNonEnterprise: true }, { +API.v1.addRoute('ldap.syncNow', { + authRequired: true, + forceTwoFactorAuthenticationForNonEnterprise: true, + twoFactorRequired: true, +}, { async post() { if (!this.userId) { throw new Error('error-invalid-user'); diff --git a/tests/end-to-end/api/26-LDAP.ts b/tests/end-to-end/api/26-LDAP.ts new file mode 100644 index 0000000000000..a8dd7fe18c55f --- /dev/null +++ b/tests/end-to-end/api/26-LDAP.ts @@ -0,0 +1,24 @@ +import { expect } from 'chai'; +import type { Response } from 'supertest'; + +import { getCredentials, api, request, credentials } from '../../data/api-data.js'; + +describe('LDAP', function() { + this.retries(0); + + before((done) => getCredentials(done)); + + describe('[/ldap.syncNow]', () => { + it('should throw an error containing totp-required error ', (done) => { + request.post(api('ldap.syncNow')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'totp-required'); + }) + .end(done); + }); + }); +}); From 194a600f31a1037716ac4de297cfff0b8a4f9942 Mon Sep 17 00:00:00 2001 From: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> Date: Tue, 23 Nov 2021 23:32:07 +0530 Subject: [PATCH 109/137] Regression: Fix incorrect API path for Livechat calls (#23778) --- client/startup/routes.ts | 2 +- client/views/meet/MeetPage.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/startup/routes.ts b/client/startup/routes.ts index 69096f1a1aac9..aa6e5dc8ea1b6 100644 --- a/client/startup/routes.ts +++ b/client/startup/routes.ts @@ -60,7 +60,7 @@ FlowRouter.route('/meet/:rid', { async action(_params, queryParams) { if (queryParams?.token !== undefined) { // visitor login - const visitor = await APIClient.v1.get(`/livechat/visitor/${queryParams?.token}`); + const visitor = await APIClient.v1.get(`livechat/visitor/${queryParams?.token}`); if (visitor?.visitor) { return appLayout.render({ component: MeetPage }); } diff --git a/client/views/meet/MeetPage.tsx b/client/views/meet/MeetPage.tsx index f0e570d3bf9a7..58d243cbb2d43 100644 --- a/client/views/meet/MeetPage.tsx +++ b/client/views/meet/MeetPage.tsx @@ -25,7 +25,7 @@ const MeetPage: FC = () => { const closeCallTab = (): void => window.close(); const setupCallForVisitor = useCallback(async () => { - const room = await APIClient.v1.get(`/livechat/room?token=${visitorToken}&rid=${roomId}`); + const room = await APIClient.v1.get(`livechat/room?token=${visitorToken}&rid=${roomId}`); if (room?.room?.v?.token === visitorToken) { setVisitorId(room.room.v._id); setVisitorName(room.room.fname); @@ -39,7 +39,7 @@ const MeetPage: FC = () => { }, [visitorToken, roomId]); const setupCallForAgent = useCallback(async () => { - const room = await APIClient.v1.get(`/rooms.info?roomId=${roomId}`); + const room = await APIClient.v1.get(`rooms.info?roomId=${roomId}`); if (room?.room?.servedBy?._id === Meteor.userId()) { setVisitorName(room.room.fname); room?.room?.responseBy?.username From 3d5bb9d87876da96a746e58efaabf1644dd6afd6 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 23 Nov 2021 19:12:00 -0300 Subject: [PATCH 110/137] Bump version to 4.2.0-rc.1 --- .docker/Dockerfile.rhel | 2 +- .github/history.json | 32 +++++++++ .snapcraft/resources/prepareRocketChat | 2 +- .snapcraft/snap/snapcraft.yaml | 2 +- HISTORY.md | 96 +++++++++++++++----------- app/utils/rocketchat.info | 2 +- package-lock.json | 2 +- package.json | 2 +- 8 files changed, 94 insertions(+), 46 deletions(-) diff --git a/.docker/Dockerfile.rhel b/.docker/Dockerfile.rhel index 689338bf59fb6..4433da8f7c970 100644 --- a/.docker/Dockerfile.rhel +++ b/.docker/Dockerfile.rhel @@ -1,6 +1,6 @@ FROM registry.access.redhat.com/ubi8/nodejs-12 -ENV RC_VERSION 4.2.0-rc.0 +ENV RC_VERSION 4.2.0-rc.1 MAINTAINER buildmaster@rocket.chat diff --git a/.github/history.json b/.github/history.json index 9520cb124e7e0..7480f42087028 100644 --- a/.github/history.json +++ b/.github/history.json @@ -67690,6 +67690,38 @@ ] } ] + }, + "4.2.0-rc.1": { + "node_version": "12.22.1", + "npm_version": "6.14.1", + "apps_engine_version": "1.28.1", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "23778", + "title": "Regression: Fix incorrect API path for livechat calls", + "userLogin": "murtaza98", + "milestone": "4.2.0", + "contributors": [ + "murtaza98" + ] + }, + { + "pr": "23775", + "title": "Regression: Fix LDAP sync route", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo", + "sampaiodiego" + ] + } + ] } } } \ No newline at end of file diff --git a/.snapcraft/resources/prepareRocketChat b/.snapcraft/resources/prepareRocketChat index 804bc1f1f9318..0ab53cbba6990 100755 --- a/.snapcraft/resources/prepareRocketChat +++ b/.snapcraft/resources/prepareRocketChat @@ -1,6 +1,6 @@ #!/bin/bash -curl -SLf "https://releases.rocket.chat/4.2.0-rc.0/download/" -o rocket.chat.tgz +curl -SLf "https://releases.rocket.chat/4.2.0-rc.1/download/" -o rocket.chat.tgz tar xf rocket.chat.tgz --strip 1 diff --git a/.snapcraft/snap/snapcraft.yaml b/.snapcraft/snap/snapcraft.yaml index 9c40d5bbcdb68..648395e717936 100644 --- a/.snapcraft/snap/snapcraft.yaml +++ b/.snapcraft/snap/snapcraft.yaml @@ -7,7 +7,7 @@ # 5. `snapcraft snap` name: rocketchat-server -version: 4.2.0-rc.0 +version: 4.2.0-rc.1 summary: Rocket.Chat server description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ confinement: strict diff --git a/HISTORY.md b/HISTORY.md index dd96ad8533d4f..d7798a1b77d47 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,25 @@ # 4.2.0 (Under Release Candidate Process) +## 4.2.0-rc.1 +`2021-11-23 · 2 🔍 · 3 👩‍💻👨‍💻` + +
    +🔍 Minor changes + + +- Regression: Fix incorrect API path for livechat calls ([#23778](https://github.com/RocketChat/Rocket.Chat/pull/23778)) + +- Regression: Fix LDAP sync route ([#23775](https://github.com/RocketChat/Rocket.Chat/pull/23775)) + +
    + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@ggazzo](https://github.com/ggazzo) +- [@murtaza98](https://github.com/murtaza98) +- [@sampaiodiego](https://github.com/sampaiodiego) + ## 4.2.0-rc.0 `2021-11-23 · 9 🎉 · 7 🚀 · 25 🐛 · 17 🔍 · 23 👩‍💻👨‍💻` @@ -332,7 +351,7 @@ ### 🚀 Improvements -- Add markdown to custom fields in user Info ([#20947](https://github.com/RocketChat/Rocket.Chat/pull/20947) by [@yash-rajpal](https://github.com/yash-rajpal)) +- Add markdown to custom fields in user Info ([#20947](https://github.com/RocketChat/Rocket.Chat/pull/20947)) Added markdown to custom fields to render links @@ -560,7 +579,6 @@ - [@cuonghuunguyen](https://github.com/cuonghuunguyen) - [@dependabot[bot]](https://github.com/dependabot[bot]) - [@wolbernd](https://github.com/wolbernd) -- [@yash-rajpal](https://github.com/yash-rajpal) ### 👩‍💻👨‍💻 Core Team 🤓 @@ -580,6 +598,7 @@ - [@tassoevan](https://github.com/tassoevan) - [@thassiov](https://github.com/thassiov) - [@tiagoevanp](https://github.com/tiagoevanp) +- [@yash-rajpal](https://github.com/yash-rajpal) # 4.0.5 `2021-10-25 · 1 🐛 · 1 🔍 · 2 👩‍💻👨‍💻` @@ -2777,15 +2796,15 @@ - **ENTERPRISE:** Omnichannel Monitors can't forward chats to departments that they are not supervising ([#22142](https://github.com/RocketChat/Rocket.Chat/pull/22142)) -- Adding Custom Fields to show on user info check ([#20955](https://github.com/RocketChat/Rocket.Chat/pull/20955) by [@yash-rajpal](https://github.com/yash-rajpal)) +- Adding Custom Fields to show on user info check ([#20955](https://github.com/RocketChat/Rocket.Chat/pull/20955)) The setting custom fields to show under user info was not being used when rendering fields in user info. This pr adds those checks and only renders the fields mentioned under in admin -> accounts -> Custom Fields to Show in User Info. -- Adding permission 'add-team-channel' for Team Channels Contextual bar ([#21591](https://github.com/RocketChat/Rocket.Chat/pull/21591) by [@yash-rajpal](https://github.com/yash-rajpal)) +- Adding permission 'add-team-channel' for Team Channels Contextual bar ([#21591](https://github.com/RocketChat/Rocket.Chat/pull/21591)) Added 'add-team-channel' permission to the 2 buttons in team channels contextual bar, for adding channels to teams. -- Adding retentionEnabledDefault check before showing warning message ([#20692](https://github.com/RocketChat/Rocket.Chat/pull/20692) by [@yash-rajpal](https://github.com/yash-rajpal)) +- Adding retentionEnabledDefault check before showing warning message ([#20692](https://github.com/RocketChat/Rocket.Chat/pull/20692)) Added check for retentionEnabledDefault before showing prune warning message. @@ -3047,7 +3066,7 @@ } ``` -- Visibility of burger menu on certain width ([#20736](https://github.com/RocketChat/Rocket.Chat/pull/20736) by [@yash-rajpal](https://github.com/yash-rajpal)) +- Visibility of burger menu on certain width ([#20736](https://github.com/RocketChat/Rocket.Chat/pull/20736)) Burger was not visible on a certain width, specifically between 600 to 780. if width is more than 780px sidebar is shown, if less than 600 then burger icon was shown. But it wasn't shown between 600px to 780 px. It was because for showing burger icon we were only checking for `isMobile` which is lenght only less than 600. So i added one more check for condition if length is less than 780 px. @@ -3230,7 +3249,6 @@ - [@siva2204](https://github.com/siva2204) - [@sumukhah](https://github.com/sumukhah) - [@umakantv](https://github.com/umakantv) -- [@yash-rajpal](https://github.com/yash-rajpal) ### 👩‍💻👨‍💻 Core Team 🤓 @@ -3251,6 +3269,7 @@ - [@tassoevan](https://github.com/tassoevan) - [@thassiov](https://github.com/thassiov) - [@tiagoevanp](https://github.com/tiagoevanp) +- [@yash-rajpal](https://github.com/yash-rajpal) # 3.14.5 `2021-06-06 · 1 🚀 · 1 🐛 · 1 👩‍💻👨‍💻` @@ -3600,7 +3619,7 @@ ![image](https://user-images.githubusercontent.com/17487063/113359447-2d1b5500-931e-11eb-81fa-86f60fcee3a9.png) -- Checking 'start-discussion' Permission for MessageBox Actions ([#21564](https://github.com/RocketChat/Rocket.Chat/pull/21564) by [@yash-rajpal](https://github.com/yash-rajpal)) +- Checking 'start-discussion' Permission for MessageBox Actions ([#21564](https://github.com/RocketChat/Rocket.Chat/pull/21564)) Permissions 'start-discussion-other-user' and 'start-discussion' are checked everywhere before letting anyone start any discussions, this permission check was missing for message box actions, so added it. @@ -3864,7 +3883,6 @@ - [@sauravjoshi23](https://github.com/sauravjoshi23) - [@sumukhah](https://github.com/sumukhah) - [@wolbernd](https://github.com/wolbernd) -- [@yash-rajpal](https://github.com/yash-rajpal) ### 👩‍💻👨‍💻 Core Team 🤓 @@ -3885,6 +3903,7 @@ - [@tassoevan](https://github.com/tassoevan) - [@thassiov](https://github.com/thassiov) - [@tiagoevanp](https://github.com/tiagoevanp) +- [@yash-rajpal](https://github.com/yash-rajpal) # 3.13.5 `2021-05-27 · 1 🐛 · 1 👩‍💻👨‍💻` @@ -4207,7 +4226,7 @@ - Add missing `unreads` field to `users.info` REST endpoint ([#20905](https://github.com/RocketChat/Rocket.Chat/pull/20905)) -- Added hideUnreadStatus check before showing unread messages on roomList ([#20867](https://github.com/RocketChat/Rocket.Chat/pull/20867) by [@yash-rajpal](https://github.com/yash-rajpal)) +- Added hideUnreadStatus check before showing unread messages on roomList ([#20867](https://github.com/RocketChat/Rocket.Chat/pull/20867)) Added hide unread counter check, if the show unread messages is turned off, now unread messages badge won't be shown to user. @@ -4330,7 +4349,7 @@ - Replace wrong field description on Room Information panel ([#21395](https://github.com/RocketChat/Rocket.Chat/pull/21395) by [@rafaelblink](https://github.com/rafaelblink)) -- Reply count of message is decreased after a message from thread is deleted ([#19977](https://github.com/RocketChat/Rocket.Chat/pull/19977) by [@yash-rajpal](https://github.com/yash-rajpal)) +- Reply count of message is decreased after a message from thread is deleted ([#19977](https://github.com/RocketChat/Rocket.Chat/pull/19977)) The reply count now is decreased if a message from a thread is deleted. @@ -4547,7 +4566,7 @@ - Regression: When only 'teams' type is provided, show only rooms with teamMain on `rooms.adminRooms` endpoint ([#21322](https://github.com/RocketChat/Rocket.Chat/pull/21322)) -- Release 3.13.0 ([#21437](https://github.com/RocketChat/Rocket.Chat/pull/21437) by [@PriyaBihani](https://github.com/PriyaBihani) & [@cuonghuunguyen](https://github.com/cuonghuunguyen) & [@fcecagno](https://github.com/fcecagno) & [@lucassartor](https://github.com/lucassartor) & [@shrinish123](https://github.com/shrinish123) & [@yash-rajpal](https://github.com/yash-rajpal)) +- Release 3.13.0 ([#21437](https://github.com/RocketChat/Rocket.Chat/pull/21437) by [@PriyaBihani](https://github.com/PriyaBihani) & [@cuonghuunguyen](https://github.com/cuonghuunguyen) & [@fcecagno](https://github.com/fcecagno) & [@lucassartor](https://github.com/lucassartor) & [@shrinish123](https://github.com/shrinish123)) - Update Apps-Engine version ([#21398](https://github.com/RocketChat/Rocket.Chat/pull/21398)) @@ -4575,7 +4594,6 @@ - [@shrinish123](https://github.com/shrinish123) - [@sumukhah](https://github.com/sumukhah) - [@vova-zush](https://github.com/vova-zush) -- [@yash-rajpal](https://github.com/yash-rajpal) ### 👩‍💻👨‍💻 Core Team 🤓 @@ -4596,6 +4614,7 @@ - [@sampaiodiego](https://github.com/sampaiodiego) - [@tassoevan](https://github.com/tassoevan) - [@tiagoevanp](https://github.com/tiagoevanp) +- [@yash-rajpal](https://github.com/yash-rajpal) # 3.12.7 `2021-05-27 · 1 🐛 · 1 👩‍💻👨‍💻` @@ -4671,7 +4690,7 @@ ### 🚀 Improvements -- Close Call contextual bar after starting jitsi call. ([#21004](https://github.com/RocketChat/Rocket.Chat/pull/21004) by [@yash-rajpal](https://github.com/yash-rajpal)) +- Close Call contextual bar after starting jitsi call. ([#21004](https://github.com/RocketChat/Rocket.Chat/pull/21004)) After jitsi call is started, if the call is started in a new window then we should close contextual tab bar. So, when 'YES' is pressed on modal, we call handleClose function if openNewWindow is true, as call doesn't starts on tab bar, it starts on new window. @@ -4681,19 +4700,16 @@ - Missing spaces on attachment ([#21020](https://github.com/RocketChat/Rocket.Chat/pull/21020)) -- Stopping Jitsi reload ([#20973](https://github.com/RocketChat/Rocket.Chat/pull/20973) by [@yash-rajpal](https://github.com/yash-rajpal)) +- Stopping Jitsi reload ([#20973](https://github.com/RocketChat/Rocket.Chat/pull/20973)) The Function where Jitsi call is started gets called many times due to `room.usernames` dep of useMemo, this dep triggers reloading of this function many times. So removing this dep from useMemo dependencies -### 👩‍💻👨‍💻 Contributors 😍 - -- [@yash-rajpal](https://github.com/yash-rajpal) - ### 👩‍💻👨‍💻 Core Team 🤓 - [@dougfabris](https://github.com/dougfabris) - [@tassoevan](https://github.com/tassoevan) +- [@yash-rajpal](https://github.com/yash-rajpal) # 3.12.0 `2021-02-28 · 5 🎉 · 17 🚀 · 74 🐛 · 30 🔍 · 29 👩‍💻👨‍💻` @@ -4742,15 +4758,15 @@ - Added auto-focus for better user-experience. ([#19954](https://github.com/RocketChat/Rocket.Chat/pull/19954) by [@Darshilp326](https://github.com/Darshilp326)) -- Added disable button check for send invite button ([#20337](https://github.com/RocketChat/Rocket.Chat/pull/20337) by [@yash-rajpal](https://github.com/yash-rajpal)) +- Added disable button check for send invite button ([#20337](https://github.com/RocketChat/Rocket.Chat/pull/20337)) Added Disable check for send invite button. If the text field is empty button would be disabled, and after any valid email is filled, button would get enabled -- Added key prop, removing unwanted warnings ([#20473](https://github.com/RocketChat/Rocket.Chat/pull/20473) by [@yash-rajpal](https://github.com/yash-rajpal)) +- Added key prop, removing unwanted warnings ([#20473](https://github.com/RocketChat/Rocket.Chat/pull/20473)) Removes warnings listed on the issue -- Added Markdown links to custom status. ([#20470](https://github.com/RocketChat/Rocket.Chat/pull/20470) by [@yash-rajpal](https://github.com/yash-rajpal)) +- Added Markdown links to custom status. ([#20470](https://github.com/RocketChat/Rocket.Chat/pull/20470)) Added markdown links to user's custom status. @@ -4776,7 +4792,7 @@ It brings more flexibility, allowing us to use different hooks and different components for each header -- Check Livechat message length through REST API endpoint ([#20366](https://github.com/RocketChat/Rocket.Chat/pull/20366) by [@yash-rajpal](https://github.com/yash-rajpal)) +- Check Livechat message length through REST API endpoint ([#20366](https://github.com/RocketChat/Rocket.Chat/pull/20366)) Added checks for message length for livechat message api, it shouldn't exceed specified character limit. @@ -4825,21 +4841,21 @@ Added tooltips to "Expand" and "Follow Message"/"Unfollow Message" in ThreadView for coherency. -- Added Bio Structure for UserCard, rendering Skeleton View on loading Instead of [Object][Object] ([#20305](https://github.com/RocketChat/Rocket.Chat/pull/20305) by [@yash-rajpal](https://github.com/yash-rajpal)) +- Added Bio Structure for UserCard, rendering Skeleton View on loading Instead of [Object][Object] ([#20305](https://github.com/RocketChat/Rocket.Chat/pull/20305)) Added Bio Structure for rendering Skeleton View on loading UserCard. -- Added check for view admin permission page ([#20403](https://github.com/RocketChat/Rocket.Chat/pull/20403) by [@yash-rajpal](https://github.com/yash-rajpal)) +- Added check for view admin permission page ([#20403](https://github.com/RocketChat/Rocket.Chat/pull/20403)) Admin Permission page was visible to all, if you add admin/permissions after the base url. This should not be visible to all user, only people with certain permissions should be able to see this page. I am also able to see permissions page for open workspace of Rocket chat. ![image](https://user-images.githubusercontent.com/58601732/105829728-bfd00880-5fea-11eb-9121-6c53a752f140.png) -- Adding the accidentally deleted tag template, used by other templates ([#20772](https://github.com/RocketChat/Rocket.Chat/pull/20772) by [@yash-rajpal](https://github.com/yash-rajpal)) +- Adding the accidentally deleted tag template, used by other templates ([#20772](https://github.com/RocketChat/Rocket.Chat/pull/20772)) Adding back accidentally deleted tag Template. -- Admin cannot clear user details like bio or nickname ([#20785](https://github.com/RocketChat/Rocket.Chat/pull/20785) by [@yash-rajpal](https://github.com/yash-rajpal)) +- Admin cannot clear user details like bio or nickname ([#20785](https://github.com/RocketChat/Rocket.Chat/pull/20785)) When the API users.update is called to update user data, it passes data to saveUser function. Here before saving data like bio or nickname we are checking if they are available or not. If data is available then we are saving it, but we are not doing anything when data isn't available. @@ -4847,13 +4863,13 @@ - Admin Panel pages not visible in Safari ([#20912](https://github.com/RocketChat/Rocket.Chat/pull/20912)) -- Announcement with multiple lines fixed. ([#20381](https://github.com/RocketChat/Rocket.Chat/pull/20381) by [@yash-rajpal](https://github.com/yash-rajpal)) +- Announcement with multiple lines fixed. ([#20381](https://github.com/RocketChat/Rocket.Chat/pull/20381)) Announcements with multiple lines used to break UI for announcements bar. Fixed it by replacing all break lines in announcement with empty space (" ") . The announcement modal would work as usual and show all break lines. - Atlassian Crowd login with 2FA enabled ([#20834](https://github.com/RocketChat/Rocket.Chat/pull/20834)) -- Attachment download from title fixed ([#20585](https://github.com/RocketChat/Rocket.Chat/pull/20585) by [@yash-rajpal](https://github.com/yash-rajpal)) +- Attachment download from title fixed ([#20585](https://github.com/RocketChat/Rocket.Chat/pull/20585)) Added target = '_self' to attachment link, this seems to fix the problem, without this attribute, error page is displayed. @@ -4944,7 +4960,7 @@ ![image](https://user-images.githubusercontent.com/2493803/106494751-90f9dc80-6499-11eb-901b-5e4dbdc678ba.png) -- Fix Empty highlighted words field ([#20329](https://github.com/RocketChat/Rocket.Chat/pull/20329) by [@yash-rajpal](https://github.com/yash-rajpal)) +- Fix Empty highlighted words field ([#20329](https://github.com/RocketChat/Rocket.Chat/pull/20329)) Able to Empty the highlighted text field in preferences @@ -5000,7 +5016,7 @@ - Add a new setting ("Add Reply-To header") in the Email settings' page to control when the Reply-To header is used in e-mail notifications; - The new setting is turned off (`false` value) by default. -- New Integration page was not being displayed ([#20670](https://github.com/RocketChat/Rocket.Chat/pull/20670) by [@yash-rajpal](https://github.com/yash-rajpal)) +- New Integration page was not being displayed ([#20670](https://github.com/RocketChat/Rocket.Chat/pull/20670)) - Notification worker stopping on error ([#20605](https://github.com/RocketChat/Rocket.Chat/pull/20605)) @@ -5214,7 +5230,6 @@ - [@paulobernardoaf](https://github.com/paulobernardoaf) - [@pierreozoux](https://github.com/pierreozoux) - [@rafaelblink](https://github.com/rafaelblink) -- [@yash-rajpal](https://github.com/yash-rajpal) ### 👩‍💻👨‍💻 Core Team 🤓 @@ -5233,6 +5248,7 @@ - [@sampaiodiego](https://github.com/sampaiodiego) - [@tassoevan](https://github.com/tassoevan) - [@tiagoevanp](https://github.com/tiagoevanp) +- [@yash-rajpal](https://github.com/yash-rajpal) # 3.11.5 `2021-04-20 · 1 🐛 · 1 👩‍💻👨‍💻` @@ -5295,7 +5311,7 @@ ### 🐛 Bug fixes -- Attachment download from title fixed ([#20585](https://github.com/RocketChat/Rocket.Chat/pull/20585) by [@yash-rajpal](https://github.com/yash-rajpal)) +- Attachment download from title fixed ([#20585](https://github.com/RocketChat/Rocket.Chat/pull/20585)) Added target = '_self' to attachment link, this seems to fix the problem, without this attribute, error page is displayed. @@ -5314,7 +5330,6 @@ ### 👩‍💻👨‍💻 Contributors 😍 - [@lolimay](https://github.com/lolimay) -- [@yash-rajpal](https://github.com/yash-rajpal) ### 👩‍💻👨‍💻 Core Team 🤓 @@ -5322,6 +5337,7 @@ - [@renatobecker](https://github.com/renatobecker) - [@sampaiodiego](https://github.com/sampaiodiego) - [@tiagoevanp](https://github.com/tiagoevanp) +- [@yash-rajpal](https://github.com/yash-rajpal) # 3.11.0 `2021-01-31 · 8 🎉 · 9 🚀 · 52 🐛 · 44 🔍 · 32 👩‍💻👨‍💻` @@ -5427,7 +5443,7 @@ Made user avatar change buttons to be descriptive of what they do. -- Tooltip added for Kebab menu on chat header ([#20116](https://github.com/RocketChat/Rocket.Chat/pull/20116) by [@yash-rajpal](https://github.com/yash-rajpal)) +- Tooltip added for Kebab menu on chat header ([#20116](https://github.com/RocketChat/Rocket.Chat/pull/20116)) Added the missing Tooltip for kebab menu on chat header. ![tooltip after](https://user-images.githubusercontent.com/58601732/104031406-b07f4b80-51f2-11eb-87a4-1e8da78a254f.gif) @@ -5449,12 +5465,12 @@ Users can be removed from channels without any error message. -- Added context check for closing active tabbar for member-list ([#20228](https://github.com/RocketChat/Rocket.Chat/pull/20228) by [@yash-rajpal](https://github.com/yash-rajpal)) +- Added context check for closing active tabbar for member-list ([#20228](https://github.com/RocketChat/Rocket.Chat/pull/20228)) When we click on a username and then click on see user's full profile, a tab gets active and shows us the user's profile, the problem occurs when the tab is still active and we try to see another user's profile. In this case, tabbar gets closed. To resolve this, added context check for closing action of active tabbar. -- Added Margin between status bullet and status label ([#20199](https://github.com/RocketChat/Rocket.Chat/pull/20199) by [@yash-rajpal](https://github.com/yash-rajpal)) +- Added Margin between status bullet and status label ([#20199](https://github.com/RocketChat/Rocket.Chat/pull/20199)) Added Margins between status bullet and status label @@ -5519,7 +5535,7 @@ After changes made on https://github.com/RocketChat/Rocket.Chat/pull/19931, the `Livechat.RegisterGuest` method started removing properties from the visitor inappropriately. The properties that did not receive value were removed from the object. Those changes were made to support the new Contact Form, but now the form has its own method to deal with Contact data so those changes are no longer necessary. -- Markdown added for Header Room topic ([#20021](https://github.com/RocketChat/Rocket.Chat/pull/20021) by [@yash-rajpal](https://github.com/yash-rajpal)) +- Markdown added for Header Room topic ([#20021](https://github.com/RocketChat/Rocket.Chat/pull/20021)) With the new 3.10.0 version update the Links in topic section below room name were not working, for more info refer issue #20018 @@ -5599,7 +5615,7 @@ ![image](https://user-images.githubusercontent.com/27704687/106056093-0a29b600-60cd-11eb-8038-eabbc0d8fb03.png) -- Status circle in profile section ([#20016](https://github.com/RocketChat/Rocket.Chat/pull/20016) by [@yash-rajpal](https://github.com/yash-rajpal)) +- Status circle in profile section ([#20016](https://github.com/RocketChat/Rocket.Chat/pull/20016)) The Status Circle in status message text input is now centered vertically. @@ -5803,7 +5819,6 @@ - [@sushant52](https://github.com/sushant52) - [@tlskinneriv](https://github.com/tlskinneriv) - [@wggdeveloper](https://github.com/wggdeveloper) -- [@yash-rajpal](https://github.com/yash-rajpal) - [@zdumitru](https://github.com/zdumitru) ### 👩‍💻👨‍💻 Core Team 🤓 @@ -5821,6 +5836,7 @@ - [@tassoevan](https://github.com/tassoevan) - [@thassiov](https://github.com/thassiov) - [@tiagoevanp](https://github.com/tiagoevanp) +- [@yash-rajpal](https://github.com/yash-rajpal) # 3.10.5 `2021-01-27 · 1 🐛 · 1 👩‍💻👨‍💻` diff --git a/app/utils/rocketchat.info b/app/utils/rocketchat.info index ce95521600029..06ce104de2921 100644 --- a/app/utils/rocketchat.info +++ b/app/utils/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "4.2.0-rc.0" + "version": "4.2.0-rc.1" } diff --git a/package-lock.json b/package-lock.json index c1ba19d8f2c1a..cd87cd4f32ffb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "Rocket.Chat", - "version": "4.2.0-rc.0", + "version": "4.2.0-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 78699baad6a0f..4f1bf1bfedb9c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Rocket.Chat", "description": "The Ultimate Open Source WebChat Platform", - "version": "4.2.0-rc.0", + "version": "4.2.0-rc.1", "author": { "name": "Rocket.Chat", "url": "https://rocket.chat/" From 9ce2291e6c302429514554ff75f473508151b858 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Thu, 25 Nov 2021 11:03:08 -0600 Subject: [PATCH 111/137] Regression: Fix sort param on omnichannel endpoints (#23789) --- client/components/Omnichannel/hooks/useAgentsList.ts | 2 +- client/components/Omnichannel/hooks/useDepartmentsList.ts | 2 +- client/views/admin/customEmoji/CustomEmoji.tsx | 2 +- definition/rest/helpers/PaginatedRequest.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/components/Omnichannel/hooks/useAgentsList.ts b/client/components/Omnichannel/hooks/useAgentsList.ts index 5c01de2c89670..d280aa5a0df34 100644 --- a/client/components/Omnichannel/hooks/useAgentsList.ts +++ b/client/components/Omnichannel/hooks/useAgentsList.ts @@ -37,7 +37,7 @@ export const useAgentsList = ( ...(options.text && { text: options.text }), offset: start, count: end + start, - sort: `{ name: 1 }`, + sort: `{ "name": 1 }`, }); const items = agents.map((agent: any) => { diff --git a/client/components/Omnichannel/hooks/useDepartmentsList.ts b/client/components/Omnichannel/hooks/useDepartmentsList.ts index 9b6b8e46a2556..8545873bd9ff0 100644 --- a/client/components/Omnichannel/hooks/useDepartmentsList.ts +++ b/client/components/Omnichannel/hooks/useDepartmentsList.ts @@ -40,7 +40,7 @@ export const useDepartmentsList = ( text: options.filter, offset: start, count: end + start, - sort: `{ name: 1 }`, + sort: `{ "name": 1 }`, }); const items = departments diff --git a/client/views/admin/customEmoji/CustomEmoji.tsx b/client/views/admin/customEmoji/CustomEmoji.tsx index c8b40a477d54b..c674080821c75 100644 --- a/client/views/admin/customEmoji/CustomEmoji.tsx +++ b/client/views/admin/customEmoji/CustomEmoji.tsx @@ -42,7 +42,7 @@ const CustomEmoji: FC = function CustomEmoji({ onClick, reload useMemo( () => ({ query: JSON.stringify({ name: { $regex: text || '', $options: 'i' } }), - sort: `{ ${sortBy}: ${sortDirection === 'asc' ? 1 : -1} }`, + sort: `{ "${sortBy}": ${sortDirection === 'asc' ? 1 : -1} }`, count: itemsPerPage, offset: current, }), diff --git a/definition/rest/helpers/PaginatedRequest.ts b/definition/rest/helpers/PaginatedRequest.ts index edaa045a5590d..3543675e39223 100644 --- a/definition/rest/helpers/PaginatedRequest.ts +++ b/definition/rest/helpers/PaginatedRequest.ts @@ -1,5 +1,5 @@ export type PaginatedRequest = { count?: number; offset?: number; - sort?: `{ ${S}: ${1 | -1} }` | string; + sort?: `{ "${S}": ${1 | -1} }` | string; } & T; From 8a9f5d071daeae9365c6abebf54dfb181938eb2b Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 26 Nov 2021 09:11:32 -0300 Subject: [PATCH 112/137] Regression: Include files on EE services build (#23793) --- .github/workflows/build_and_test.yml | 2 ++ ee/server/services/tsconfig.json | 1 + 2 files changed, 3 insertions(+) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 6631a6cb53c7e..be8b2b3941096 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -142,6 +142,8 @@ jobs: run: | cd ./ee/server/services npm run build + # check if build succeeded + [ ! -d ./dist/ee/server/services ] && exit 1 rm -rf dist/ - name: Build Rocket.Chat From Pull Request diff --git a/ee/server/services/tsconfig.json b/ee/server/services/tsconfig.json index 820685091f9a3..f80de2d7ff4c9 100644 --- a/ee/server/services/tsconfig.json +++ b/ee/server/services/tsconfig.json @@ -42,6 +42,7 @@ // "experimentalDecorators": true, }, "include": [ + "./**/*", "../../../definition" ], "exclude": [ From 63dc8c3d499ee328ae9ee89d93ff6f4313171499 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 26 Nov 2021 09:12:35 -0300 Subject: [PATCH 113/137] Bump version to 4.2.0-rc.2 --- .docker/Dockerfile.rhel | 2 +- .github/history.json | 30 ++++++++++++++++++++++++++ .snapcraft/resources/prepareRocketChat | 2 +- .snapcraft/snap/snapcraft.yaml | 2 +- HISTORY.md | 18 ++++++++++++++++ app/utils/rocketchat.info | 2 +- package-lock.json | 2 +- package.json | 2 +- 8 files changed, 54 insertions(+), 6 deletions(-) diff --git a/.docker/Dockerfile.rhel b/.docker/Dockerfile.rhel index 4433da8f7c970..bc4b5f923a14c 100644 --- a/.docker/Dockerfile.rhel +++ b/.docker/Dockerfile.rhel @@ -1,6 +1,6 @@ FROM registry.access.redhat.com/ubi8/nodejs-12 -ENV RC_VERSION 4.2.0-rc.1 +ENV RC_VERSION 4.2.0-rc.2 MAINTAINER buildmaster@rocket.chat diff --git a/.github/history.json b/.github/history.json index 7480f42087028..cfdec09086e50 100644 --- a/.github/history.json +++ b/.github/history.json @@ -67722,6 +67722,36 @@ ] } ] + }, + "4.2.0-rc.2": { + "node_version": "12.22.1", + "npm_version": "6.14.1", + "apps_engine_version": "1.28.1", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "23793", + "title": "Regression: Include files on EE services build", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "23789", + "title": "Regression: Fix sort param on omnichannel endpoints", + "userLogin": "KevLehman", + "contributors": [ + "KevLehman" + ] + } + ] } } } \ No newline at end of file diff --git a/.snapcraft/resources/prepareRocketChat b/.snapcraft/resources/prepareRocketChat index 0ab53cbba6990..fd1caaf99c6e7 100755 --- a/.snapcraft/resources/prepareRocketChat +++ b/.snapcraft/resources/prepareRocketChat @@ -1,6 +1,6 @@ #!/bin/bash -curl -SLf "https://releases.rocket.chat/4.2.0-rc.1/download/" -o rocket.chat.tgz +curl -SLf "https://releases.rocket.chat/4.2.0-rc.2/download/" -o rocket.chat.tgz tar xf rocket.chat.tgz --strip 1 diff --git a/.snapcraft/snap/snapcraft.yaml b/.snapcraft/snap/snapcraft.yaml index 648395e717936..59c0d4f43091e 100644 --- a/.snapcraft/snap/snapcraft.yaml +++ b/.snapcraft/snap/snapcraft.yaml @@ -7,7 +7,7 @@ # 5. `snapcraft snap` name: rocketchat-server -version: 4.2.0-rc.1 +version: 4.2.0-rc.2 summary: Rocket.Chat server description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ confinement: strict diff --git a/HISTORY.md b/HISTORY.md index d7798a1b77d47..8c39d791ee3a4 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,24 @@ # 4.2.0 (Under Release Candidate Process) +## 4.2.0-rc.2 +`2021-11-26 · 2 🔍 · 2 👩‍💻👨‍💻` + +
    +🔍 Minor changes + + +- Regression: Fix sort param on omnichannel endpoints ([#23789](https://github.com/RocketChat/Rocket.Chat/pull/23789)) + +- Regression: Include files on EE services build ([#23793](https://github.com/RocketChat/Rocket.Chat/pull/23793)) + +
    + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@KevLehman](https://github.com/KevLehman) +- [@sampaiodiego](https://github.com/sampaiodiego) + ## 4.2.0-rc.1 `2021-11-23 · 2 🔍 · 3 👩‍💻👨‍💻` diff --git a/app/utils/rocketchat.info b/app/utils/rocketchat.info index 06ce104de2921..c4596ebcbb0eb 100644 --- a/app/utils/rocketchat.info +++ b/app/utils/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "4.2.0-rc.1" + "version": "4.2.0-rc.2" } diff --git a/package-lock.json b/package-lock.json index cd87cd4f32ffb..933e9532c6fd5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "Rocket.Chat", - "version": "4.2.0-rc.1", + "version": "4.2.0-rc.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 4f1bf1bfedb9c..ae50c5bd03439 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Rocket.Chat", "description": "The Ultimate Open Source WebChat Platform", - "version": "4.2.0-rc.1", + "version": "4.2.0-rc.2", "author": { "name": "Rocket.Chat", "url": "https://rocket.chat/" From 1ed18b7ee6e8f5a77f798832fce9e5b2ff5b0d05 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 26 Nov 2021 11:55:15 -0300 Subject: [PATCH 114/137] Regression: Add @rocket.chat/emitter to EE services (#23802) --- ee/server/services/package-lock.json | 5 +++++ ee/server/services/package.json | 9 +++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ee/server/services/package-lock.json b/ee/server/services/package-lock.json index 110aa6323e6ef..49fddd2ef26e6 100644 --- a/ee/server/services/package-lock.json +++ b/ee/server/services/package-lock.json @@ -234,6 +234,11 @@ "debug": "^4.3.1" } }, + "@rocket.chat/emitter": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@rocket.chat/emitter/-/emitter-0.30.1.tgz", + "integrity": "sha512-BH1wMBo5AZwgWXIRm4k2M5rz9W7uDR+2xbfo/HP2bIjyTTq9mlU/20w0JBXAR7PaMf8gWgyfAWWGPi4ZBHA+ag==" + }, "@rocket.chat/string-helpers": { "version": "0.29.0", "resolved": "https://registry.npmjs.org/@rocket.chat/string-helpers/-/string-helpers-0.29.0.tgz", diff --git a/ee/server/services/package.json b/ee/server/services/package.json index 984e7ce794bff..802ae72e2eadb 100644 --- a/ee/server/services/package.json +++ b/ee/server/services/package.json @@ -6,7 +6,11 @@ "scripts": { "dev": "pm2 start ecosystem.config.js", "pm2": "pm2", - "start": "ts-node index.ts", + "start:account": "ts-node --files ./account/service.ts", + "start:authorization": "ts-node --files ./authorization/service.ts", + "start:ddp-streamer": "ts-node --files ./ddp-streamer/service.ts", + "start:presence": "ts-node --files ./presence/service.ts", + "start:stream-hub": "ts-node --files ./stream-hub/service.ts", "typecheck": "tsc --noEmit --skipLibCheck", "build": "tsc", "build-containers": "npm run build && docker-compose build && rm -rf ./dist", @@ -18,7 +22,8 @@ "author": "Rocket.Chat", "license": "MIT", "dependencies": { - "@rocket.chat/string-helpers": "^0.29.0", + "@rocket.chat/emitter": "^0.30.1", + "@rocket.chat/string-helpers": "^0.30.1", "ajv": "^8.7.1", "bcrypt": "^5.0.1", "body-parser": "^1.19.0", From 479bb09c5f8877b698a09dd3ced6e75bdf1efb8b Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 26 Nov 2021 11:56:07 -0300 Subject: [PATCH 115/137] Bump version to 4.2.0-rc.3 --- .docker/Dockerfile.rhel | 2 +- .github/history.json | 22 ++++++++++++++++++++++ .snapcraft/resources/prepareRocketChat | 2 +- .snapcraft/snap/snapcraft.yaml | 2 +- HISTORY.md | 15 +++++++++++++++ app/utils/rocketchat.info | 2 +- package-lock.json | 2 +- package.json | 2 +- 8 files changed, 43 insertions(+), 6 deletions(-) diff --git a/.docker/Dockerfile.rhel b/.docker/Dockerfile.rhel index bc4b5f923a14c..74895d7ba08bd 100644 --- a/.docker/Dockerfile.rhel +++ b/.docker/Dockerfile.rhel @@ -1,6 +1,6 @@ FROM registry.access.redhat.com/ubi8/nodejs-12 -ENV RC_VERSION 4.2.0-rc.2 +ENV RC_VERSION 4.2.0-rc.3 MAINTAINER buildmaster@rocket.chat diff --git a/.github/history.json b/.github/history.json index cfdec09086e50..ef4ced39ce0d7 100644 --- a/.github/history.json +++ b/.github/history.json @@ -67752,6 +67752,28 @@ ] } ] + }, + "4.2.0-rc.3": { + "node_version": "12.22.1", + "npm_version": "6.14.1", + "apps_engine_version": "1.28.1", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "23802", + "title": "Regression: Add @rocket.chat/emitter to EE services", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + } + ] } } } \ No newline at end of file diff --git a/.snapcraft/resources/prepareRocketChat b/.snapcraft/resources/prepareRocketChat index fd1caaf99c6e7..a49e9fe82a72e 100755 --- a/.snapcraft/resources/prepareRocketChat +++ b/.snapcraft/resources/prepareRocketChat @@ -1,6 +1,6 @@ #!/bin/bash -curl -SLf "https://releases.rocket.chat/4.2.0-rc.2/download/" -o rocket.chat.tgz +curl -SLf "https://releases.rocket.chat/4.2.0-rc.3/download/" -o rocket.chat.tgz tar xf rocket.chat.tgz --strip 1 diff --git a/.snapcraft/snap/snapcraft.yaml b/.snapcraft/snap/snapcraft.yaml index 59c0d4f43091e..b7d88cd083b83 100644 --- a/.snapcraft/snap/snapcraft.yaml +++ b/.snapcraft/snap/snapcraft.yaml @@ -7,7 +7,7 @@ # 5. `snapcraft snap` name: rocketchat-server -version: 4.2.0-rc.2 +version: 4.2.0-rc.3 summary: Rocket.Chat server description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ confinement: strict diff --git a/HISTORY.md b/HISTORY.md index 8c39d791ee3a4..b28966703db5d 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,21 @@ # 4.2.0 (Under Release Candidate Process) +## 4.2.0-rc.3 +`2021-11-26 · 1 🔍 · 1 👩‍💻👨‍💻` + +
    +🔍 Minor changes + + +- Regression: Add @rocket.chat/emitter to EE services ([#23802](https://github.com/RocketChat/Rocket.Chat/pull/23802)) + +
    + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@sampaiodiego](https://github.com/sampaiodiego) + ## 4.2.0-rc.2 `2021-11-26 · 2 🔍 · 2 👩‍💻👨‍💻` diff --git a/app/utils/rocketchat.info b/app/utils/rocketchat.info index c4596ebcbb0eb..3efcecab6bdc3 100644 --- a/app/utils/rocketchat.info +++ b/app/utils/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "4.2.0-rc.2" + "version": "4.2.0-rc.3" } diff --git a/package-lock.json b/package-lock.json index 933e9532c6fd5..a0be360ad8185 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "Rocket.Chat", - "version": "4.2.0-rc.2", + "version": "4.2.0-rc.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index ae50c5bd03439..58017e67808e7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Rocket.Chat", "description": "The Ultimate Open Source WebChat Platform", - "version": "4.2.0-rc.2", + "version": "4.2.0-rc.3", "author": { "name": "Rocket.Chat", "url": "https://rocket.chat/" From 6d4784efe5843597baa085861d11f9a831a89ad7 Mon Sep 17 00:00:00 2001 From: Martin Schoeler Date: Fri, 26 Nov 2021 15:27:48 -0300 Subject: [PATCH 116/137] Regression: Current Chats not Filtering (#23803) --- .../omnichannel/currentChats/CurrentChatsPage.tsx | 3 +-- .../omnichannel/currentChats/FilterByText.tsx | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/client/views/omnichannel/currentChats/CurrentChatsPage.tsx b/client/views/omnichannel/currentChats/CurrentChatsPage.tsx index e5df137c3c208..fde9fec1bcbeb 100644 --- a/client/views/omnichannel/currentChats/CurrentChatsPage.tsx +++ b/client/views/omnichannel/currentChats/CurrentChatsPage.tsx @@ -44,11 +44,10 @@ const CurrentChatsPage: FC<{ renderRow={renderRow} results={data && data.rooms} total={data && data.total} - setParams={setParams} params={params} reload={reload} renderFilter={({ onChange, ...props }: any): any => ( - + )} /> diff --git a/client/views/omnichannel/currentChats/FilterByText.tsx b/client/views/omnichannel/currentChats/FilterByText.tsx index eb40b39f02a15..1f4eebc35dfa2 100644 --- a/client/views/omnichannel/currentChats/FilterByText.tsx +++ b/client/views/omnichannel/currentChats/FilterByText.tsx @@ -18,10 +18,11 @@ import RemoveAllClosed from './RemoveAllClosed'; type FilterByTextType = FC<{ setFilter: Dispatch>; + setParams: Dispatch>; reload?: () => void; }>; -const FilterByText: FilterByTextType = ({ setFilter, reload, ...props }) => { +const FilterByText: FilterByTextType = ({ setFilter, setParams, reload, ...props }) => { const setModal = useSetModal(); const dispatchToastMessage = useToastMessageDispatch(); const t = useTranslation(); @@ -96,7 +97,17 @@ const FilterByText: FilterByTextType = ({ setFilter, reload, ...props }) => { tags: tags.map((tag) => tag.label), customFields: customFields.reduce(reducer, {}), }); - }, [setFilter, guest, servedBy, status, department, from, to, tags, customFields]); + setParams({ + guest, + servedBy, + status, + ...(department?.value && department.value !== 'all' && { department: department.value }), + from: from && moment(new Date(from)).utc().format('YYYY-MM-DDTHH:mm:ss'), + to: to && moment(new Date(to)).utc().format('YYYY-MM-DDTHH:mm:ss'), + tags: tags.map((tag) => tag.label), + customFields: customFields.reduce(reducer, {}), + }); + }, [setFilter, guest, servedBy, status, department, from, to, tags, customFields, setParams]); const handleClearFilters = useMutableCallback(() => { reset(); From 2f050e82a53e0a9fbc203fc4d6eddfba918b10af Mon Sep 17 00:00:00 2001 From: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> Date: Mon, 29 Nov 2021 18:07:58 +0530 Subject: [PATCH 117/137] Regression: Mark Livechat WebRTC video calling as alpha (#23813) --- app/livechat/server/config.ts | 1 + packages/rocketchat-i18n/i18n/en.i18n.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/livechat/server/config.ts b/app/livechat/server/config.ts index d8b2d124c15aa..0d37ad8bf9dde 100644 --- a/app/livechat/server/config.ts +++ b/app/livechat/server/config.ts @@ -619,6 +619,7 @@ Meteor.startup(function() { ], i18nDescription: 'Feature_depends_on_selected_call_provider_to_be_enabled_from_administration_settings', i18nLabel: 'Call_provider', + alert: 'The WebRTC provider is currently in alpha!
    We recommend using Firefox Browser for this feature since there are some known bugs within other browsers that still need to be fixed.
    Please report bugs to github.com/RocketChat/Rocket.Chat/issues', enableQuery: omnichannelEnabledQuery, }); }); diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 7e4aa128245d2..7415bdc285c73 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -1847,7 +1847,7 @@ "Favorite": "Favorite", "Favorite_Rooms": "Enable Favorite Rooms", "Favorites": "Favorites", - "Feature_depends_on_selected_call_provider_to_be_enabled_from_administration_settings": "This feature depends on the above selected call provider to be enabled from the administration settings.", + "Feature_depends_on_selected_call_provider_to_be_enabled_from_administration_settings": "This feature depends on the above selected call provider to be enabled from the administration settings.
    For **Jitsi**, please make sure you have Jitsi Enabled under Admin -> Video Conference -> Jitsi -> Enabled.
    For **WebRTC**, please make sure you have WebRTC enabled under Admin -> WebRTC -> Enabled.", "Feature_Depends_on_Livechat_Visitor_navigation_as_a_message_to_be_enabled": "This feature depends on \"Send Visitor Navigation History as a Message\" to be enabled.", "Feature_Limiting": "Feature Limiting", "Features": "Features", From 3b6ebacebe993a03f10a6768b606a9a4ce5bf550 Mon Sep 17 00:00:00 2001 From: "lingohub[bot]" <69908207+lingohub[bot]@users.noreply.github.com> Date: Mon, 29 Nov 2021 16:26:03 -0300 Subject: [PATCH 118/137] =?UTF-8?q?i18n:=20Language=20update=20from=20Ling?= =?UTF-8?q?oHub=20=F0=9F=A4=96=20on=202021-11-29Z=20(#23812)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Language update from LingoHub 🤖 Project Name: Rocket.Chat Project Link: https://translate.lingohub.com/rocketchat/dashboard/rocket-dot-chat User: Robot LingoHub Easy language translations with LingoHub 🚀 Co-authored-by: Diego Sampaio --- packages/rocketchat-i18n/i18n/af.i18n.json | 3 +- packages/rocketchat-i18n/i18n/ar.i18n.json | 172 +++-- packages/rocketchat-i18n/i18n/az.i18n.json | 3 +- packages/rocketchat-i18n/i18n/be-BY.i18n.json | 2 +- packages/rocketchat-i18n/i18n/bg.i18n.json | 3 +- packages/rocketchat-i18n/i18n/bs.i18n.json | 2 +- packages/rocketchat-i18n/i18n/ca.i18n.json | 674 +++++++++--------- packages/rocketchat-i18n/i18n/cs.i18n.json | 10 +- packages/rocketchat-i18n/i18n/cy.i18n.json | 2 +- packages/rocketchat-i18n/i18n/da.i18n.json | 12 +- packages/rocketchat-i18n/i18n/de-AT.i18n.json | 8 +- packages/rocketchat-i18n/i18n/de.i18n.json | 7 +- packages/rocketchat-i18n/i18n/el.i18n.json | 11 +- packages/rocketchat-i18n/i18n/en.i18n.json | 3 +- packages/rocketchat-i18n/i18n/eo.i18n.json | 3 +- packages/rocketchat-i18n/i18n/es.i18n.json | 498 +++++++------ packages/rocketchat-i18n/i18n/et.i18n.json | 3 +- packages/rocketchat-i18n/i18n/eu.i18n.json | 1 + packages/rocketchat-i18n/i18n/fa.i18n.json | 19 +- packages/rocketchat-i18n/i18n/fi.i18n.json | 7 +- packages/rocketchat-i18n/i18n/fr.i18n.json | 113 ++- packages/rocketchat-i18n/i18n/gl.i18n.json | 2 +- packages/rocketchat-i18n/i18n/he.i18n.json | 19 +- packages/rocketchat-i18n/i18n/hi-IN.i18n.json | 1 + packages/rocketchat-i18n/i18n/hr.i18n.json | 5 +- packages/rocketchat-i18n/i18n/hu.i18n.json | 576 ++++++++++++++- packages/rocketchat-i18n/i18n/id.i18n.json | 6 +- packages/rocketchat-i18n/i18n/it.i18n.json | 20 +- packages/rocketchat-i18n/i18n/ja.i18n.json | 8 +- packages/rocketchat-i18n/i18n/ka-GE.i18n.json | 5 +- packages/rocketchat-i18n/i18n/km.i18n.json | 8 +- packages/rocketchat-i18n/i18n/ko.i18n.json | 2 +- packages/rocketchat-i18n/i18n/ku.i18n.json | 7 +- packages/rocketchat-i18n/i18n/lo.i18n.json | 7 +- packages/rocketchat-i18n/i18n/lt.i18n.json | 20 +- packages/rocketchat-i18n/i18n/lv.i18n.json | 7 +- packages/rocketchat-i18n/i18n/mn.i18n.json | 6 +- packages/rocketchat-i18n/i18n/ms-MY.i18n.json | 15 +- packages/rocketchat-i18n/i18n/nl.i18n.json | 107 ++- packages/rocketchat-i18n/i18n/no.i18n.json | 3 +- packages/rocketchat-i18n/i18n/pl.i18n.json | 4 +- packages/rocketchat-i18n/i18n/pt-BR.i18n.json | 56 +- packages/rocketchat-i18n/i18n/pt.i18n.json | 6 +- packages/rocketchat-i18n/i18n/ro.i18n.json | 8 +- packages/rocketchat-i18n/i18n/ru.i18n.json | 5 +- packages/rocketchat-i18n/i18n/sk-SK.i18n.json | 21 +- packages/rocketchat-i18n/i18n/sl-SI.i18n.json | 3 +- packages/rocketchat-i18n/i18n/sq.i18n.json | 4 +- packages/rocketchat-i18n/i18n/sr.i18n.json | 92 ++- packages/rocketchat-i18n/i18n/sv.i18n.json | 10 +- packages/rocketchat-i18n/i18n/ta-IN.i18n.json | 9 +- packages/rocketchat-i18n/i18n/th-TH.i18n.json | 3 +- packages/rocketchat-i18n/i18n/tr.i18n.json | 12 +- packages/rocketchat-i18n/i18n/ug.i18n.json | 9 +- packages/rocketchat-i18n/i18n/uk.i18n.json | 7 +- packages/rocketchat-i18n/i18n/vi-VN.i18n.json | 3 +- packages/rocketchat-i18n/i18n/zh-HK.i18n.json | 7 +- packages/rocketchat-i18n/i18n/zh-TW.i18n.json | 4 +- packages/rocketchat-i18n/i18n/zh.i18n.json | 2 +- 59 files changed, 1806 insertions(+), 839 deletions(-) diff --git a/packages/rocketchat-i18n/i18n/af.i18n.json b/packages/rocketchat-i18n/i18n/af.i18n.json index 2c83ea2baafa8..5d56a61b8ef0d 100644 --- a/packages/rocketchat-i18n/i18n/af.i18n.json +++ b/packages/rocketchat-i18n/i18n/af.i18n.json @@ -555,6 +555,7 @@ "Continuous_sound_notifications_for_new_livechat_room": "Deurlopende klank kennisgewings vir nuwe livechat kamer", "Conversation": "gesprek", "Conversation_closed": "Gesprek gesluit: __comment__.", + "Conversation_finished": "Gesprek afgehandel", "Conversation_finished_message": "Gesprek Beëindigde Boodskap", "conversation_with_s": "die gesprek met %s", "Convert_Ascii_Emojis": "Skakel ASCII om na Emoji", @@ -2682,6 +2683,7 @@ "Users_added": "Die gebruikers is bygevoeg", "Users_in_role": "Gebruikers in rol", "UTF8_Names_Slugify": "UTF8 Name Slugify", + "Videocall_enabled": "Video-oproep aangeskakel", "Validate_email_address": "Bevestig e-pos adres", "Verification": "Verifikasie", "Verification_Description": "U mag die volgende plekhouers gebruik:
    • [Verifikasie_Url] vir die verifikasie-URL.
    • [naam], [fname], [lname] vir die volle naam, voornaam of van die gebruiker se naam.
    • [e-pos] vir die gebruiker se e-pos.
    • [Site_Name] en [Site_URL] vir die Aansoek Naam en URL onderskeidelik.
    ", @@ -2696,7 +2698,6 @@ "Video_Conference": "Videokonferensie", "Video_message": "Video boodskap", "Videocall_declined": "Video-oproep geweier.", - "Videocall_enabled": "Video-oproep aangeskakel", "View_All": "Bekyk alle lede", "View_Logs": "Bekyk logs", "View_mode": "Kyk af", diff --git a/packages/rocketchat-i18n/i18n/ar.i18n.json b/packages/rocketchat-i18n/i18n/ar.i18n.json index 264bf1a21f4ea..958898b741a7f 100644 --- a/packages/rocketchat-i18n/i18n/ar.i18n.json +++ b/packages/rocketchat-i18n/i18n/ar.i18n.json @@ -40,6 +40,7 @@ "Accounts_AllowUserAvatarChange": "السماح بتغيير الصورة الرمزية", "Accounts_AllowUsernameChange": "السماح بتغيير اسم المستخدم", "Accounts_AllowUserProfileChange": "السماح بتعديل الملف الشخصي للعضو", + "Accounts_AllowUserStatusMessageChange": "السماح برسالة الحالة المخصصة", "Accounts_AvatarCacheTime": "الصورة الرمزية وقت الكاش", "Accounts_AvatarCacheTime_description": "عدد الثواني التي يُطلب من بروتوكول http فيها تخزين الصور الرمزية في ذاكرة التخزين المؤقت.", "Accounts_AvatarResize": "تغيير حجم الصور الرمزية", @@ -57,6 +58,7 @@ "Accounts_Default_User_Preferences_not_available": "أخفق استرداد تفضيلات المستخدم لأنه لم يتم إعدادها من قبل المستخدم حتى الآن", "Accounts_DefaultUsernamePrefixSuggestion": "اقتراح بادئة اسم المستخدم الافتراضية", "Accounts_denyUnverifiedEmail": "رفض البريد الإلكتروني لم يتم التحقق منها", + "Accounts_Directory_DefaultView": "قائمة الدليل الافتراضية", "Accounts_Email_Activated": "[نيم]

    تم تنشيط حسابك.

    ", "Accounts_Email_Activated_Subject": "تم تفعيل الحساب", "Accounts_Email_Approved": "[نيم]

    تمت الموافقة على حسابك.

    ", @@ -98,19 +100,19 @@ "Accounts_OAuth_Drupal_id": "رمز تعريف العميل الخاص بدوربال oAuth2", "Accounts_OAuth_Drupal_secret": "مفتاح العميل السري الخاص بدوربال oAuth2", "Accounts_OAuth_Facebook": "تسجيل الدخول الى الفيسبوك", - "Accounts_OAuth_Facebook_callback_url": "URL الفيسبوك الاستدعاء", + "Accounts_OAuth_Facebook_callback_url": "رابط الفيسبوك الاستدعاء", "Accounts_OAuth_Facebook_id": "الفيسبوك معرف التطبيق", "Accounts_OAuth_Facebook_secret": "الفيسبوك السرية", "Accounts_OAuth_Github": "أوث ممكن", - "Accounts_OAuth_Github_callback_url": "URL جيثب الاستدعاء", + "Accounts_OAuth_Github_callback_url": "رابط جيثب الاستدعاء", "Accounts_OAuth_GitHub_Enterprise": "أوث ممكن", - "Accounts_OAuth_GitHub_Enterprise_callback_url": "URL جيثب المؤسسة الاستدعاء", + "Accounts_OAuth_GitHub_Enterprise_callback_url": "رابط جيثب المؤسسة الاستدعاء", "Accounts_OAuth_GitHub_Enterprise_id": "رقم العميل", "Accounts_OAuth_GitHub_Enterprise_secret": "سر العميل", "Accounts_OAuth_Github_id": "رقم العميل", "Accounts_OAuth_Github_secret": "سر العميل", "Accounts_OAuth_Gitlab": "أوث ممكن", - "Accounts_OAuth_Gitlab_callback_url": "URL GitLab الاستدعاء", + "Accounts_OAuth_Gitlab_callback_url": "رابط GitLab الاستدعاء", "Accounts_OAuth_Gitlab_id": "GitLab رقم", "Accounts_OAuth_Gitlab_identity_path": "مسار الهوية", "Accounts_OAuth_Gitlab_secret": "سر العميل", @@ -172,7 +174,10 @@ "Accounts_Registration_AuthenticationServices_Default_Roles": "الأدوار الافتراضية لخدمات المصادقة", "Accounts_Registration_AuthenticationServices_Default_Roles_Description": "سيتم تعيين الأدوار الافتراضية للمستخدمين عند التسجيل عبر خدمات المصادقة (افصل بينها بفاصلة)", "Accounts_Registration_AuthenticationServices_Enabled": "تسجيل مع خدمات المصادقة", - "Accounts_Registration_InviteUrlType": " نوع للدعوةURL", + "Accounts_Registration_Users_Default_Roles": "الأدوار الافتراضية للمستخدمين", + "Accounts_Registration_Users_Default_Roles_Description": "سيتم منح المستخدمين الأدوار الافتراضية (مفصولة بفواصل) عند التسجيل من خلال التسجيل اليدوي (بما في ذلك عبر واجهة برمجة التطبيقات)", + "Accounts_Registration_Users_Default_Roles_Enabled": "تمكين الأدوار الافتراضية للتسجيل اليدوي", + "Accounts_Registration_InviteUrlType": " نوع رابط الدعوة", "Accounts_RegistrationForm": "استمارة التسجيل", "Accounts_RegistrationForm_Disabled": "معطل", "Accounts_RegistrationForm_LinkReplacementText": "نص نموذج التسجيل رابط بديل", @@ -186,6 +191,7 @@ "Accounts_SetDefaultAvatar": "ضبط إعدادات الصورة الرمزية", "Accounts_SetDefaultAvatar_Description": "يتم المحاولة لتعيين الصورة الرمزية الافتراضية بناءً على حساب OAuth أو حساب Gravatar", "Accounts_ShowFormLogin": "يستند النموذج مشاهدة الدخول", + "Accounts_TwoFactorAuthentication_By_Email_Auto_Opt_In_Description": "سيتم تمكين المصادقة الثنائية عبر البريد الإلكتروني للمستخدمين الجدد افتراضيًا. سيكونون قادرين على تعطيله في صفحة ملفهم الشخصي.", "Accounts_TwoFactorAuthentication_Enabled": "تمكين المصادقة الثنائية", "Accounts_TwoFactorAuthentication_MaxDelta": "أقصى دلتا", "Accounts_TwoFactorAuthentication_MaxDelta_Description": "يحدد Maximum Delta عدد الرموز المميزة في أي وقت معين. يتم إنشاء الرموز المميزة كل 30 ثانية ، وتكون صالحة لمدة (30 * Maximum Delta) ثانية.
    مثال: مع تعيين الحد الأقصى من دلتا على 10 ، يمكن استخدام كل رمز مميز حتى 300 ثانية قبل أو بعد طابعه الزمني. هذا مفيد عندما لا تتم مزامنة ساعة العميل بشكل صحيح مع الخادم.", @@ -201,7 +207,7 @@ "Add_agent": "أضف وكيل", "Add_custom_oauth": "إضافة أوث مخصصة", "Add_Domain": "إضافة اسم مجال", - "Add_files_from": "إضافة ملفات من", + "Add_files_from": "إضافة ملفات من:", "Add_manager": "أضف مدير", "Add_Reaction": "إضافة تفاعل", "Add_Role": "إضافة دور", @@ -228,6 +234,7 @@ "Admin_Info": "معلومات المسؤول", "Administration": "الإدارة", "Adult_images_are_not_allowed": "لا يسمح بالصور للبالغين", + "Aerospace_and_Defense": "الفضاء والدفاع", "After_OAuth2_authentication_users_will_be_redirected_to_this_URL": "بعد المصادقة في OAuth2، سيتم إعادة توجيه المستخدمين إلى هذا الرابط", "Agent": "الموظف", "Agent_added": "تمت إضافة الوكيل", @@ -283,11 +290,15 @@ "API_Enable_CORS": "تفعيل CORS", "API_Enable_Direct_Message_History_EndPoint": "تفعيل نهاية سجل الرسائل المباشرة ", "API_Enable_Direct_Message_History_EndPoint_Description": "وهذا يتيح `/ أبي / v1 / im.history.others` الذي يسمح بعرض الرسائل المباشرة المرسلة من قبل المستخدمين الآخرين أن المتصل ليست جزءا من.", + "API_Enable_Rate_Limiter_Limit_Calls_Default": "يدعو الرقم الافتراضي إلى محدد السعر", + "API_Enable_Rate_Limiter_Limit_Calls_Default_Description": "عدد الاستدعاءات الافتراضية لكل نقطة نهاية لواجهة برمجة تطبيقات REST ، المسموح بها خلال النطاق الزمني المحدد أدناه", + "API_Enable_Rate_Limiter_Limit_Time_Default": "الحد الزمني الافتراضي لمحدد المعدل (بالمللي ثانية)", + "API_Enable_Rate_Limiter_Limit_Time_Default_Description": "المهلة الافتراضية للحد من عدد المكالمات في كل نقطة نهاية لواجهة برمجة تطبيقات REST (بالمللي ثانية)", "API_Enable_Shields": "تمكين الدروع", "API_Enable_Shields_Description": "تمكين الدروع المتاحة في `/ أبي / V1 / shield.svg`", - "API_GitHub_Enterprise_URL": "URL الخادم", + "API_GitHub_Enterprise_URL": "رابط الخادم", "API_GitHub_Enterprise_URL_Description": "مثال: http://domain.com (بدون الشرطة المائلة في الأخير)", - "API_Gitlab_URL": "URL GitLab", + "API_Gitlab_URL": "رابط GitLab", "API_Shield_Types": "أنواع الدرع", "API_Shield_Types_Description": "أنواع الدروع للتمكين كقائمة مفصولة بفواصل، اختر من `online` أو` channel` أو `*` للجميع", "API_Token": "API رمز", @@ -296,7 +307,7 @@ "API_Upper_Count_Limit": "الحد الأقصى لقيمة القيد", "API_Upper_Count_Limit_Description": "ما هو الحد الأقصى لعدد السجلات التي يجب أن تعودها واجهة برمجة تطبيقات ريست (عندما لا تكون غير محدودة)؟", "API_User_Limit": "حد المستخدم لإضافة جميع الاعضاء للقناة", - "API_Wordpress_URL": "URL ورد", + "API_Wordpress_URL": "رابط ورد", "Apiai_Key": "Api.ai مفتاح", "Apiai_Language": "Api.ai اللغة", "App_author_homepage": "المؤلف الصفحة الرئيسية", @@ -320,6 +331,8 @@ "Apply_and_refresh_all_clients": "تطبيق وتحديث كافة عملاء", "Apps": "التطبيقات", "Apps_Framework_enabled": "تمكين إطار التطبيق", + "Apps_Permissions_upload_read": "الوصول للملفات التي تم تحميلها على هذا الخادم", + "Apps_Permissions_upload_write": "تحميل الملفات على هذا الخادم", "Apps_Settings": "إعدادات التطبيق", "Apps_WhatIsIt": "التطبيقات: ما هي؟", "Apps_WhatIsIt_paragraph1": "أيقونة جديدة في منطقة الإدارة! ماذا يعني هذا وما هي التطبيقات؟", @@ -332,7 +345,7 @@ "are_typing": "يكتبون", "Are_you_sure": "هل أنت متأكد؟", "Are_you_sure_you_want_to_delete_your_account": "هل أنت متأكد من أنك تريد حذف حسابك؟", - "Are_you_sure_you_want_to_disable_Facebook_integration": "هل تريد بالتأكيد تعطيل دمج فاسيبوك؟", + "Are_you_sure_you_want_to_disable_Facebook_integration": "هل تريد بالتأكيد تعطيل دمج فيسبوك؟", "Assign_admin": "تعيين المدير", "assign-admin-role": "تعيين دور المدير", "assign-admin-role_description": "التصريح بمنح المستخدمين الآخرين دور المدير", @@ -393,12 +406,12 @@ "Backup_codes": "شيفرات التخزين الاحتياطي", "ban-user": "حظر المستخدم", "ban-user_description": "التصريح لحظر مستخدم من القناة", - "BBB_Video_Call": "جلسة BBB\n", + "BBB_Video_Call": "جلسة BBB", "Beta_feature_Depends_on_Video_Conference_to_be_enabled": "ميزة تجريبية. تعتمد على تفعيل إقامة مؤتمر عبر الفيديو.", "Block_User": "حظر المستخدم", "Blockchain": "Blockchain", "Body": "الجسم", - "bold": "عريض", + "bold": "غامق", "bot_request": "طلب الروبوت", "BotHelpers_userFields": "حقول المستخدم", "BotHelpers_userFields_Description": "كسف من حقول المستخدم التي يمكن الوصول إليها عن طريق طرق مساعد السير.", @@ -407,10 +420,11 @@ "Broadcast_channel": "قناة البث", "Broadcast_channel_Description": "يمكن للمستخدمين المصرح لهم فقط كتابة رسائل جديدة ، ولكن سيتمكن المستخدمون الآخرون من الرد", "Broadcast_Connected_Instances": "بث حالات متصلة", + "Browse_Files": "تصفح ملفات", "Bugsnag_api_key": "مفتاح بوجسناغ أبي", "Build_Environment": "بناء البيئة", - "bulk-register-user": "إنشاء قنوات دفعة واحدة", - "bulk-register-user_description": "التصريح بإنشاء قنوات دفعة واحدة", + "bulk-register-user": "إنشاء مستخدمين دفعة واحدة", + "bulk-register-user_description": "التصريح بإنشاء مستخدمين دفعة واحدة", "busy": "مشغول", "Busy": "مشغول", "busy_female": "مشغولة", @@ -420,6 +434,7 @@ "by": "بواسطة", "cache_cleared": "تم مسح التخزين المؤقت", "Call": "مكالمة", + "Caller": "متصل", "Cancel": "إلغاء", "Cancel_message_input": "لإلغاء التغييرات", "Cannot_invite_users_to_direct_rooms": "لا يمكن دعوة المستخدمين إلى غرف توجيه", @@ -458,7 +473,7 @@ "Channel_Name_Placeholder": "الرجاء إدخال اسم القناة ...", "Channel_to_listen_on": "السماع إلى هذه القناة", "Channel_Unarchived": "وكانت قناة مع اسم `# %s` إلغاء أرشفة بنجاح", - "Channels": "القنوات", + "Channels": "Channel", "Channels_are_where_your_team_communicate": "القنوات هي المكان الذي يتواصل فيه فريقك", "Channels_list": "قائمة القنوات العامة", "Chat_button": "زر الدردشة", @@ -544,6 +559,7 @@ "Closed": "مغلق", "Closed_by_visitor": "تم الإغلاق من قبل الزائر", "Closing_chat": "إغلاق الدردشة", + "Cloud_logout": "تسجيل الخروج من سحابة Rocket.Chat", "Collaborative": "تعاونية", "Collapse_Embedded_Media_By_Default": "إخفاء الوسائط المدمجة بشكل تلقائي", "color": "اللون", @@ -569,6 +585,7 @@ "Continuous_sound_notifications_for_new_livechat_room": "إخطارات صوتية مستمرة لغرفة livechat الجديدة", "Conversation": "محادثة", "Conversation_closed": "المحادثة أغلقت: __comment__.", + "Conversation_finished": "تم إنهاء المحادثة", "Conversation_finished_message": "المحادثة انتهى الرسالة", "conversation_with_s": "المحادثة مع %s", "Convert_Ascii_Emojis": "حول محارف الأسكي إلى اموجي", @@ -829,6 +846,8 @@ "create-d_description": "التصريح بالبدء بإرسال رسائل مباشرة", "create-p": "إنشاء قنوات خاصة", "create-p_description": "التصريح بإنشاء قنوات خاصة", + "create-personal-access-tokens": "قم بإنشاء رموز وصول شخصية", + "create-personal-access-tokens_description": "إذن لإنشاء رموز وصول شخصية", "create-user": "إنشاء مستخدم", "create-user_description": "التصريح بإنشاء مستخدمين", "Created_at": "أنشئت في", @@ -857,6 +876,8 @@ "Custom_oauth_unique_name": "اسم فريد أوث مخصص", "Custom_Script_Logged_In": "سيناريو المخصصة لتسجيل الدخول للمستخدمين", "Custom_Script_Logged_Out": "سيناريو مخصصة للمستخدمين تسجيل الخروج", + "Custom_Script_On_Logout": "برنامج نصي مخصص لتدفق تسجيل الخروج", + "Custom_Script_On_Logout_Description": "البرنامج النصي المخصص الذي سيتم تشغيله عند تنفيذ تدفق الخروج فقط", "Custom_Scripts": "سكربتات مخصصة", "Custom_Sound_Add": "إضافة صوت مخصص", "Custom_Sound_Delete_Warning": "لا يمكن التراجع عن حذف الصوت", @@ -881,6 +902,7 @@ "Deactivate": "تعطيل", "Decline": "إلغاء", "Default": "افتراضي", + "Default_value": "القيمة الافتراضية", "Delete": "حذف", "Delete_message": "حذف رسالة", "Delete_my_account": "حذف حسابي", @@ -916,6 +938,7 @@ "Desktop_Notifications_Enabled": "تنبيهات سطح المكتب مفعلة", "Different_Style_For_User_Mentions": "نمط مختلف للمستخدم يذكر", "Direct_message_someone": "رسالة شخص المباشر", + "Direct_message_you_have_joined": "لقد انضممت إلى رسالة مباشرة جديدة مع ", "Direct_Messages": "الرسائل المباشرة", "Direct_Reply": "الرد المباشر", "Direct_Reply_Debug": "تصحيح الرد المباشر", @@ -925,7 +948,7 @@ "Direct_Reply_Frequency": "تردد التحقق من البريد الإلكتروني", "Direct_Reply_Frequency_Description": "(بالدقائق، الافتراضي / الحد الأدنى 2)", "Direct_Reply_Host": "الرد المباشر المضيف", - "Direct_Reply_IgnoreTLS": "IgnoreTLS", + "Direct_Reply_IgnoreTLS": "تجاهل TLS", "Direct_Reply_Password": "كلمة السر", "Direct_Reply_Port": "Direct_Reply_Port", "Direct_Reply_Protocol": "بروتوكول الرد المباشر", @@ -936,15 +959,23 @@ "Directory": "دليل", "Disable_Facebook_integration": "تعطيل التكامل الفيسبوك", "Disable_Notifications": "إلغاء تفعيل الإشعارات", - "Disable_two-factor_authentication": "إلغاء تفعيل المصادقة بخطوتين", + "Disable_two-factor_authentication": "تعطيل المصادقة الثنائية عبر TOTP", + "Disable_two-factor_authentication_email": "تعطيل المصادقة الثنائية عبر البريد الإلكتروني", "Disabled": "تعطيل", "Disallow_reacting": "عدم الاستجابة", "Disallow_reacting_Description": "لا تسمح بالتفاعل", + "Discussion": "مناقشة", "Discussion_description": "ساهم بإعطاء نظرة عامة لما يجري. عند إنشاء مناقشة يتم إنشاء قناة فرعية وربطها بالقناة المحددة", + "Discussion_first_message_disabled_due_to_e2e": "يمكنك البدء في إرسال رسائل مشفرة من طرف إلى طرف في هذه المناقشة بعد إنشائها.", "Discussion_name": "اسم النقاش", "Discussion_start": "ابدأ نقاش", "Discussion_target_channel": "القناة أو المجموعة الأب", + "Discussion_target_channel_prefix": "أنت تقوم بإنشاء مناقشة في", + "Discussion_title": "قم بإنشاء مناقشة جديدة", "Discussions": "مناقشات", + "Display": "عرض", + "Display_avatars": "عرض الصور الرمزية", + "Display_Avatars_Sidebar": "عرض الصور الرمزية في الشريط الجانبي", "Display_offline_form": "عرض النموذج متواجد حاليا", "Display_unread_counter": "عرض عدد الرسائل غير المقروءة", "Displays_action_text": "نص العمل يعرض", @@ -1014,11 +1045,14 @@ "Emoji_provided_by_JoyPixels": "JoyPixels رموز تعبيرية مقدمة من", "EmojiCustomFilesystem": "ملفات الرموز التعبيرية المخصصة", "Empty_title": "عنوان فارغ", + "Enable_message_parser_early_adoption": "قم بتمكين المحلل اللغوي للرسائل", + "Enable_message_parser_early_adoption_alert": "هذه ميزة تجريبية وستظل على هذا النحو على الأقل حتى الإصدار 3.19.0 ، وهذا الخيار هو مساعدتنا في الاختبارات. بمجرد عدم العثور على أي مشاكل أخرى ، سنقوم بإزالة هذا الخيار والانتقال إلى الحل الجديد", "Enable": "تمكين", "Enable_Auto_Away": "تمكين السيارات بعيدا", "Enable_Desktop_Notifications": "تفعيل تنبيهات سطح المكتب", "Enable_Svg_Favicon": "تفعيل أيقونة SVG ", - "Enable_two-factor_authentication": "تفعيل المصادقة بخطوتين", + "Enable_two-factor_authentication": "تمكين المصادقة الثنائية عبر TOTP", + "Enable_two-factor_authentication_email": "قم بتمكين المصادقة الثنائية عبر البريد الإلكتروني", "Enabled": "مفعل", "Encrypted": "مشفر", "Encrypted_message": "رسالة مشفرة", @@ -1092,10 +1126,10 @@ "error-invalid-subscription": "الاشتراك غير صالح", "error-invalid-token": "رمز غير صحيح", "error-invalid-triggerWords": "triggerWords غير صالح", - "error-invalid-urls": "عنوان URL غير صالح", + "error-invalid-urls": "روابط غير صالحة", "error-invalid-user": "مستخدم غير صالح", "error-invalid-username": "اسم المستخدم غير صالح", - "error-invalid-webhook-response": "رد URL webhook مع أي وضع آخر من 200", + "error-invalid-webhook-response": "رد رابط webhook مع أي وضع آخر من 200", "error-logged-user-not-in-room": "أنت لست في الغرفة ` %s`", "error-message-deleting-blocked": "يتم حظر رسالة حذف", "error-message-editing-blocked": "تم حظر تحرير رسالة", @@ -1112,6 +1146,7 @@ "error-password-policy-not-met-oneSpecial": "لا تتوافق كلمة المرور مع سياسة الخادم ذات الحرف الخاص واحد على الأقل", "error-password-policy-not-met-oneUppercase": "كلمة المرور لا تتوافق مع نهج الخادم ذي الحرف الكبير واحد على الأقل", "error-password-policy-not-met-repeatingCharacters": "لا تتوافق كلمة المرور مع سياسة الخادم الخاصة بأحرف مكررة ممنوعة (لديك الكثير من نفس الأحرف بجانب بعضها البعض)", + "error-personal-access-tokens-are-current-disabled": "رموز الوصول الشخصية معطلة حاليًا", "error-push-disabled": "تم تعطيل دفع", "error-remove-last-owner": "هذا هو صاحب الماضي. يرجى تحديد المالك الجديد قبل إزالة هذه واحدة.", "error-role-in-use": "لا يمكن حذف دور لأنه في استخدام", @@ -1125,7 +1160,7 @@ "error-user-limit-exceeded": "يتجاوز عدد المستخدمين الذين تحاول دعوتهم إلى #channel_name الحد الذي حدده المشرف", "error-user-not-in-room": "المستخدم ليس في هذه الغرفة", "error-user-registration-disabled": "تم تعطيل تسجيل المستخدم", - "error-user-registration-secret": "يسمح تسجيل المستخدم فقط عبر URL السري", + "error-user-registration-secret": "يسمح تسجيل المستخدم فقط عبر رابط سري", "error-you-are-last-owner": "كنت صاحب الماضي. الرجاء تعيين المالك الجديد قبل مغادرة الغرفة.", "Esc_to": "زر الهروب", "Event_Trigger": "مشغل العملية", @@ -1156,11 +1191,12 @@ "Favorites": "المفضلة", "Feature_Depends_on_Livechat_Visitor_navigation_as_a_message_to_be_enabled": "تعتمد هذه الميزة على \"إرسال محفوظات تنقل الزائر كرسالة\" ليتم تمكينها.", "Features_Enabled": "الميزات الممكنة", + "Federation_Invite_Users_To_Private_Rooms": "من الآن فصاعدًا ، يمكنك دعوة المستخدمين المتحدين فقط إلى الغرف الخاصة أو المناقشات.", "Federation_Domain": "نطاق", "FEDERATION_Domain": "نطاق", "FEDERATION_Status": "الحالة", "Field": "حقل", - "Field_removed": "إزالة الميدان", + "Field_removed": "إزالة الحقل", "Field_required": "حقل مطلوب", "File_exceeds_allowed_size_of_bytes": "يتجاوز حجم الملف المسموح به بايت __size__", "File_name_Placeholder": "ابحث في الملفات...", @@ -1185,9 +1221,9 @@ "FileUpload_GoogleStorage_Bucket": "اسم سلة تخزين غوغل", "FileUpload_GoogleStorage_Bucket_Description": "اسم الدلو الذي يجب تحميل الملفات إليه.", "FileUpload_GoogleStorage_Proxy_Avatars": "تجسيد الوكيل", - "FileUpload_GoogleStorage_Proxy_Avatars_Description": "يتم إرسال ملفات الملف الشخصي في برنامج الخادم الوكيل عبر الخادم بدلاً من الوصول المباشر إلى عنوان URL للأصل", + "FileUpload_GoogleStorage_Proxy_Avatars_Description": "يتم إرسال ملفات الملف الشخصي في برنامج الخادم الوكيل عبر الخادم بدلاً من الوصول المباشر إلى عنوان رابط للأصل", "FileUpload_GoogleStorage_Proxy_Uploads": "تحميل الوكيل", - "FileUpload_GoogleStorage_Proxy_Uploads_Description": "يتم إرسال عمليات نقل ملف الخادم عبر الخادم بدلاً من الوصول المباشر إلى عنوان URL للأصل", + "FileUpload_GoogleStorage_Proxy_Uploads_Description": "يتم إرسال عمليات نقل ملف الخادم عبر الخادم بدلاً من الوصول المباشر إلى عنوان رابط للأصل", "FileUpload_GoogleStorage_Secret": "المفتاح السري لتخزين غوغل", "FileUpload_GoogleStorage_Secret_Description": "يرجى اتباع هذه الإرشاداتولصق النتيجة هنا.", "FileUpload_MaxFileSize": "الحد الأقصى لتحميل الملف الحجم (بايت)", @@ -1201,13 +1237,13 @@ "FileUpload_S3_AWSAccessKeyId": "الأمازون S3 AWSAccessKeyId", "FileUpload_S3_AWSSecretAccessKey": "الأمازون S3 AWSSecretAccessKey", "FileUpload_S3_Bucket": "الأمازون S3 اسم دلو", - "FileUpload_S3_BucketURL": "URL دلو", + "FileUpload_S3_BucketURL": "رابط دلو", "FileUpload_S3_CDN": "نطاق كندي للتنزيل", "FileUpload_S3_ForcePathStyle": "فرض مظهر المسار", "FileUpload_S3_Proxy_Avatars": "تجسيد الوكيل", - "FileUpload_S3_Proxy_Avatars_Description": "يتم إرسال ملفات الملف الشخصي في برنامج الخادم الوكيل عبر الخادم بدلاً من الوصول المباشر إلى عنوان URL للأصل", + "FileUpload_S3_Proxy_Avatars_Description": "يتم إرسال ملفات الملف الشخصي في برنامج الخادم الوكيل عبر الخادم بدلاً من الوصول المباشر إلى عنوان رابط للأصل", "FileUpload_S3_Proxy_Uploads": "تحميل الوكيل", - "FileUpload_S3_Proxy_Uploads_Description": "يتم إرسال عمليات نقل ملف الخادم عبر الخادم بدلاً من الوصول المباشر إلى عنوان URL للأصل", + "FileUpload_S3_Proxy_Uploads_Description": "يتم إرسال عمليات نقل ملف الخادم عبر الخادم بدلاً من الوصول المباشر إلى عنوان رابط للأصل", "FileUpload_S3_Region": "منطقة", "FileUpload_S3_SignatureVersion": "نسخة التوقيع", "FileUpload_S3_URLExpiryTimeSpan": "فترة انتهاء صلاحية الروابط", @@ -1215,10 +1251,10 @@ "FileUpload_Storage_Type": "نوع التخزين", "FileUpload_Webdav_Password": "WebDAV كلمة السر", "FileUpload_Webdav_Proxy_Avatars": "تجسيد الوكيل", - "FileUpload_Webdav_Proxy_Avatars_Description": "يتم إرسال ملفات الملف الشخصي في برنامج الخادم الوكيل عبر الخادم بدلاً من الوصول المباشر إلى عنوان URL للأصل", + "FileUpload_Webdav_Proxy_Avatars_Description": "يتم إرسال ملفات الملف الشخصي في برنامج الخادم الوكيل عبر الخادم بدلاً من الوصول المباشر إلى عنوان رابط للأصل", "FileUpload_Webdav_Proxy_Uploads": "تحميل الوكيل", - "FileUpload_Webdav_Proxy_Uploads_Description": "يتم إرسال عمليات نقل ملف الخادم عبر الخادم بدلاً من الوصول المباشر إلى عنوان URL للأصل", - "FileUpload_Webdav_Server_URL": "عنوان URL لدخول خادم WebDAV", + "FileUpload_Webdav_Proxy_Uploads_Description": "يتم إرسال عمليات نقل ملف الخادم عبر الخادم بدلاً من الوصول المباشر إلى عنوان رابط للأصل", + "FileUpload_Webdav_Server_URL": "عنوان رابط لدخول خادم WebDAV", "FileUpload_Webdav_Upload_Folder_Path": "تحميل مسار المجلد", "FileUpload_Webdav_Upload_Folder_Path_Description": "مسار مجلد WebDAV الذي يجب أن يتم تحميل الملفات إليه", "FileUpload_Webdav_Username": "اسم مستخدم WebDAV", @@ -1227,7 +1263,9 @@ "Financial_Services": "الخدمات المالية", "First_Channel_After_Login": "القناة الأولى بعد تسجيل الدخول", "Flags": "أعلام", + "Follow_message": "اتبع الرسالة", "Follow_social_profiles": "اتبع محات اجتماعية لدينا، مفترق لنا على جيثب وتبادل الأفكار حول التطبيق rocket.chat على متن trello لدينا.", + "Following": "تابع", "Fonts": "الخطوط", "Food_and_Drink": "طعام و مشروبات", "Footer": "تذييل", @@ -1255,8 +1293,9 @@ "From_Email": "من البريد الإلكتروني", "From_email_warning": "تحذير: حقل من يخضع لإعدادات خادم البريد الخاص بك.", "Gaming": "الألعاب", - "General": "العامة", + "General": "عام", "Generate_New_Link": "إنشاء رابط جديد", + "Get_link": "إنسخ الرابط", "github_no_public_email": "ليس لديك أي بريد الإلكتروني عام في حسابك على Github", "Give_a_unique_name_for_the_custom_oauth": "تعطي اسما فريدا لأوث مخصصة", "Give_the_application_a_name_This_will_be_seen_by_your_users": "إعطاء التطبيق اسما. وسوف يظهر هذا من قبل المستخدمين.", @@ -1269,6 +1308,7 @@ "GoogleTagManager_id": "جوجل مدير العلامات معرف", "Government": "الحكومي", "Group_by_Type": "المجموعة حسب النوع", + "Group_discussions": "مناقشات جماعية", "Group_favorites": "مجموعة المفضلة", "Group_mentions_disabled_x_members": "تشير المجموعة إلى \"@ all\" و \"@ here\" تم تعطيلها للغرف التي تضم أكثر من __total__ أعضاء.", "Group_mentions_only": "تشير المجموعة فقط", @@ -1364,7 +1404,7 @@ "Install_FxOs_error": "عذرا، لم ينجح على النحو المنشود! ظهر الخطأ التالي:", "Install_FxOs_follow_instructions": "الرجاء التأكد من تثبيت التطبيق على جهازك (اضغط على \"تثبيت\" عندما دفع).", "Install_package": "ثبت المجموعة", - "Installation": "تركيب", + "Installation": "تنصيب", "Installed_at": "تثبيت في", "Instance_Record": "مثال على قيد", "Instructions_to_your_visitor_fill_the_form_to_send_a_message": "تعليمات لزائرك ملء النموذج لإرسال رسالة", @@ -1502,6 +1542,7 @@ "Keyboard_Shortcuts_Keys_5": "أمر(أو ألت) + سهم لليمين", "Keyboard_Shortcuts_Keys_6": "أمر(أو ألت) + سهم لأسفل", "Keyboard_Shortcuts_Keys_7": "شيفت+ أدخل", + "Keyboard_Shortcuts_Mark_all_as_read": "حدد جميع الرسائل (في جميع القنوات) كمقروءة", "Keyboard_Shortcuts_Move_To_Beginning_Of_Message": "الانتقال إلى بداية الرسالة", "Keyboard_Shortcuts_Move_To_End_Of_Message": "الانتقال إلى نهاية الرسالة", "Keyboard_Shortcuts_New_Line_In_Message": "سطر جديد في إدخال رسالة إنشاء", @@ -1541,6 +1582,7 @@ "LDAP_BaseDN_Description": "اسم المؤهل بالكامل المميز (DN) من الشجرة الفرعية LDAP تريد البحث للمستخدمين والمجموعات. يمكنك إضافة ما تريد. ومع ذلك، يجب أن تحدد كل مجموعة في قاعدة نطاق نفس المستخدمين التي تنتمي إليها. إذا قمت بتحديد مجموعات المستخدمين المحظورة، وفقط للمستخدمين الذين ينتمون لتلك الجماعات أن يكون في نطاق. نوصي تحديد المستوى الخاص بك شجرة دليل LDAP كقاعدة المجال الخاص بك واستخدام فلتر البحث للتحكم في الوصول.", "LDAP_CA_Cert": "CA سيرت", "LDAP_Connect_Timeout": "انتهاء مدة الاتصال (بالملي ثانية)", + "LDAP_DataSync_AutoLogout": "تسجيل الخروج التلقائي للمستخدمين المعطّلون", "LDAP_Default_Domain": "المجال الافتراضي", "LDAP_Default_Domain_Description": "إذا تم توفير النطاق الافتراضي سيتم استخدامه لإنشاء بريد إلكتروني فريد للمستخدمين حيث لم يتم استيراد البريد الإلكتروني من لداب. سيتم تثبيت الرسالة الإلكترونية باسم `أوزرنام @ default_domain` أور` unique_id @ default_domain`.
    مثال: `rocket.chat`", "LDAP_Enable": "تفعيل", @@ -1581,19 +1623,21 @@ "LDAP_Search_Page_Size_Description": "سيعود الحد الأقصى لعدد الإدخالات لكل صفحة نتيجة ليتم معالجتها", "LDAP_Search_Size_Limit": "حجم البحث الحد", "LDAP_Search_Size_Limit_Description": "الحد الأقصى لعدد الإدخالات المراد إرجاعها.
    ** الاهتمام ** يجب أن يزيد هذا الرقم عن ** حجم صفحة البحث **", + "LDAP_Sync_AutoLogout_Enabled": "تفعيل تسجيل الخروج التلقائي", + "LDAP_Sync_AutoLogout_Interval": "الفاصل الزمني لتسجيل الخروج التلقائي", "LDAP_Sync_Now": "خلفية مزامنة الآن", "LDAP_Sync_Now_Description": "سيتم تنفيذ ** مزامنة الخلفية ** الآن بدلا من الانتظار ** مزامنة الفاصل الزمني ** حتى لو ** مزامنة الخلفية ** هو خطأ.
    هذا الإجراء غير متزامن، يرجى الاطلاع على سجلات لمزيد من المعلومات حول معالجة", "LDAP_Sync_User_Avatar": "تزامن العضو الرمزية", "LDAP_Timeout": "مهلة (مللي ثانية)", "LDAP_Timeout_Description": "كم عدد الأميال التي تنتظر نتيجة بحث قبل إرجاع خطأ", - "LDAP_Unique_Identifier_Field": "معرف الميدان وفريدة من نوعها", + "LDAP_Unique_Identifier_Field": "حقل المعرف الفريد", "LDAP_Unique_Identifier_Field_Description": "التي الحقل سيتم استخدامها لربط المستخدم LDAP والمستخدم Rocket.Chat. يمكنك إبلاغ قيم متعددة مفصولة بفواصل في محاولة للحصول على قيمة من سجل LDAP.
    القيمة الافتراضية هي `objectGUID يتم، آي بي إم، entryUUID، GUID، dominoUNID، nsuniqueId، uidNumber`", "LDAP_User_Search_Field": "مجال البحث", "LDAP_User_Search_Field_Description": "السمة LDAP أن يحدد المستخدم LDAP الذي يحاول المصادقة. وينبغي أن يكون هذا المجال `sAMAccountName` للمنشآت الأكثر نشاطا دليل، ولكنه قد يكون` uid` عن حلول LDAP الأخرى، مثل ب OpenLDAP. يمكنك استخدام `mail` لتعريف المستخدمين عن طريق البريد الإلكتروني أو أيا كان السمة التي تريد.
    يمكنك استخدام قيم متعددة مفصولة بفواصل للسماح للمستخدمين تسجيل الدخول باستخدام معرفات متعددة مثل اسم المستخدم أو البريد الإلكتروني.", "LDAP_User_Search_Filter": "فلتر", "LDAP_User_Search_Filter_Description": "إذا كان سيتم السماح محددة، فقط للمستخدمين تطابق هذا المرشح لتسجيل الدخول. إذا لم يتم تحديد مرشح، جميع المستخدمين ضمن نطاق قاعدة المجال المحدد سوف تكون قادرة على تسجيل الدخول.
    على سبيل المثال ل Active Directory `memberOf = CN = ROCKET_CHAT، أوو = العام Groups`.
    على سبيل المثال لب OpenLDAP (للمد بحث مباراة) `أوو: DN: = ROCKET_CHAT`.", "LDAP_User_Search_Scope": "نطاق", - "LDAP_Username_Field": "اسم المستخدم الميدان", + "LDAP_Username_Field": "حقل اسم المستخدم", "LDAP_Username_Field_Description": "التي المجال سوف تستخدم * اسم المستخدم * للمستخدمين الجدد. اتركه فارغا لاستخدام اسم المستخدم على علم صفحة تسجيل الدخول.
    يمكنك استخدام العلامات قالب جدا، مثل `#{givenName}.#{sn}`.
    القيمة الافتراضية هي `sAMAccountName`.", "Lead_capture_email_regex": "التقاط التعبير العادي للبريد الإلكتروني", "Lead_capture_phone_regex": "يؤدي التقاط التعبير عن الهاتف الهاتف", @@ -1612,6 +1656,7 @@ "Livechat": "محادثة مباشرة", "Livechat_agents": "وكلاء المحادثات الحية", "Livechat_AllowedDomainsList": "أسماء المجالات المسموحة في الدردشة المباشرة", + "Livechat_auto_close_on_hold_chats_timeout_Description": "حدد المدة التي ستظل فيها الدردشة في قائمة الانتظار حتى يتم إغلاقها تلقائيًا بواسطة النظام. الوقت بالثواني", "Livechat_Dashboard": "لوحة المحادثات الحية", "Livechat_enabled": "تفعيل المحادثات الحية", "Livechat_Facebook_API_Key": "أومنيشانل أبي مفتاح", @@ -1661,11 +1706,11 @@ "Login": "تسجيل الدخول", "Login_with": "تسجيل الدخول بـ %s", "Logistics": "الخدمات اللوجستية", - "Logout": "تسجيل خروج", + "Logout": "تسجيل ألخروج", "Logout_Others": "تسجيل الخروج من الأجهزة الأخرى", "Mail_Message_Invalid_emails": "لقد قدمت رسائل البريد الإلكتروني واحدة أو أكثر غير صالحة:٪ ق", "Mail_Message_Missing_to": "يجب عليك اختيار واحد أو أكثر من المستخدمين أو تقديم واحدة أو أكثر من عناوين البريد الإلكتروني، مفصولة بفواصل.", - "Mail_Message_No_messages_selected_select_all": "لم تقم بتحديد أي رسالة. هل تريد أن تحدد جميع الرسائل الظاهرة؟", + "Mail_Message_No_messages_selected_select_all": "لم تقم بتحديد أي رسائل", "Mail_Messages": "إرسال الرسائل للبريد الألكتروني", "Mail_Messages_Instructions": "حدد الرسائل المراد إرسالها بالبريد الإلكتروني بالضغط على تلك الرسائل", "Mail_Messages_Subject": "مجموعة مختارة من رسائل %s", @@ -1697,9 +1742,10 @@ "Managing_integrations": "إدارة التكامل", "Manufacturing": "تصنيع", "MapView_Enabled": "تفعيل عرض الخريطة", - "MapView_Enabled_Description": "يعرض تفعيل عرض الخريطة زر مشاركة الموقع على يسار حقل رسالة الدردشة", + "MapView_Enabled_Description": "سيؤدي تمكين عرض الخريطة إلى عرض زر مشاركة الموقع على يمين حقل إدخال الدردشة.", "MapView_GMapsAPIKey": "مفتاح واجهة برمجة التطبيقات لخرائط غوغل الثابتة", "MapView_GMapsAPIKey_Description": "ويمكن الحصول على هذا من غوغل ديفيلوبيرز كونسول مجانا.", + "Mark_all_as_read": "حدد جميع الرسائل (في جميع القنوات) كمقروءة", "Mark_as_read": "تعليم كمقروء", "Mark_as_unread": "تعيين كغير مقروء", "Mark_unread": "علامة غير مقروء", @@ -1835,6 +1881,7 @@ "mute-user": "كتم المستخدم", "mute-user_description": "التصريح بكتم المستخدمين الآخرين في نفس القناة", "Muted": "صامتة", + "My Data": "بياناتي", "My_Account": "حسابي", "My_location": "موقعي", "n_messages": "%s رسائل", @@ -1850,6 +1897,8 @@ "New_Application": "تطبيق جديد", "New_Custom_Field": "حقل مخصص جديد", "New_Department": "الإدارة الجديدة", + "New_discussion": "مناقشة جديدة", + "New_discussion_first_message": "عادة ، تبدأ المناقشة بسؤال ، مثل \"كيف يمكنني تحميل صورة؟\"", "New_discussion_name": "اسم معبر لغرفة النقاش", "New_integration": "التكامل الجديد", "New_line_message_compose_input": "` %s` - سطر جديد في إدخال رسالة الإنشاء", @@ -1866,11 +1915,17 @@ "New_visitor_navigation": "التنقل الجديد: __history__", "Newer_than": "أحدث من", "Newer_than_may_not_exceed_Older_than": "\"أحدث من\" قد لا يتجاوز \"أقدم من\"", + "Nickname": "اسمك المستعار", + "Nickname_Placeholder": "أدخل اسمك المستعار...", "No": "لا", "No_available_agents_to_transfer": "لا يوجد أي موظفين ليتم نقلهم", "No_channel_with_name_%s_was_found": "لا توجد قناة باسم \"%s\"", "No_channels_yet": "لست جزء من أي قناة حتى الآن.", + "No_data_found": "لم يتم العثور على بيانات", "No_direct_messages_yet": "لم تبدأ أي محادثات حتى الآن.", + "No_Discussions_found": "لم يتم العثور على مناقشات", + "No_discussions_yet": "لا توجد مناقشات حتى الآن", + "No_emojis_found": "لم يتم العثور على رموز تعبيرية", "No_Encryption": "لا التشفير", "No_group_with_name_%s_was_found": "لا توجد مجموعة خاصة باسم \"%s\"", "No_groups_yet": "لا يوجد لديك مجموعات خاصة حتى الآن.", @@ -1893,11 +1948,14 @@ "Normal": "عادي", "Not_authorized": "غير مصرح", "Not_Available": "غير متاح", + "Not_following": "عدم اتباع", + "Not_Following": "عدم اتباع", "Not_found_or_not_allowed": "غير موجود أو غير مسموح", "Nothing": "لا شيء", "Nothing_found": "لا يوجد شيء", "Notification_Desktop_Default_For": "عرض الإخطارات سطح المكتب ل", "Notification_Push_Default_For": "دفع الإخطارات موبايل ل", + "Notification_RequireInteraction": "يتطلب التفاعل لرفض إعلام سطح المكتب", "Notifications": "الإشعارات", "Notifications_Max_Room_Members": "أقصى أعضاء الغرفة قبل تعطيل جميع الإخطارات رسالة", "Notifications_Max_Room_Members_Description": "الحد الأقصى لعدد الأعضاء في الغرفة عند تعطيل الإشعارات لجميع الرسائل. يمكن للمستخدمين لا يزال تغيير في إعداد غرفة لتلقي جميع الإخطارات على أساس فردي. (0 لتعطيل)", @@ -1936,6 +1994,7 @@ "Only_authorized_users_can_write_new_messages": "يمكن للمستخدمين المصرح لهم فقط كتابة رسائل جديدة", "Only_from_users": "فقط محتوى التقليم من هؤلاء المستخدمين (اتركه فارغًا لضبط محتوى كل شخص)", "Only_On_Desktop": "وضع المكتب -إرسال من خلال النقر على إدخال على سطح المكتب-", + "Only_works_with_chrome_version_greater_50": "يعمل فقط مع إصدارات متصفح كروم الإصدار اكبر من 50", "Only_you_can_see_this_message": "يمكنك أنت فقط رؤية هذه الرسالة", "Oops_page_not_found": "عفوًا ، لم يتم العثور على الصفحة", "Oops!": "عذرا", @@ -1984,6 +2043,7 @@ "People": "الناس", "Permalink": "الرابط الثابت", "Permissions": "التصريحات", + "Personal_Access_Tokens": "رموز الوصول الشخصية", "Phone": "الهاتف", "Pin": "ثبت", "Pin_Message": "تثبيث الرسالة", @@ -2002,7 +2062,7 @@ "PiwikAnalytics_siteId_Description": "هوية الموقع لاستخدامها لتحديد هذا الموقع. على سبيل المثال: 17", "PiwikAnalytics_url_Description": "عنوان الموقع حيث يتواجد Piwik، ومن المؤكد أن تشمل مائلة للتجريب. على سبيل المثال: //piwik.rocket.chat/", "Placeholder_for_email_or_username_login_field": "نائب عن البريد الإلكتروني أو تسجيل الدخول باسم المستخدم المجال", - "Placeholder_for_password_login_field": "نائب عن مجال تسجيل الدخول كلمة المرور", + "Placeholder_for_password_login_field": "عنصر نائب لحقل تسجيل الدخول بكلمة المرور", "Please_add_a_comment": "الرجاء إضافة تعليق", "Please_add_a_comment_to_close_the_room": "يرجى إضافة تعليق لإغلاق الغرفة", "Please_answer_survey": "يرجى ان نتوقف لحظة للرد على مسح سريع حول هذه الدردشة", @@ -2162,6 +2222,7 @@ "RetentionPolicy_AppliesToDMs": "ينطبق على الرسائل المباشرة", "RetentionPolicy_AppliesToGroups": "ينطبق على المجموعات الخاصة", "RetentionPolicy_Description": "تلقائيا prunes الرسائل القديمة عبر مثيل Rocket.Chat الخاص بك.", + "RetentionPolicy_DoNotPruneDiscussion": "لا تقم بتقليم رسائل المناقشة", "RetentionPolicy_Enabled": "مُفعّل", "RetentionPolicy_ExcludePinned": "استبعاد الرسائل المثبتة", "RetentionPolicy_FilesOnly": "فقط حذف الملفات", @@ -2173,6 +2234,8 @@ "RetentionPolicy_MaxAge_Groups": "الحد الأقصى لعمر الرسالة في المجموعات الخاصة", "RetentionPolicy_Precision": "الدقة الموقت", "RetentionPolicy_Precision_Description": "كم مرة يجب أن يتم تشغيل جهاز توقيت التقليم. يؤدي تعيين هذا إلى قيمة أكثر دقة إلى جعل القنوات ذات الموقتات السريعة للاحتفاظ تعمل بشكل أفضل ، ولكنها قد تكلف طاقة معالجة إضافية في المجتمعات الكبيرة.", + "RetentionPolicy_RoomWarning_FilesOnly": null, + "RetentionPolicy_RoomWarning_UnpinnedFilesOnly": null, "RetentionPolicyRoom_Enabled": "تقليم الرسائل القديمة تلقائيا", "RetentionPolicyRoom_ExcludePinned": "استبعاد الرسائل المثبتة", "RetentionPolicyRoom_FilesOnly": "ملفات التقليم فقط ، الاحتفاظ بالرسائل", @@ -2237,7 +2300,7 @@ "SAML_Custom_Private_Key": "محتويات المفتاح الخاص", "SAML_Custom_Provider": "مزود مخصص", "SAML_Custom_Public_Cert": "محتويات الشهادة العامة", - "SAML_Custom_user_data_fieldmap": "العضو بيانات خريطة الميدان", + "SAML_Custom_user_data_fieldmap": "خريطة حقل بيانات المستخدم", "SAML_Section_1_User_Interface": "واجهة المستخدم", "SAML_Section_4_Roles": "أدوار", "Saturday": "السبت", @@ -2282,7 +2345,7 @@ "Send": "إرسال", "Send_a_message": "إرسال رسالة", "Send_a_test_mail_to_my_user": "إرسال بريد إلكتروني إلى اختبار المستخدم الخاص بي", - "Send_a_test_push_to_my_user": "إرسال دفعة اختبار لالمستخدم الخاص بي", + "Send_a_test_push_to_my_user": "إرسال دفعة اختبار للمستخدم الخاص بي", "Send_confirmation_email": "إرسال رسالة تأكيد", "Send_data_into_RocketChat_in_realtime": "إرسال البيانات إلى Rocket.Chat في الوقت الحقيقي.", "Send_email": "إرسال البريد الإلكتروني", @@ -2333,6 +2396,7 @@ "Show_Avatars": "عرض الصور الرمزية", "Show_counter": "إظهار عداد", "Show_email_field": "إظهار حقل البريد الإلكتروني", + "Show_Message_In_Main_Thread": "إظهار رسائل الموضوع في الموضوع الرئيسي", "Show_more": "عرض المزيد", "Show_name_field": "إظهار حقل الاسم", "show_offline_users": "إظهار المستخدمين الموجودون حالياً -أونلاين-", @@ -2368,6 +2432,8 @@ "Slash_Gimme_Description": "يعرض (つ ◕_◕) つ قبل رسالتك", "Slash_LennyFace_Description": "يعرض (͡ ° ͜ʖ ͡ °) بعد رسالتك", "Slash_Shrug_Description": "يعرض ¯ \\ _ (ツ) _ / ¯ بعد رسالتك", + "Slash_Status_Description": "قم بتعيين رسالة الحالة الخاصة بك", + "Slash_Status_Params": "رسالة الحالة", "Slash_Tableflip_Description": "يعرض (╯ □ ° °) ╯( ┻━┻", "Slash_TableUnflip_Description": "يعرض ┬─┬ ノ (゜ - ゜ ノ)", "Slash_Topic_Description": "ضبط الموضوع", @@ -2412,6 +2478,10 @@ "Start_video_call": "بدء مكالمة فيديو", "Start_video_conference": "هل تريد بدء مؤتمر فيديو؟", "Start_with_s_for_user_or_s_for_channel_Eg_s_or_s": "ابدأ بـ %s للمستخدم أو %s للقناة. على سبيل المثال: %s أو %s", + "start-discussion": "ابدأ المناقشة", + "start-discussion_description": "طلب إذن لبدء المناقشة", + "start-discussion-other-user": "بدء المناقشة (مستخدم آخر)", + "start-discussion-other-user_description": "إذن ببدء مناقشة ، والذي يمنح الإذن للمستخدم بإنشاء مناقشة من رسالة أرسلها مستخدم آخر أيضًا", "Started_a_video_call": "بدء مكالمة فيديو", "Started_At": "كتبت في", "Statistics": "الإحصائيات", @@ -2422,7 +2492,7 @@ "Stats_Avg_Private_Group_Users": "متوسط مستخدمي المجموعات الخاصة", "Stats_Away_Users": "المستخدمين البعيدين", "Stats_Max_Room_Users": "أقصى عدد للمستخدمين في الغرف", - "Stats_Non_Active_Users": "المستخدمين الغير نشطين", + "Stats_Non_Active_Users": "المستخدمين الغير نشظين", "Stats_Offline_Users": "المستخدمون الغير متصلون", "Stats_Online_Users": "المستخدمون المتصلون", "Stats_Total_Channels": "مجموع القنوات", @@ -2437,8 +2507,13 @@ "Stats_Total_Rooms": "عدد الغرف", "Stats_Total_Users": "عدد الأعضاء", "Status": "الحالة", + "StatusMessage": "رسالة الحالة", + "StatusMessage_Change_Disabled": "قام مسؤول النظام بتعطيل تغيير رسائل الحالة", + "StatusMessage_Changed_Successfully": "تم تغيير رسالة الحالة بنجاح.", "StatusMessage_Placeholder": "ما الذي تفعله حالياً؟", + "StatusMessage_Too_Long": "يجب أن تكون رسالة الحالة أقصر من 120 حرفًا.", "Step": "خطوة", + "Stop_call": "أوقف المكالمة", "Stop_Recording": "إيقاف التسجيل", "Store_Last_Message": "تخزين آخر رسالة", "Store_Last_Message_Sent_per_Room": "تخزين آخر رسالة أرسلت في كل غرفة.", @@ -2481,6 +2556,7 @@ "The_emails_are_being_sent": "يتم إرسال رسائل البريد الإلكتروني.", "The_field_is_required": "هذا الحقل %s مطلوب.", "The_image_resize_will_not_work_because_we_can_not_detect_ImageMagick_or_GraphicsMagick_installed_in_your_server": "وتغيير الحجم صورة لا تعمل لأننا لا يمكن الكشف عن يماغيماغيك أو GraphicsMagick المثبتة على الخادم الخاص بك.", + "The_message_is_a_discussion_you_will_not_be_able_to_recover": "الرسالة هي مناقشة لن تتمكن من استعادة الرسائل!", "The_redirectUri_is_required": "مطلوب redirectUri", "The_server_will_restart_in_s_seconds": "سيتم إعادة تشغيل الخادم في %s ثانية", "The_setting_s_is_configured_to_s_and_you_are_accessing_from_s": "تم تكوين الإعداد %s إلى %s والتي يتم الوصول من %s!", @@ -2534,6 +2610,7 @@ "There_are_no_applications": "لم تتم إضافة أي تطبيقات أووث حتى الآن.", "There_are_no_applications_installed": "لا توجد حاليًا أي تطبيقات Rocket.Chat مثبتة.", "There_are_no_integrations": "لا توجد التكامل", + "There_are_no_personal_access_tokens_created_yet": "لا توجد رموز وصول شخصية تم إنشاؤها حتى الآن.", "There_are_no_users_in_this_role": "هناك مستخدمين في هذا الدور.", "This_conversation_is_already_closed": "تم إغلاق الدردشة للتو", "This_email_has_already_been_used_and_has_not_been_verified__Please_change_your_password": "وقد تم بالفعل استخدام هذا البريد الإلكتروني ولم يتم التحقق منها. الرجاء قم بتغيير كلمة المرور الخاصة بك.", @@ -2568,6 +2645,7 @@ "Tokens_Required_Input_Error": "الرموز المطبوعة غير الصالحة.", "Tokens_Required_Input_Placeholder": "أسماء أصول الرموز المميزة", "Topic": "الموضوع", + "Total_Discussions": "مجموع المناقشات", "Total_messages": "مجموع الرسائل", "TOTP Invalid [totp-invalid]": "الرمز أو كلمة المرور خاطئة", "totp-invalid": "الرمز أو كلمة المرور خاطئة", @@ -2586,10 +2664,12 @@ "Tuesday": "الثلاثاء", "Turn_OFF": "أطفأ", "Turn_ON": "شغله", - "Two-factor_authentication": "توثيق ذو عاملين", + "Two-factor_authentication": "المصادقة الثنائية عبر TOTP", "Two-factor_authentication_disabled": "تم تعطيل المصادقة الثنائية", + "Two-factor_authentication_email": "المصادقة الثنائية عبر البريد الإلكتروني", + "Two-factor_authentication_email_is_currently_disabled": "المصادقة الثنائية عبر البريد الإلكتروني معطلة حاليًا", "Two-factor_authentication_enabled": "تم تمكين المصادقة الثنائية", - "Two-factor_authentication_is_currently_disabled": "المصادقة بخطوتين غير مُفعّل حالياً", + "Two-factor_authentication_is_currently_disabled": "المصادقة الثنائية عبر TOTP معطلة حاليًا", "Two-factor_authentication_native_mobile_app_warning": "تحذير: بمجرد تمكين هذا، فإنك لن تكون قادرا على تسجيل الدخول على تطبيقات الجوال الأصلي (Rocket.Chat +) باستخدام كلمة المرور الخاصة بك حتى تنفذ 2FA.", "Type": "النوع", "Type_your_email": "اكتب بريدك الالكتروني", @@ -2736,6 +2816,7 @@ "Users_added": "تم إضافة المستخدمين", "Users_in_role": "المستخدمين في دور", "UTF8_Names_Slugify": "UTF8 أسماء Slugify", + "Videocall_enabled": "تم تفعيل الاتصال عبر الفيديو", "Validate_email_address": "التحقق من صحة البريد الإلكتروني", "Verification": "التحقق", "Verification_Description": "يمكنك استخدام العناصر النائبة التالية:
    • [verify_Url] لعنوان ورل للتحقق.
    • [نيم] و [فنيم] و [لنيم] للاسم الكامل للمستخدم أو الاسم الأول أو اسم العائلة، على التوالي.
    • [إمايل] للبريد الإلكتروني للمستخدم.
    • [Site_Name] و [Site_URL] لاسم التطبيق وعنوان ورل على التوالي.
    ", @@ -2751,7 +2832,6 @@ "Video_Conference": "مؤتمر عبر الفيديو", "Video_message": "رسالة فيديو", "Videocall_declined": "تم إلغاء اتصال الفيديو", - "Videocall_enabled": "تم تفعيل الاتصال عبر الفيديو", "View_All": "مشاهدة الكل", "View_Logs": "عرض سجلات", "View_mode": "اسلوب العرض", @@ -2777,7 +2857,7 @@ "view-livechat-rooms_description": "التصريح بعرض قنوات الدردشة المباشرة الأخرى", "view-logs": "عرض السجلات", "view-logs_description": "التصريخ بعرض سجلات الخادم", - "view-other-user-channels": "عرض قنوات المستخدمين الآخرين", + "view-other-user-channels": "عرض Channel المستخدمين الآخرين", "view-other-user-channels_description": "التصريح بعرض قنوات المستخدمين الآخرين", "view-outside-room": "عرض غرفة خارج", "view-p-room": "عرض الغرفة الخاصة", @@ -2876,4 +2956,4 @@ "Your_push_was_sent_to_s_devices": "وقد أرسلت دفعك إلى أجهزة٪ الصورة", "Your_server_link": "رابط الخادم الخاص بك", "Your_workspace_is_ready": "مساحة العمل الخاصة بك جاهزة لاستخدام 🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/az.i18n.json b/packages/rocketchat-i18n/i18n/az.i18n.json index a9dbf99c5b1ca..1c19714ba2f10 100644 --- a/packages/rocketchat-i18n/i18n/az.i18n.json +++ b/packages/rocketchat-i18n/i18n/az.i18n.json @@ -555,6 +555,7 @@ "Continuous_sound_notifications_for_new_livechat_room": "Yeni livechat otağı üçün davamlı səs bildirişləri", "Conversation": "Söhbət", "Conversation_closed": "Söhbət bağlandı: __comment__.", + "Conversation_finished": "Söhbət başa çatdı", "Conversation_finished_message": "Söhbət sona çatdı", "conversation_with_s": "%s ilə söhbət", "Convert_Ascii_Emojis": "ASCII'yi Emoji'ye çevirmək", @@ -2682,6 +2683,7 @@ "Users_added": "İstifadəçilər əlavə edildi", "Users_in_role": "İstifadəçi rolu", "UTF8_Names_Slugify": "UTF8 adları Slugify", + "Videocall_enabled": "Video Zəngləri Enabled", "Validate_email_address": "E-poçt ünvanı təsdiqləyin", "Verification": "Doğrulama", "Verification_Description": "Aşağıdakı yer tutuculardan istifadə edə bilərsiniz: Doğrulama URL'si üçün
    • [Verification_Url].
    • [ad], [fname], [lname] istifadəçinin tam adı, soyadı və soyadı üçün müvafiq olaraq. Istifadəçinin e-poçtu üçün
    • [email].
    • [Site_Name] və [Site_URL] üçün ərizə adı və URL sırasıyla.
    ", @@ -2696,7 +2698,6 @@ "Video_Conference": "Video Konfransı", "Video_message": "Video mesajı", "Videocall_declined": "Video Çağırıldı.", - "Videocall_enabled": "Video Zəngləri Enabled", "View_All": "Bütün üzvləri bax", "View_Logs": "Günlükləri bax", "View_mode": "Görünüş Modu", diff --git a/packages/rocketchat-i18n/i18n/be-BY.i18n.json b/packages/rocketchat-i18n/i18n/be-BY.i18n.json index 6bb78ec728423..32abdbd24c770 100644 --- a/packages/rocketchat-i18n/i18n/be-BY.i18n.json +++ b/packages/rocketchat-i18n/i18n/be-BY.i18n.json @@ -2696,6 +2696,7 @@ "Users_added": "Карыстальнікі, якія былі дададзеныя", "Users_in_role": "Карыстальнікі ў ролі", "UTF8_Names_Slugify": "UTF8 Імёны Slugify", + "Videocall_enabled": "відэазванок Enabled", "Validate_email_address": "Пацвердзіць адрас электроннай пошты", "Verification": "верыфікацыя", "Verification_Description": "Вы можаце выкарыстоўваць наступныя запаўняльнікі:
    • [VERIFICATION_URL] для праверкі URL-адрасы.
    • [імя], [імя_файла], [LNAME] поўнае імя карыстальніка, імя або прозвішча, адпаведна.
    • [пошта] для электроннай пошты карыстальніка.
    • [site_name] і [site_url] для імя прыкладання і URL адпаведна.
    ", @@ -2710,7 +2711,6 @@ "Video_Conference": "відэаканферэнцыя", "Video_message": "відэазварот", "Videocall_declined": "Відэазванок Адхілена.", - "Videocall_enabled": "відэазванок Enabled", "View_All": "Прагляд ўсіх удзельнікаў", "View_Logs": "прагляд часопісаў", "View_mode": "рэжым прагляду", diff --git a/packages/rocketchat-i18n/i18n/bg.i18n.json b/packages/rocketchat-i18n/i18n/bg.i18n.json index 60fe2a3d0dfd3..b334611ec0f31 100644 --- a/packages/rocketchat-i18n/i18n/bg.i18n.json +++ b/packages/rocketchat-i18n/i18n/bg.i18n.json @@ -555,6 +555,7 @@ "Continuous_sound_notifications_for_new_livechat_room": "Непрекъснати звукови известия за нова стая livechat", "Conversation": "разговор", "Conversation_closed": "Разговорът е затворен: __comment__.", + "Conversation_finished": "Разговорът завърши", "Conversation_finished_message": "Готово съобщение за разговор", "conversation_with_s": "разговора с %s", "Convert_Ascii_Emojis": "Конвертиране на ASCII в Emoji", @@ -2680,6 +2681,7 @@ "Users_added": "Потребителите са добавени", "Users_in_role": "Потребителите в ролята", "UTF8_Names_Slugify": "Имената на UTF8", + "Videocall_enabled": "Видеообаждането е активирано", "Validate_email_address": "Потвърдете имейл адреса", "Verification": "Проверка", "Verification_Description": "Можете да използвате следните заместващи символи:
    • [Verification_Url] за URL адреса за потвърждение.
    • [име], [fname], [име] за пълното име, съответно име или фамилия на потребителя.
    • [имейл] за имейла на потребителя.
    • [Site_Name] и [Site_URL] съответно за името на приложението и URL адреса.
    ", @@ -2694,7 +2696,6 @@ "Video_Conference": "Видео конференция", "Video_message": "Видео съобщение", "Videocall_declined": "Отхвърлено видеообаждане.", - "Videocall_enabled": "Видеообаждането е активирано", "View_All": "Преглед на всички членове", "View_Logs": "Преглед на регистрационните файлове", "View_mode": "Режим на преглед", diff --git a/packages/rocketchat-i18n/i18n/bs.i18n.json b/packages/rocketchat-i18n/i18n/bs.i18n.json index f4ab3df626ab5..7ab17eca8410d 100644 --- a/packages/rocketchat-i18n/i18n/bs.i18n.json +++ b/packages/rocketchat-i18n/i18n/bs.i18n.json @@ -2677,6 +2677,7 @@ "Users_added": "Korisnici su dodani", "Users_in_role": "Korisnici u ulozi", "UTF8_Names_Slugify": "UTF8 Imena Slugify", + "Videocall_enabled": "Videopoziv omogućen", "Validate_email_address": "Validiraj email adresu", "Verification": "Verifikacija", "Verification_Description": "Možete upotrebljavati sljedeća rezervirana mjesta:
    • [Verification_Url] za URL za potvrdu.
    • [ime], [fname], [lname] za puni naziv, ime ili prezime korisnika.
    • [e-pošta] za e-poštu korisnika.
    • [Site_Name] i [Site_URL] za naziv aplikacije i URL.
    ", @@ -2691,7 +2692,6 @@ "Video_Conference": "Video Konferencija", "Video_message": "Video poruka", "Videocall_declined": "Videopoziv odbijen", - "Videocall_enabled": "Videopoziv omogućen", "View_All": "Prikaži Sve", "View_Logs": "Pogledaj izvještaje", "View_mode": "Pregled", diff --git a/packages/rocketchat-i18n/i18n/ca.i18n.json b/packages/rocketchat-i18n/i18n/ca.i18n.json index 2521b64ca277a..a1360d4734cfe 100644 --- a/packages/rocketchat-i18n/i18n/ca.i18n.json +++ b/packages/rocketchat-i18n/i18n/ca.i18n.json @@ -25,7 +25,7 @@ "Accept_with_no_online_agents": "Acceptar sense agents en línia", "Access_not_authorized": "Accés no autoritzat", "Access_Token_URL": "URL Access Token", - "access-mailer": "Accedir a la pantalla d'enviament", + "access-mailer": "Accedir a la pantalla de correu", "access-mailer_description": "Permís per enviar correu-e massiu a tots els usuaris", "access-permissions": "Accés a la pantalla de permisos", "access-permissions_description": "Modifica permisos per a diversos rols", @@ -62,12 +62,12 @@ "Accounts_BlockedDomainsList": "Llista de dominis bloquejats", "Accounts_BlockedDomainsList_Description": "Llista de dominis bloquejats separada per comes", "Accounts_BlockedUsernameList": "Llista de noms d'usuari bloquejats", - "Accounts_BlockedUsernameList_Description": "Llista separada per comes de noms d'usuari bloquejats (no distingeix majúscules/minúscules)", + "Accounts_BlockedUsernameList_Description": "Llista de noms d'usuaris bloquejats separada per comes (no distingeix majúscules i minúscules)", "Accounts_CustomFields_Description": "Ha de ser un JSON vàlid, on les claus són els noms dels camps que contenen un diccionari de configuració de camps. Exemple:
    {\n \"role\": {\n \"type\": \"select\",\n \"defaultValue\": \"student\",\n \"options\": [\"teacher\", \"student\"],\n \"required\": true,\n \"modifyRecordField\": {\n \"array\": true,\n \"field\": \"roles\"\n }\n },\n \"twitter\": {\n \"type\": \"text\",\n \"required\": true,\n \"minLength\": 2,\n \"maxLength\": 10\n }\n} ", "Accounts_CustomFieldsToShowInUserInfo": "Camps personalitzats a mostrar a l'informació d'usuari", "Accounts_Default_User_Preferences": "Preferències d'usuari per defecte", "Accounts_Default_User_Preferences_audioNotifications": "Alerta de notificacions d'àudio per defecte", - "Accounts_Default_User_Preferences_desktopNotifications": "Alerta per defecte per a les notificacions d'escriptori", + "Accounts_Default_User_Preferences_desktopNotifications": "Alerta de notificacions d'escriptori per defecte", "Accounts_Default_User_Preferences_pushNotifications": "Alerta per defecte notificacions mòbil", "Accounts_Default_User_Preferences_not_available": "No es van poder recuperar les preferències de l'usuari perquè l'usuari encara no les ha configurat", "Accounts_DefaultUsernamePrefixSuggestion": "Prefix suggerit per al nom d'usuari per defecte", @@ -114,13 +114,15 @@ "Accounts_OAuth_Custom_Merge_Users": "Uneix usuaris", "Accounts_OAuth_Custom_Name_Field": "Camp de nom", "Accounts_OAuth_Custom_Roles_Claim": "Nom del camp Rols / Grups", + "Accounts_OAuth_Custom_Roles_To_Sync": "Rols per sincronitzar", + "Accounts_OAuth_Custom_Roles_To_Sync_Description": "Rols d'OAuth per sincronitzar a l'inici de sessió i la creació de l'usuari (separats per comes).", "Accounts_OAuth_Custom_Scope": "Àmbit (scope)", "Accounts_OAuth_Custom_Secret": "Secret", "Accounts_OAuth_Custom_Show_Button_On_Login_Page": "Mostra botó a la pàgina d'inici de sessió", "Accounts_OAuth_Custom_Token_Path": "Ruta del token", "Accounts_OAuth_Custom_Token_Sent_Via": "Token enviat via", "Accounts_OAuth_Custom_Username_Field": "Camp de nom d'usuari", - "Accounts_OAuth_Drupal": "Activa inici de sessió de Drupal", + "Accounts_OAuth_Drupal": "Inici de sessió de Drupal habilitat", "Accounts_OAuth_Drupal_callback_url": "Redirect URI de Drupal oAuth2", "Accounts_OAuth_Drupal_id": "Client ID de Drupal oAuth2", "Accounts_OAuth_Drupal_secret": "Client Secret de Drupal oAuth2", @@ -161,7 +163,7 @@ "Accounts_OAuth_Nextcloud_URL": "URL del servidor de Nextcloud", "Accounts_OAuth_Proxy_host": "Host del servidor intermediari (proxy)", "Accounts_OAuth_Proxy_services": "Serveis del servidor intermediari (proxy)", - "Accounts_OAuth_Tokenpass": "Tokenpass Login", + "Accounts_OAuth_Tokenpass": "Inici de sessió amb Tokenpass", "Accounts_OAuth_Tokenpass_callback_url": "Tokenpass Callback URL", "Accounts_OAuth_Tokenpass_id": "Tokenpass Id", "Accounts_OAuth_Tokenpass_secret": "Tokenpass Secret", @@ -173,7 +175,7 @@ "Accounts_OAuth_Wordpress_authorize_path": "Ruta d'autorització", "Accounts_OAuth_Wordpress_callback_url": "URL de retorn (callback) de WordPress", "Accounts_OAuth_Wordpress_id": "WordPress ID", - "Accounts_OAuth_Wordpress_identity_path": "Ruta de la identitat", + "Accounts_OAuth_Wordpress_identity_path": "Ruta d'identitat", "Accounts_OAuth_Wordpress_identity_token_sent_via": "Token d'identitat enviat mitjançant", "Accounts_OAuth_Wordpress_scope": "Scope", "Accounts_OAuth_Wordpress_secret": "WordPress Secret", @@ -200,7 +202,7 @@ "Accounts_Password_Policy_MinLength": "Longitud mínima", "Accounts_Password_Policy_MinLength_Description": "Assegura que les contrasenyes han de tenir al menys aquesta quantitat de caràcters. Utilitza `-1` per desactivar.", "Accounts_PasswordReset": "Restablir contrasenya", - "Accounts_Registration_AuthenticationServices_Default_Roles": "Rols per defecte per als serveis d'autenticació", + "Accounts_Registration_AuthenticationServices_Default_Roles": "Rols predeterminats per a Serveis d'Autenticació", "Accounts_Registration_AuthenticationServices_Default_Roles_Description": "Rols per defecte (separats per comes) que s'assignaran als usuaris quan es registrin a través dels serveis d'autenticació", "Accounts_Registration_AuthenticationServices_Enabled": "Registre mitjançant serveis d'autenticació", "Accounts_Registration_Users_Default_Roles": "Rols predeterminats per als usuaris", @@ -214,31 +216,31 @@ "Accounts_RegistrationForm_LinkReplacementText": "Text de substitució de l'enllaç del formulari de registre", "Accounts_RegistrationForm_Public": "Públic", "Accounts_RegistrationForm_Secret_URL": "URL secret", - "Accounts_RegistrationForm_SecretURL": "URL secret del fomulari de registre", - "Accounts_RegistrationForm_SecretURL_Description": "Cal proporcionar una cadena de text aleatori que s'afegirà a l'URL de registre. Exemple: https://open.rocket.chat/register/[secret_hash]", + "Accounts_RegistrationForm_SecretURL": "URL Secret del Fomulari de Registre", + "Accounts_RegistrationForm_SecretURL_Description": "Heu de proporcionar una cadena de text aleatori que s'afegirà a la URL de registre. Exemple: https://open.rocket.chat/register/[secret_hash]", "Accounts_RequireNameForSignUp": "Requerir el nom per registrar-se", "Accounts_RequirePasswordConfirmation": "Requereix confirmació de la contrasenya", "Accounts_RoomAvatarExternalProviderUrl": "Room URL de l'proveïdor extern d'Avatar", "Accounts_RoomAvatarExternalProviderUrl_Description": "Exemple: `https://acme.com/api/v1/{roomId}`", "Accounts_SearchFields": "Camps a considerar a la cerca", - "Accounts_Send_Email_When_Activating": "Enviar un correu electrònic a l'usuari quan estigui activat", + "Accounts_Send_Email_When_Activating": "EEnviar correu electrònic a l'usuari quan l'usuari està activat", "Accounts_Send_Email_When_Deactivating": "Enviar un correu electrònic a l'usuari quan estigui desactivat", - "Accounts_Set_Email_Of_External_Accounts_as_Verified": "Establir el correu electrònic dels comptes externes com verificat", + "Accounts_Set_Email_Of_External_Accounts_as_Verified": "Establir el correu electrònic dels comptes externs com a verificat", "Accounts_Set_Email_Of_External_Accounts_as_Verified_Description": "Els correus electrònics de comptes creats des de serveis externs, com LDAP, OAuth, etc., es marcaran com verificats automàticament.", "Accounts_SetDefaultAvatar": "Avatar per defecte", "Accounts_SetDefaultAvatar_Description": "Prova de determinar l'avatar per defecte basant-se en el compte d'OAuth o bé Gravatar", - "Accounts_ShowFormLogin": "Mostrar el formulari d'inici de sessió per defecte", + "Accounts_ShowFormLogin": "Mostra formulari d'inici de sessió per defecte", "Accounts_TwoFactorAuthentication_By_TOTP_Enabled": "Habiliteu l'autenticació de dos factors a través de TOTP", "Accounts_TwoFactorAuthentication_By_TOTP_Enabled_Description": "Els usuaris poden configurar el seu autenticació de dos factors utilitzant qualsevol aplicació TOTP, com Google Authenticator o Authy.", "Accounts_TwoFactorAuthentication_By_Email_Auto_Opt_In": "Establir segon factor d'autenticació via correu electrònic per defecte per a nous usuaris", "Accounts_TwoFactorAuthentication_By_Email_Auto_Opt_In_Description": "els nous usuaris tindran activat per defecte el segon factor d'autenticació per correu electrònic. Podran desactivar-lo en la seva pàgina de perfil.", "Accounts_TwoFactorAuthentication_By_Email_Code_Expiration": "Temps de caducitat del codi enviat per correu electrònic en segons", "Accounts_TwoFactorAuthentication_By_Email_Enabled": "Habilitar segon factor d'autenticació via correu electrònic", - "Accounts_TwoFactorAuthentication_By_Email_Enabled_Description": "Els usuaris amb correu electrònic verificat i la opció habilitada a la seva pàgina de perfil rebran un correu electrònic amb un codi temporal per autoritzar certes accions com iniciar sessió, desar el perfil, etc.", + "Accounts_TwoFactorAuthentication_By_Email_Enabled_Description": "Els usuaris amb correu electrònic verificat i l'opció habilitada a la pàgina de perfil rebran un correu electrònic amb un codi temporal per autoritzar certes accions com iniciar sessió, desar el perfil, etc.", "Accounts_TwoFactorAuthentication_Enabled": "Habilitar l'autenticació de dos factors a través d'TOTP", "Accounts_TwoFactorAuthentication_Enabled_Description": "Els usuaris poden configurar el seu segon factor d'autenticació.
    fent servir qualsevol aplicació TOTP, com Google Authenticator o Authy", "Accounts_TwoFactorAuthentication_Enforce_Password_Fallback": "Aplicar suport de contrasenya", - "Accounts_TwoFactorAuthentication_Enforce_Password_Fallback_Description": "Els usuaris es veuran obligats a ingressar la contrasenya, per a accions importants, si no s'habilita cap altre mètode d'autenticació de segon factor per a aquest usuari i s'estableix una contrasenya per a ell.", + "Accounts_TwoFactorAuthentication_Enforce_Password_Fallback_Description": "Els usuaris es veuran obligats a ingressar la contrasenya per a accions importants si no s'habilita cap altre mètode d'autenticació de dos factors per a aquest usuari i s'estableix una contrasenya per a ell.", "Accounts_TwoFactorAuthentication_MaxDelta": "Delta màxima", "Accounts_TwoFactorAuthentication_MaxDelta_Description": "El Maximum Delta determina quants tokens són vàlids en un moment donat. Els tokens es generen cada 30 segons i són vàlids per a (30 * Maximum Delta) segons.
    Exemple: amb un Delta màxim establert a 10, cada token es pot utilitzar fins a 300 segons abans o després de la marca de temps. Això és útil quan el rellotge del client no està correctament sincronitzat amb el servidor.", "Accounts_TwoFactorAuthentication_RememberFor": "Recordar segon factor d'autenticació durant (segons)", @@ -248,14 +250,14 @@ "Accounts_UserAddedEmail_Default": "

    Benvingut a [Site_Name]

    Vés a [Site_URL] i prova la millor eina de programari lliure per a treball a distància disponible actualment!

    Pots entrar utilitzant el teu correu-e: [email] i contrasenya: [password]. És possible que et demanem canviar-la quan entris per primera vegada.", "Accounts_UserAddedEmail_Description": "És possible utilitzar els marcadors:

    • [name], [fname], [lname] per al nom complet de l'usuari, nom o cognom, respectivament.
    • [email] per a l'adreça de correu electrònic de l'usuari.
    • [password] per la contrasenya.
    • [Site_Name] i [Site_URL] pel nom del lloc web i de l'adreça URL, respectivament.
    ", "Accounts_UserAddedEmailSubject_Default": "Se t'ha afegit a [Site_Name]", - "Accounts_Verify_Email_For_External_Accounts": "Verificar el correu electrònic per als comptes externes", + "Accounts_Verify_Email_For_External_Accounts": "Verificar el correu electrònic per als comptes externs", "Action": "Acció", "Action_required": "Acció requerida", "Activate": "Activa", "Active": "Actiu", "Active_users": "Usuaris actius", "Activity": "Activitat", - "Add": "Afegeix", + "Add": "Afegir", "Add_agent": "Afegeix agent", "Add_custom_emoji": "Afageix emoji personalitzat", "Add_custom_oauth": "Afegeix OAuth personalitzat", @@ -266,7 +268,7 @@ "Add_Reaction": "Afegeix reacció", "Add_Role": "Afegeix rol", "Add_Sender_To_ReplyTo": "Afegir remitent per respondre", - "Add_user": "Afegeix usuari", + "Add_user": "Afegir usuari", "Add_User": "Afegeix usuari", "Add_users": "Afegeix usuaris", "Add_members": "Afegir membres", @@ -278,7 +280,7 @@ "add-user_description": "Permís per afegir nous usuaris al servidor via la pantalla d'usuaris", "add-user-to-any-c-room": "Afegir usuari a canal públic", "add-user-to-any-c-room_description": "Permís per afegir un usuari a qualsevol canal públic", - "add-user-to-any-p-room": "Afegir usuari a canal privat", + "add-user-to-any-p-room": "Afegir usuari a qualsevol Channel privat", "add-user-to-any-p-room_description": "Permís per afegir un usuari a qualsevol canal privat", "add-user-to-joined-room": "Afegir usuari a canal on unit", "add-user-to-joined-room_description": "Permís per afegir un usuari a un canal on està unit", @@ -287,14 +289,14 @@ "Adding_user": "Afegint usuari", "Additional_emails": "Correus electrònics addicionals", "Additional_Feedback": "Retroalimentació addicional", - "additional_integrations_Bots": "Si esteu buscant com integrar el vostre bot, no busqueu més, el nostre adaptador Hubot. https://github.com/RocketChat/hubot-rocketchat", + "additional_integrations_Bots": "Si esteu buscant com integrar el vostre propi bot, no busqueu més que el nostre adaptador Hubot. https://github.com/RocketChat/hubot-rocketchat", "additional_integrations_Zapier": "Està buscant integrar un altre programari i aplicacions amb Rocket.Chat però no té temps per fer-ho manualment? Llavors, suggerim utilitzar Zapier, que és totalment compatible. Llegiu més sobre això a la nostra documentació https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_disabled_encryption": "El seu administrador no va habilitar encriptació E2E", "Admin_Info": "Informació d'administrador", "Administration": "Administració", "Adult_images_are_not_allowed": "Les imatges per a adults no són permeses", "Aerospace_and_Defense": "Aeroespacial i Defensa", - "After_OAuth2_authentication_users_will_be_redirected_to_this_URL": "Després de l'autenticació OAuth2, els usuaris seran redirigits a aquest URL. Pots afegir un URL per línia.", + "After_OAuth2_authentication_users_will_be_redirected_to_this_URL": "Després de l'autenticació OAuth2, els usuaris seran redirigits a una URL en aquesta llista. Podeu afegir un URL per línia.", "Agent": "Agent", "Agent_added": "Agent afegit", "Agent_Info": "Informació de l'agent", @@ -352,7 +354,7 @@ "API_Drupal_URL": "Adreça URL del servidor de Drupal", "API_Drupal_URL_Description": "Exemple: https://domini.com (sense la barra final)", "API_Embed": "Incrusta (embed)", - "API_Embed_Description": "Activa o no les previsualitzacions d'enllaços quan un usuari publica l'enllaç a un web.", + "API_Embed_Description": "Si les vistes prèvies denllaços incrustats estan habilitades o no quan un usuari publica un enllaç a un lloc web.", "API_Embed_UserAgent": "Incrusta user agent de la consulta", "API_EmbedCacheExpirationDays": "Caducitat de la memòria cau de les incrustacions (en dies)", "API_EmbedDisabledFor": "Deshabilitar la incrustació per als usuaris", @@ -370,6 +372,8 @@ "API_Enable_Rate_Limiter_Dev": "Habilitar el limitador de freqüència en desenvolupament", "API_Enable_Rate_Limiter_Dev_Description": "Hauria limitar la quantitat de trucades als punts finals en l'entorn de desenvolupament?", "API_Enable_Rate_Limiter_Limit_Calls_Default": "Nombre de trucades per defecte al limitador de velocitat", + "Rate_Limiter_Limit_RegisterUser": "Trucades de números predeterminats al limitador de velocitat per registrar un usuari", + "Rate_Limiter_Limit_RegisterUser_Description": "Nombre de trucades predeterminades per a usuaris que registren punts finals (REST i API en temps real), permeses dins del rang de temps definit a la secció API Rate Limiter.", "API_Enable_Rate_Limiter_Limit_Calls_Default_Description": "Nombre de trucades predeterminades per a cada punt final de l'API REST, permeses dins de la franja de temps definit a continuació", "API_Enable_Rate_Limiter_Limit_Time_Default": "Límit de temps predeterminat per al limitador de freqüència (en ms)", "API_Enable_Rate_Limiter_Limit_Time_Default_Description": "Temps d'espera per defecte per limitar el nombre de trucades en cada punt final de l'API REST (en ms)", @@ -385,16 +389,17 @@ "API_Personal_Access_Tokens_Regenerate_Modal": "Si va perdre o va oblidar el vostre token, pot tornar a generar-lo, però recordeu que totes les aplicacions que fan servir aquest token s'han d'actualitzar", "API_Personal_Access_Tokens_Remove_Modal": "Esteu segur que voleu eliminar aquest Token d'accés personal?", "API_Personal_Access_Tokens_To_REST_API": "Tokens d'accés personal a l'API REST", + "API_Rate_Limiter": "Limitador de taxa API", "API_Shield_Types": "Tipus d'escut", "API_Shield_Types_Description": "Tipus d'escut que s'activaran, com a llista separada per comes. Triar entre `online`, `channel` o `*` per a tots", "API_Shield_user_require_auth": "Requereix autenticació per als escuts dels usuaris", "API_Token": "API Token", "API_Tokenpass_URL": "URL del servidor Tokenpass", "API_Tokenpass_URL_Description": "Exemple: https://domain.com (excloent la barra inclinada final)", - "API_Upper_Count_Limit": "Nombre màxim de registres", + "API_Upper_Count_Limit": "Quantitat màxima de registre", "API_Upper_Count_Limit_Description": "Quin és el nombre màxim de registres que la API REST pot retornar (si no és il·limitat)?", "API_Use_REST_For_DDP_Calls": "Utilitza REST en lloc de websocket per a les trucades de Meteor", - "API_User_Limit": "Límit d'usuaris per afegir tots els usuaris a canal", + "API_User_Limit": "Límit d'usuari per afegir tots els usuaris a Channel", "API_Wordpress_URL": "URL de WordPress", "api-bypass-rate-limit": "Límit de velocitat d'omissió per a la API REST", "api-bypass-rate-limit_description": "Permís per trucar a l'API sense limitació de tarifes", @@ -408,7 +413,7 @@ "App_status_auto_enabled": "Actiu", "App_status_constructed": "Construït", "App_status_disabled": "Inactiu", - "App_status_error_disabled": "Desactivat: error no capturat", + "App_status_error_disabled": "Deshabilitat: error desconegut", "App_status_initialized": "Inicialitzat", "App_status_invalid_license_disabled": "Desactivat: Llicència no vàlida", "App_status_invalid_settings_disabled": "Deshabilitat: la configuració necessària", @@ -452,16 +457,16 @@ "Apps_Interface_IPostRoomDeleted": "Esdeveniment que passa després que una sala és eliminada", "Apps_Interface_IPostRoomUserJoined": "Esdeveniment que passa després que un usuari s'uneixi a una sala (pública, privada)", "Apps_Interface_IPreMessageDeletePrevent": "Esdeveniment que passa després que un missatge és eliminat", - "Apps_Interface_IPreMessageSentExtend": "Esdeveniment que passa abans que un missatge és enviat", + "Apps_Interface_IPreMessageSentExtend": "Esdeveniment que passa abans que s'enviï un missatge", "Apps_Interface_IPreMessageSentModify": "Esdeveniment que passa abans que un missatge és enviat", - "Apps_Interface_IPreMessageSentPrevent": "Esdeveniment que passa abans que un missatge és enviat", + "Apps_Interface_IPreMessageSentPrevent": "Esdeveniment que passa abans que s'enviï un missatge", "Apps_Interface_IPreMessageUpdatedExtend": "Esdeveniment que passa abans que un missatge és actualitzat", - "Apps_Interface_IPreMessageUpdatedModify": "Esdeveniment que passa abans que un missatge és actualitzat", + "Apps_Interface_IPreMessageUpdatedModify": "Esdeveniment que passa abans que s'actualitzi un missatge", "Apps_Interface_IPreMessageUpdatedPrevent": "Esdeveniment que passa abans que un missatge és actualitzat", "Apps_Interface_IPreRoomCreateExtend": "Esdeveniment que passa abans que una sala és creada", "Apps_Interface_IPreRoomCreateModify": "Esdeveniment que passa abans que una sala és creada", - "Apps_Interface_IPreRoomCreatePrevent": "Esdeveniment que passa abans que una sala és creada", - "Apps_Interface_IPreRoomDeletePrevent": "Esdeveniment que passa abans que una sala és eliminada", + "Apps_Interface_IPreRoomCreatePrevent": "Esdeveniment que passa abans que es creï una sala", + "Apps_Interface_IPreRoomDeletePrevent": "Esdeveniment que passa abans que s'elimini una sala", "Apps_Interface_IPreRoomUserJoined": "Esdeveniment que passa abans que un usuari s'uneixi a una sala (pública, privada)", "Apps_License_Message_appId": "No s'ha emès la llicència per a aquesta aplicació.", "Apps_License_Message_bundle": "Llicència emesa per un paquet que no conté l'aplicació", @@ -476,8 +481,8 @@ "Apps_Logs_TTL_30days": "30 dies", "Apps_Logs_TTL_Alert": "Depenent de la mida de la col·lecció de registres, canviar aquesta configuració pot causar lentitud per alguns moments", "Apps_Marketplace_Deactivate_App_Prompt": "Vol realment desactivar aquesta aplicació?", - "Apps_Marketplace_Login_Required_Description": "Comprar aplicacions de l'Marketplace Rocket.Chat requereix registrar el teu entorn de treball i iniciar sessió.", - "Apps_Marketplace_Login_Required_Title": "Identificació requerida al Marketplace", + "Apps_Marketplace_Login_Required_Description": "Comprar aplicacions de Rocket.Chat Marketplace requereix registrar el vostre espai de treball i iniciar sessió.", + "Apps_Marketplace_Login_Required_Title": "Es requereix l'inici de sessió al Marketplace", "Apps_Marketplace_Modify_App_Subscription": "Modificar la Subscripció", "Apps_Marketplace_pricingPlan_monthly": "__price__ / mes", "Apps_Marketplace_pricingPlan_monthly_perUser": "__price__ / mes per usuari", @@ -488,7 +493,7 @@ "Apps_Marketplace_pricingPlan_yearly": "__price__ / any", "Apps_Marketplace_pricingPlan_yearly_perUser": "__price__ / any per usuari", "Apps_Marketplace_Uninstall_App_Prompt": "Vols realment desinstal·lar aquesta aplicació?", - "Apps_Marketplace_Uninstall_Subscribed_App_Anyway": "Desinstal·leu-la de totes maneres", + "Apps_Marketplace_Uninstall_Subscribed_App_Anyway": "Desinstal·lar de totes maneres", "Apps_Marketplace_Uninstall_Subscribed_App_Prompt": "Aquesta aplicació té una subscripció activa i la desinstal·lació no la cancel·la. Si voleu fer això, modifiqui la seva subscripció abans de desinstal·lar.", "Apps_Permissions_Review_Modal_Title": "Permisos necessaris", "Apps_Permissions_Review_Modal_Subtitle": "Aquesta aplicació vol accedir als permisos següents. Estàs d'acord?", @@ -550,7 +555,7 @@ "assign-roles": "Assignar rols", "assign-roles_description": "Permís per assignar rols a altres usuaris", "at": "a", - "At_least_one_added_token_is_required_by_the_user": "A l'almenys un dels tokens afegits és requerit per l'usuari", + "At_least_one_added_token_is_required_by_the_user": "L'usuari requereix com a mínim un token agregat", "AtlassianCrowd": "Atlassian Crowd", "Attachment_File_Uploaded": "Fitxer pujat", "Attribute_handling": "Tractament d'atributs", @@ -569,9 +574,9 @@ "Authorize": "Autoritzar", "Auto_Load_Images": "Carregar automàticament les imatges", "Auto_Selection": "Selecció automàtica", - "Auto_Translate": "Autotraducció", - "auto-translate": "Auto-traducció", - "auto-translate_description": "Permís per utilitzar l'eina d'auto-traducció", + "Auto_Translate": "Traducció automàtica", + "auto-translate": "Traducció automàtica", + "auto-translate_description": "Permís per fer servir l'eina de traducció automàtica", "AutoLinker": "Enllaç automàtic", "AutoLinker_Email": "Auto-enllaça correu-e", "AutoLinker_Phone": "Auto-enllaça telèfon", @@ -585,7 +590,7 @@ "Automatic_Translation": "Traducció automàtica", "AutoTranslate": "Autotraducció", "AutoTranslate_APIKey": "Clau API", - "AutoTranslate_Change_Language_Description": "Canviar l'idioma d'autotraducció no traduirà els missatges anteriors.", + "AutoTranslate_Change_Language_Description": "Canviar l'idioma de traducció automàtica no tradueix els missatges anteriors.", "AutoTranslate_DeepL": "DeepL", "AutoTranslate_Enabled": "Activa autotraducció", "AutoTranslate_Enabled_Description": "L'activació de la traducció automàtica permetrà a les persones amb el permís traduir automàticament el permís de traduir tots els missatges automàticament al seu idioma seleccionat. Es poden aplicar tarifes.", @@ -600,10 +605,10 @@ "Avatar_changed_successfully": "Avatar canviat correctament", "Avatar_URL": "URL de l'avatar", "Avatar_url_invalid_or_error": "L'adreça URL proporcionada és invàlida o no accessible. Si us plau, torneu-ho a intentar amb una altra.", - "Avg_chat_duration": "Durada mitjana del xat", + "Avg_chat_duration": "Mitjana de durada del xat", "Avg_first_response_time": "Mitjana del temps de la primera resposta", "Avg_of_abandoned_chats": "Mitjana de xats abandonats", - "Avg_of_available_service_time": "Mitjana del temps de servei disponible", + "Avg_of_available_service_time": "Mitjana del temps disponible del servei", "Avg_of_chat_duration_time": "Mitjana del temps de durada del xat", "Avg_of_service_time": "Mitjana del temps de servei", "Avg_of_waiting_time": "Mitjana del temps d’espera", @@ -646,10 +651,10 @@ "Block_Multiple_Failed_Logins_By_Ip": "Bloquejar els intents fallits d'accés per IP", "Block_Multiple_Failed_Logins_By_User": "Bloquejar els intents fallits d'accés per nom d'usuari", "Block_Multiple_Failed_Logins_Enable_Collect_Login_data_Description": "Emmagatzemar la IP i el nom d'usuari dels intents d'accés en una col·lecció a la base de dades", - "Block_Multiple_Failed_Logins_Enabled": "Habilitar la recopilació de dades d'inici de sessió", + "Block_Multiple_Failed_Logins_Enabled": "Habilitar recopilar dades d'inici de sessió", "Block_Multiple_Failed_Logins_Ip_Whitelist": "Llista blanca d'IP", "Block_Multiple_Failed_Logins_Ip_Whitelist_Description": "Llista separada per comes d'IP permeses", - "Block_Multiple_Failed_Logins_Time_To_Unblock_By_Ip_In_Minutes": "Temps per desbloquejar la IP (En minuts)", + "Block_Multiple_Failed_Logins_Time_To_Unblock_By_Ip_In_Minutes": "Temps per desbloquejar IP (en minuts)", "Block_Multiple_Failed_Logins_Time_To_Unblock_By_User_In_Minutes": "Temps per desbloquejar a l'usuari (en minuts)", "Block_Multiple_Failed_Logins_Notify_Failed": "Notificació d'intents fallits d'inici de sessió", "Block_Multiple_Failed_Logins_Notify_Failed_Channel": "Channel per enviar les notificacions", @@ -676,8 +681,8 @@ "Broadcasting_enabled": "Transmissió activada", "Broadcasting_media_server_url": "URL del servidor de mitjans de transmissió", "Browse_Files": "Cerca de fitxers", - "Browser_does_not_support_audio_element": "El seu navegador no suporta l'element d'àudio.", - "Browser_does_not_support_video_element": "El seu navegador no suporta l'element de vídeo.", + "Browser_does_not_support_audio_element": "El vostre navegador no és compatible amb l'element d'àudio.", + "Browser_does_not_support_video_element": "El vostre navegador no és compatible amb l'element de vídeo.", "Bugsnag_api_key": "Clau API Bugsnag", "Build_Environment": "Entorn de compilació", "bulk-register-user": "Crear usuaris de forma massiva", @@ -701,6 +706,9 @@ "By_author": "Per __author__", "cache_cleared": "Memòria cau esborrada", "Call": "Trucada", + "Call_declined": "Trucada rebutjada!", + "Call_provider": "Proveïdor de trucades", + "Call_Already_Ended": "Trucada ja finalitzada", "call-management": "Gestió de trucades", "call-management_description": "Permís per iniciar una reunió", "Caller": "Emissor", @@ -715,13 +723,13 @@ "Canned_Response_Sharing_Private_Description": "Només vostè i els administradors de livechat poden accedir a aquesta resposta predefinida", "Canned_Response_Sharing_Public_Description": "Qualsevol pot accedir a aquesta resposta predefinida", "Canned_Responses": "Resposta predefinida", - "Canned_Responses_Enable": "Activa resposta predefinida", + "Canned_Responses_Enable": "Habilitar resposta predefinida", "Create_your_First_Canned_Response": "Crea primera resposta predefinida", "Cannot_invite_users_to_direct_rooms": "No es pot convidar els usuaris a les sales directes", "Cannot_open_conversation_with_yourself": "No es pot obrir una conversa amb un mateix", "Cannot_share_your_location": "No es posible compartir la ubicació ...", "CAS_autoclose": "Finestra emergent de tancament automàtic de sessió", - "CAS_base_url": "Adreça URL SSO base", + "CAS_base_url": "URL base de SSO", "CAS_base_url_Description": "Adreça URL base del servei extern SSO. Ex: https://sso.example.undef/sso/", "CAS_button_color": "Color de fons del botó d'inici de sessió", "CAS_button_label_color": "Color de text del botó d'inici de sessió", @@ -739,7 +747,7 @@ "CAS_Sync_User_Data_FieldMap": "Mapa d'atributs", "CAS_Sync_User_Data_FieldMap_Description": "Utilitza aquesta entrada JSON per construir atributs interns (claus) des d'atributs externs (valors). Els valors d'atributs externs que continguin '%' seran interpolats com a cadenes de caràcters del valor.
    Per exemple, `{\"email\":\"%email%\", \"nom\":\"%nom%, %cognoms%\"}`

    El mapa d'atributs sempre s'interpolarà. La versió CAS 1.0 només permet l'atribut `username`. Els atributs interns disponibles són: username, name, email, rooms; rooms és una llista separada per comes de sales a unir-se durant la creació d'un usuari. Ex: {\"rooms\": \"%team%,%department%\"} uniria els nous usuaris CAS creats a les sales del seu equip (team) i departament (department).", "CAS_trust_username": "Confiar en el nom d'usuari de CAS", - "CAS_trust_username_description": "Quan està habilitat, Rocket.Chat confiarà en que qualsevol nom d'usuari de CAS pertany a el mateix usuari en Rocket.Chat.
    Això pot ser necessari si es canvia el nom d'un usuari en CAS, però també pot permetre que les persones prenguin el control de Rocket. Converseu els comptes canviant el nom dels seus propis usuaris CAS", + "CAS_trust_username_description": "Quan està habilitat, Rocket.Chat confiarà que qualsevol nom d'usuari de CAS pertany al mateix usuari a Rocket.Chat.
    Això pot ser necessari si es canvia el nom d'un usuari a CAS, però també pot permetre que les persones prenguin el control de Rocket. Xategeu els comptes canviant el nom dels vostres propis usuaris de CAS.", "CAS_version": "Versió CAS", "CAS_version_Description": "Només utilitzis una versió CAS suportada pel teu servei CAS SSO.", "Categories": "Categories", @@ -760,7 +768,7 @@ "Channel_created": "Canal `#%s` creat.", "Channel_doesnt_exist": "El canal `#%s` no existeix", "Channel_Export": "Exportar canal", - "Channel_name": "Nom del canal", + "Channel_name": "Nom del Channel", "Channel_Name_Placeholder": "Sisplau, introdueix el nom de canal...", "Channel_to_listen_on": "Canal on escoltar", "Channel_Unarchived": "El canal `#%s` s'ha desarxivat correctament.", @@ -770,7 +778,7 @@ "Channels_list": "Llista de canals públics", "Channel_what_is_this_channel_about": "De què tracta aquest canal?", "Chart": "Gràfic", - "Chat_button": "botó de xat", + "Chat_button": "Botó de xat", "Chat_close": "Tancar Xat", "Chat_closed": "Xat tancat", "Chat_closed_by_agent": "Xat tancat per l'agent", @@ -802,7 +810,7 @@ "Chatpal_Batch_Size_Description": "La mida del lot dels documents d'índex (en l'arrencada)", "Chatpal_channel_not_joined_yet": "Channel encara no s'ha unit", "Chatpal_create_key": "Crea una clau", - "Chatpal_created_key_successfully": "La clau de l'API s'ha creat correctament", + "Chatpal_created_key_successfully": "API-Key creada amb èxit", "Chatpal_Current_Room_Only": "Mateixa sala", "Chatpal_Default_Result_Type": "Tipus de resultat per defecte", "Chatpal_Default_Result_Type_Description": "Defineix quin tipus de resultat es mostra per resultat. Tot vol dir que es proporciona una descripció general de tots els tipus.", @@ -850,10 +858,10 @@ "Choose_the_alias_that_will_appear_before_the_username_in_messages": "Tria l'àlies que apareixerà abans del nom d'usuari als missatges.", "Choose_the_username_that_this_integration_will_post_as": "Tria el nom d'usuari amb el qual aquesta integració publicarà.", "Choose_users": "Trieu usuaris", - "Clean_Usernames": "Esborreu noms d'usuari", - "clean-channel-history": "Esborrar l'historial de canal", + "Clean_Usernames": "Esborrar noms d'usuari", + "clean-channel-history": "Esborrar l'historial de Channel", "clean-channel-history_description": "Permís per esborrar l'historial dels canals", - "clear": "Esborra", + "clear": "Esborrar", "Clear_all_unreads_question": "Esborrar tots els missatges no llegits?", "clear_cache_now": "Esborra la memòria cau ara", "Clear_filters": "Esborra els filtres", @@ -878,7 +886,7 @@ "close-livechat-room": "Tancar Room de Livechat", "close-livechat-room_description": "Permís per tancar la sala d'LiveChat actual", "Close_menu": "Tanca el menú", - "close-others-livechat-room": "Tancar un altre sala de Livechat", + "close-others-livechat-room": "Tancar un altre Room de Livechat", "close-others-livechat-room_description": "Permís per tancar altres canals de LiveChat", "Closed": "Tancat", "Closed_At": "Tancat a les", @@ -894,7 +902,7 @@ "Cloud_Invalid_license": "Llicència no vàlida!", "Cloud_Apply_license": "Aplicar llicència", "Cloud_connectivity": "Connectivitat al núvol", - "Cloud_address_to_send_registration_to": "La direcció a la qual enviar el correu electrònic de registre en el núvol.", + "Cloud_address_to_send_registration_to": "La direcció a la qual enviar el correu electrònic de registre al núvol.", "Cloud_click_here": "Després de copiar el text, vés a [la consola Cloud (fés clic aquí)](__cloudConsoleUrl__).", "Cloud_console": "Consola en el núvol", "Cloud_error_code": "Codi: __errorCode__", @@ -906,7 +914,7 @@ "Cloud_register_error": "Hi ha hagut un error a l'intentar processar la seva sol·licitud. Torneu-ho de nou més tard.", "Cloud_Register_manually": "Registra't sense connexió", "Cloud_register_offline_finish_helper": "Després de completar el procés de registre en Cloud Console, hauria d'aparèixer un text. Enganxeu-lo aquí per finalitzar el registre.", - "Cloud_register_offline_helper": "Els espais de treball es poden registrar manualment si es restringeix l'accés a la xarxa o l'espai d'aire. Copieu el text a continuació i aneu a la nostra consola en el núvol per completar el procés.", + "Cloud_register_offline_helper": "Els espais de treball es poden registrar manualment si restringiu l'accés a la xarxa o l'espai d'aire. Copieu el text a continuació i aneu a la nostra consola al núvol per completar el procés.", "Cloud_register_success": "El seu espai de treball s'ha registrat correctament!", "Cloud_registration_pending_html": " Les notificacions en dispositius mòbils no funcionessin fins que el registre hagi finalitzat. Llegir més ", "Cloud_registration_pending_title": "El registre en el núvol encara està pendent", @@ -917,27 +925,27 @@ "Cloud_Service_Agree_PrivacyTerms": "Acords i termes de privacitat de el servei en el núvol", "Cloud_Service_Agree_PrivacyTerms_Description": "Estic d'acord amb els Termes i la Política de privacitat", "Cloud_Service_Agree_PrivacyTerms_Login_Disabled_Warning": "Ha d'acceptar els termes de privacitat del núvol (Assistent de configuració> Informació del núvol> Acord de termes de privacitat de el servei de núvol) per connectar al seu espai de treball en el núvol.", - "Cloud_status_page_description": "Si un Servei de Cloud en particular té problemes, pot verificar els problemes coneguts a la nostra pàgina d'estat a", + "Cloud_status_page_description": "Si un Servei de Cloud en particular té problemes, podeu verificar els problemes coneguts a la nostra pàgina d'estat a", "Cloud_token_instructions": "Per registrar el seu espai de treball, aneu a Cloud Console. Inicieu sessió o creeu un compte i feu clic a registrar-autogestionat. Enganxeu el testimoni proporcionat a continuació", "Cloud_troubleshooting": "Resolució de problemes", - "Cloud_update_email": "Actualitza el correu electrònic", + "Cloud_update_email": "Actualitzar el correu electrònic", "Cloud_what_is_it": "Què és això?", "Cloud_what_is_it_additional": "A més, podrà administrar llicències, facturació i suport des de la consola del núvol Rocket.Chat.", - "Cloud_what_is_it_description": "Rocket.Chat Cloud Connect li permet connectar el seu espai de treball Rocket.Chat autohospedado als serveis que oferim al nostre núvol.", + "Cloud_what_is_it_description": "Rocket.Chat Cloud Connect us permet connectar el vostre espai de treball Rocket.Chat autoallotjat als serveis que brindem al nostre núvol.", "Cloud_what_is_it_services_like": "Serveis com:", "Cloud_workspace_connected": "El seu espai de treball està connectat a Rocket.Chat Cloud. Inicia la sessió al compte de Rocket.Chat Cloud aquí li permetrà interactuar amb alguns serveis com al marketplace.", - "Cloud_workspace_connected_plus_account": "El vostre espai de treball està connectat al Rocket.Chat Cloud i hi ha un compte associat.", - "Cloud_workspace_connected_without_account": "El seu espai de treball ara està connectat a Rocket.Chat Cloud. Si ho desitja, podeu entrar en Rocket.Chat Cloud i associar el seu espai de treball amb el seu compte en el núvol.", - "Cloud_workspace_disconnect": "Si ja no desitja utilitzar els serveis en el núvol, pot desconnectar el seu espai de treball de Rocket.Chat Cloud.", - "Cloud_workspace_support": "Si té problemes amb un servei en el núvol, intenti sincronitzar primer. Si el problema persisteix, obriu un tiquet de suport en Cloud Console.", + "Cloud_workspace_connected_plus_account": "El vostre espai de treball ara està connectat a Rocket.Chat Cloud i un compte està associat.", + "Cloud_workspace_connected_without_account": "El vostre espai de treball ara està connectat a Rocket.Chat Cloud. Si ho desitgeu, podeu iniciar sessió a Rocket.Chat Cloud i associar el vostre espai de treball amb el vostre compte al núvol.", + "Cloud_workspace_disconnect": "Si ja no voleu utilitzar els serveis al núvol, podeu desconnectar el vostre espai de treball de Rocket.Chat Cloud.", + "Cloud_workspace_support": "Si teniu problemes amb un servei al núvol, intenteu sincronitzar primer. Si el problema persisteix, obriu un tiquet de suport al Cloud Console.", "Collaborative": "Col·laboratiu", "Collapse": "Caiguda", - "Collapse_Embedded_Media_By_Default": "Contraure mitjans integrats per defecte", + "Collapse_Embedded_Media_By_Default": "Contreure mitjans integrats per defecte", "color": "Color", "Color": "Color", "Colors": "Colors", "Commands": "Ordres", - "Comment_to_leave_on_closing_session": "Comentari a deixar en tancar la sessió", + "Comment_to_leave_on_closing_session": "Comentari per deixar a la sessió de tancament", "Comment": "Comentari", "Common_Access": "Accés comú", "Community": "Comunitat", @@ -979,7 +987,7 @@ "Contact_Info": "Informació de contacte", "Content": "Contingut", "Continue": "Continuar", - "Continuous_sound_notifications_for_new_livechat_room": "Notificacions de so continus per a la nova sala de LiveChat", + "Continuous_sound_notifications_for_new_livechat_room": "Notificacions de so contínues per a una nova sala Livechat", "Conversation": "Conversa", "Conversation_closed": "Conversa tancada: __comment__.", "Conversation_closing_tags": "Etiquetes de tancament de la conversa", @@ -1114,7 +1122,7 @@ "Country_Kenya": "Kenya", "Country_Kiribati": "Kiribati", "Country_Korea_Democratic_Peoples_Republic_of": "República Popular Democràtica de Corea", - "Country_Korea_Republic_of": "Corea, República de", + "Country_Korea_Republic_of": " República de Corea", "Country_Kuwait": "Kuwait", "Country_Kyrgyzstan": "Kirguizistan", "Country_Lao_Peoples_Democratic_Republic": "República Democràtica Popular Lao", @@ -1255,7 +1263,7 @@ "create-d_description": "Permís per a iniciar missatges directes", "create-invite-links": "Crear enllaços d'invitació", "create-invite-links_description": "Permís per crear enllaços d'invitació als canals", - "create-p": "Crear canals privats", + "create-p": "Crear Channel privats", "create-p_description": "Permís per crear canals privats", "create-personal-access-tokens": "Crear Tokens d'accés personal", "create-personal-access-tokens_description": "Permís per crear tokens d'accés personal", @@ -1265,7 +1273,7 @@ "Created_as": "Creat com", "Created_at": "Creat a", "Created_at_s_by_s": "Creat a %s per %s", - "Created_at_s_by_s_triggered_by_s": "Creat el %s per %s i disparat per %s", + "Created_at_s_by_s_triggered_by_s": "Creat el %s per %s i activat per %s", "Created_by": "Creat per", "CRM_Integration": "Integració CRM", "CROWD_Allow_Custom_Username": "Permet un nom d'usuari personalitzat a Rocket.Chat", @@ -1284,7 +1292,7 @@ "Custom_Emoji": "Emoticona personalitzada", "Custom_Emoji_Add": "Afegir nova emoticona", "Custom_Emoji_Added_Successfully": "Emoticona personalitzada afegida correctament", - "Custom_Emoji_Delete_Warning": "L'eliminació d'una emoticona no es pot desfer.", + "Custom_Emoji_Delete_Warning": "Eliminar un emoji no es pot desfer.", "Custom_Emoji_Error_Invalid_Emoji": "Emoticona invàlida", "Custom_Emoji_Error_Name_Or_Alias_Already_In_Use": "L'emoticona personalitzada o un dels seus àlies ja s'utilitza.", "Custom_Emoji_Has_Been_Deleted": "L'emoticona personalitzada s'ha eliminat.", @@ -1314,15 +1322,15 @@ "Custom_Sounds": "Sons personalitzats", "Custom_Status": "Estat personalitzat", "Custom_Translations": "Traduccions personalitzades", - "Custom_Translations_Description": "Ha de ser un objecte JSON vàlid on les claus són el codi de l'idioma i contenen un diccionari de clau i traducció. Exemple:
    {\n \"en\": {\n \"Channels\": \"Rooms\"\n },\n \"pt\":{\n \"Channels\": \"Salas\"\n }\n} ", + "Custom_Translations_Description": "Ha de ser un JSON vàlid on les claus siguin idiomes que continguin un diccionari de clau i traduccions. Exemple:
    {\n \"en\": {\n \"Channels\": \"Rooms\"\n },\n \"pt\":{\n \"Channels\": \"Salas\"\n }\n} ", "Custom_User_Status": "Estat d’usuari personalitzat", "Custom_User_Status_Add": "Afegir estat d'usuari personalitzat", "Custom_User_Status_Added_Successfully": "Es va afegir correctament l'estat d'usuari personalitzat", "Custom_User_Status_Delete_Warning": "L'eliminació d'un estat d'usuari personalitzat no es pot desfer.", - "Custom_User_Status_Edit": "Edita l'estat de l'usuari personalitzat", + "Custom_User_Status_Edit": "Editar l'estat de l'usuari personalitzat", "Custom_User_Status_Error_Invalid_User_Status": "Estat d’usuari no vàlid", "Custom_User_Status_Error_Name_Already_In_Use": "El nom d’estat d’usuari personalitzat ja està en ús.", - "Custom_User_Status_Has_Been_Deleted": "L'estat d'usuari personalitzat s'ha eliminat", + "Custom_User_Status_Has_Been_Deleted": "S'ha eliminat l'estat d'usuari personalitzat", "Custom_User_Status_Info": "Informació sobre l'estat d'usuari personalitzat", "Custom_User_Status_Updated_Successfully": "Estat d'usuari personalitzat actualitzat amb èxit", "Customer_without_registered_email": "El client no té una adreça de correu electrònic registrada", @@ -1340,9 +1348,10 @@ "Days": "Díes", "DB_Migration": "Migració de base de dades", "DB_Migration_Date": "Data de migració de la BD", + "DDP_Rate_Limit": "Límit de taxa DDP", "DDP_Rate_Limit_Connection_By_Method_Enabled": "Límit de Connexió per Mètode: habilitat", - "DDP_Rate_Limit_Connection_By_Method_Interval_Time": "Límit de Connexió per Mètode: interval de temps", - "DDP_Rate_Limit_Connection_By_Method_Requests_Allowed": "Límit de Connexió per Mètode: peticions permeses", + "DDP_Rate_Limit_Connection_By_Method_Interval_Time": "Límit per connexió per mètode: temps d'interval", + "DDP_Rate_Limit_Connection_By_Method_Requests_Allowed": "Límit per connexió per mètode: sol·licituds permeses", "DDP_Rate_Limit_Connection_Enabled": "Límit per Connexió: habilitat", "DDP_Rate_Limit_Connection_Interval_Time": "Límit per Connexió: interval de temps", "DDP_Rate_Limit_Connection_Requests_Allowed": "Límit per connexió: sol·licituds permeses", @@ -1355,7 +1364,7 @@ "DDP_Rate_Limit_User_Enabled": "Límit per usuari: habilitat", "DDP_Rate_Limit_User_Interval_Time": "Límit per usuari: interval de temps", "DDP_Rate_Limit_User_Requests_Allowed": "Límit per usuari: peticions permeses", - "Deactivate": "Desactiva", + "Deactivate": "Desactivar", "Decline": "Rebutja", "Decode_Key": "Clau de descodificació", "Default": "Per defecte", @@ -1369,10 +1378,10 @@ "Delete_Role_Warning": "Eliminar un rol l'eliminarà per sempre. Això no es pot desfer.", "Delete_Room_Warning": "Eliminar una sala de xat esborra tots els missatges que conté. Aquesta acció no es pot desfer.", "Delete_User_Warning": "Eliminar un usuari també esborra tots els missatges que ha enviat. Aquesta acció no es pot desfer.", - "Delete_User_Warning_Delete": "Eliminar un usuari també esborra tots els missatges que ha enviat. Aquesta acció no es pot desfer.", - "Delete_User_Warning_Keep": "S'eliminarà l'usuari, però els seus missatges romandran visibles. Això no es pot desfer.", + "Delete_User_Warning_Delete": "Eliminar un usuari causarà l'eliminació de tots els missatges creats per aquest usuari. Aquesta acció no es pot desfer.", + "Delete_User_Warning_Keep": "L'usuari serà eliminat, però els vostres missatges romandran visibles. Això no es pot desfer.", "Delete_User_Warning_Unlink": "Eliminar un usuari eliminarà el nom d'usuari de tots els seus missatges. Això no es pot desfer.", - "delete-c": "Esborrar canals públics", + "delete-c": "Esborrar Channel públics", "delete-c_description": "Permís per esborrar canals públics", "delete-d": "Esborrar missatges directes", "delete-d_description": "Permís per esborrar missatges directes", @@ -1390,20 +1399,20 @@ "Department_not_found": "Departament no trobat", "Department_removed": "Departament eliminat", "Departments": "Departaments", - "Deployment_ID": "Deployment ID", + "Deployment_ID": "ID de desplegament", "Deployment": "Desplegament", "Description": "Descripció", "Desktop": "Escriptori", "Desktop_Notification_Test": "Prova de notificació d'escriptori", "Desktop_Notifications": "Notificacions d'escriptori", - "Desktop_Notifications_Default_Alert": "Alerta per defecte per a les notificacions d'escriptori", + "Desktop_Notifications_Default_Alert": "Alerta predeterminada de notificacions d'escriptori", "Desktop_Notifications_Disabled": "Les notificacions d'escriptori han estat desactivades. Canvia les preferències del navegador si vols tornar a activar-les.", "Desktop_Notifications_Duration": "Durada de les notificacions d'escriptori", "Desktop_Notifications_Duration_Description": "Segons de mostra de les notificacions d'escriptori. Això pot afectar al centre de notificacions del macOS. Introduïu 0 per utilitzar la configuració del navegador per defecte i no afectar al centre de notificacions.", "Desktop_Notifications_Enabled": "Les notificacions d'escriptori estan activades", "Desktop_Notifications_Not_Enabled": "Les notificacions d'escriptori no estan habilitades", "Details": "Detalls", - "Different_Style_For_User_Mentions": "Estil diferent per les mencions d'usuari", + "Different_Style_For_User_Mentions": "Estil diferent per a les mencions de lusuari", "Direct_Message": "Missatge directe", "Direct_message_creation_description": "Estàs a punt de crear un xat amb múltiples usuaris. Afegeix als usuaris amb els que t'agradaria parlar, tots en el mateix lloc, fent servir missatges directes.", "Direct_message_someone": "Envia un missatge directe a algú", @@ -1414,9 +1423,9 @@ "Direct_Reply_Debug": "Debug resposta directa", "Direct_Reply_Debug_Description": "[Compte] Activa el mode de depuració mostraria el seu 'Contrasenya de text sense format' a la consola d'administració.", "Direct_Reply_Delete": "Eliminar correus electrònics", - "Direct_Reply_Delete_Description": "[Atenció!] Si s'activa aquesta opció, tots els missatges no llegits seran eliminats irrevocablement, fins i tot els que no són respostes directes. La bústia de correu electrònic configurat està llavors sempre buit i no pot ser processat en \"paral·lel\" per humans.", + "Direct_Reply_Delete_Description": "[Atenció!] Si aquesta opció està activada, tots els missatges no llegits s'eliminen irrevocablement, fins i tot aquells que no són respostes directes. La bústia de correu electrònic configurada està sempre buida i no pot ser processada en \"paral·lel\" per humans.", "Direct_Reply_Enable": "Activa resposta directa", - "Direct_Reply_Enable_Description": "[Atenció!] Si \"Resposta Directa\" està habilitat, Rocket.Chat controlarà la bústia de correu electrònic configurat. Tots els correus electrònics no llegits són recuperats, marcats com a llegits i processats. \"Resposta directa\" només ha de ser activada si la bústia utilitzat està destinat exclusivament per a l'accés de Rocket.Chat i no és llegit / processat \"en paral·lel\" per humans.", + "Direct_Reply_Enable_Description": "[Atenció!] Si la \"Resposta directa\" està habilitada, Rocket.Chat controlarà la bústia de correu electrònic configurada. Tots els correus electrònics no llegits es recuperen, es marquen com a llegits i es processen. La \"Resposta directa\" només s'ha d'activar si la bústia de correu utilitzada està destinada exclusivament per a l'accés de Rocket.Chat i no és llegit / processat \"en paral·lel\" per humans.", "Direct_Reply_Frequency": "Freqüència de comprovació de correu-e", "Direct_Reply_Frequency_Description": "(en minuts, per defecte/mínim 2)", "Direct_Reply_Host": "Host de resposta directa", @@ -1428,7 +1437,7 @@ "Direct_Reply_Separator_Description": "[Modifiqueu només si sabeu exactament què feu, consulteu documents]
    Separador entre base i etiqueta del correu electrònic", "Direct_Reply_Username": "Nom d'usuari", "Direct_Reply_Username_Description": "Utilitzeu el correu electrònic absolut, l'etiquetatge no està permès, se sobreescriurà", - "Directory": "directori", + "Directory": "Directori", "Disable": "Desactivar", "Disable_Facebook_integration": "Desactiva la integració de Facebook", "Disable_Notifications": "Desactiva notificacions", @@ -1440,9 +1449,9 @@ "Discard": "Descartar", "Disconnect": "Desconnectar", "Discussion": "Discussió", - "Discussion_description": "Ajudeu a mantenir una visió general del que està succeint! A l'crear una discussió, es crea un subcanal de què va seleccionar i tots dos es vinculen.", + "Discussion_description": "Ajudeu a mantenir una visió general del que està succeint! En crear una discussió, es crea un subcanal del qual vau seleccionar i tots dos es vinculen.", "Discussion_first_message_disabled_due_to_e2e": "Pot començar a enviar missatges xifrats d'extrem a extrem en aquesta discussió després de la seva creació.", - "Discussion_first_message_title": "el teu missatge", + "Discussion_first_message_title": "El teu missatge", "Discussion_name": "Nom de la discussió", "Discussion_start": "Començar una discussió", "Discussion_target_channel": "Canal o grup pare", @@ -1454,7 +1463,7 @@ "Display": "Visualització", "Display_avatars": "Mostra avatars", "Display_Avatars_Sidebar": "Mostra avatars a la barra lateral", - "Display_chat_permissions": "Mostra permisos de xat", + "Display_chat_permissions": "Mostrar permisos de xat", "Display_offline_form": "Mostra el formulari de fora de línia", "Display_setting_permissions": "Mostra permisos per canviar la configuració", "Display_unread_counter": "Mostra el nombre de missatges no llegits", @@ -1463,7 +1472,7 @@ "Do_not_display_unread_counter": "No mostreu cap comptador d'aquest canal", "Do_not_provide_this_code_to_anyone": "No comparteixi aquest codi amb ningú.", "Do_Nothing": "No fer res", - "Do_you_want_to_accept": "Ho accepteu?", + "Do_you_want_to_accept": "Vols acceptar?", "Do_you_want_to_change_to_s_question": "Canvia a %s?", "Document_Domain": "Domini del document", "Domain": "Domini", @@ -1472,7 +1481,7 @@ "Domains": "dominis", "Domains_allowed_to_embed_the_livechat_widget": "Llista separada per comes dels dominis on es permet incloure el xat en viu. Deixeu en blanc per permetre'ls tots.", "Dont_ask_me_again": "No em tornis a preguntar!", - "Dont_ask_me_again_list": "No tornis a demanar-me una altra vegada", + "Dont_ask_me_again_list": "No tornis a preguntar-me llista", "Download": "Descarrega", "Download_Info": "Descarregar informació", "Download_My_Data": "Descarregar les meves dades (HTML)", @@ -1494,21 +1503,21 @@ "E2E_enable": "Habilitar E2E", "E2E_disable": "Deshabilitat E2E", "E2E_Enable_alert": "Aquesta funció està actualment en versió beta. Informeu d’errors a github.com/RocketChat/Rocket.Chat/issues i tingueu en compte:
    - Les operacions de cerca no trobaran els missatges xifrats de les sales xifrades.
    - És possible que les aplicacions mòbils no admetin els missatges encriptats. (ho estan implementant).
    - És possible que els robots no puguin veure els missatges xifrats fins que no implementin el suport.
    - Les càrregues no es xifraran en aquesta versió.", - "E2E_Enable_description": "Habiliteu l'opció per crear grups encriptades i poder canviar grups i missatges directes per ser encriptats", + "E2E_Enable_description": "Habiliteu l'opció per crear grups encriptats i poder canviar grups i missatges directes per ser encriptats", "E2E_Enabled": "E2E activat", "E2E_Enabled_Default_DirectRooms": "Habilitar l'encriptació per a les Rooms directes per defecte", "E2E_Enabled_Default_PrivateRooms": "Habilitar l'encriptació per a les Rooms privades per defecte", "E2E_Encryption_Password_Change": "Canviar la contrasenya de xifrat", - "E2E_Encryption_Password_Explanation": "Ara pot crear grups privats codificats i missatges directes. També pot canviar els grups privats o missatges directes existents a xifrats.

    1 /> Això és xifrat d'extrem a extrem, així que la clau per xifrar / desxifrar els teus missatges no es guardarà al servidor. Per aquesta raó necessites guardar la contrasenya en un lloc segur. Se't demanarà que la introdueixis en altres dispositius en els que vulguis utilitzar l'encriptació de E2E.", + "E2E_Encryption_Password_Explanation": "Ara podeu crear grups privats xifrats i missatges directes. També podeu canviar els grups privats o DM existents a xifrats.

    Aquest és un xifratge d'extrem a extrem, de manera que la clau per codificar / descodificar els seus missatges no es desarà al servidor. Per això, heu de desar la contrasenya en un lloc segur. Se us demanarà que l'introduïu en altres dispositius on vulgueu utilitzar el xifratge e2e.", "E2E_key_reset_email": "Notificació de reinici de clau E2E", "E2E_password_request_text": "Per accedir als seus grups privats xifrats i als missatges directes, introdueixi la contrasenya de xifrat.
    Necessites introduir aquesta contrasenya per xifrar / desxifrar els teus missatges en cada client que utilitzis, ja que la clau no s'emmagatzema en el servidor.", - "E2E_password_reveal_text": "Ara podeu crear grups privats xifrats i missatges directes. També podeu canviar els grups privats o DM existents a xifrats. [Html0]

    Això fa que el xifratge finalitzi, de manera que la clau per codificar / descodificar els vostres missatges no es desarà al servidor. Per aquest motiu, heu de desar aquesta contrasenya en un lloc segur. Haureu d’introduir-lo en altres dispositius en què vulgueu utilitzar el xifratge e2e. Més informació aquí!
    La seva contrassenya es:


    Aquesta és una contrasenya generada automàticament; podeu configurar una nova contrasenya per a la vostra clau de xifrat en qualsevol moment des de qualsevol navegador que hàgiu introduït la contrasenya existent.
    Aquesta contrasenya només s’emmagatzema en aquest del navegador fins que deseu la contrasenya i desactiveu aquest missatge.", - "E2E_Reset_Email_Content": "S'ha desconnectat automàticament. Quan torni a iniciar sessió, Rocket.Chat generarà una nova clau i restaurarà el seu accés a qualsevol sala xifrada que tingui un o més membres en línia. A causa de la naturalesa de l'xifrat E2E, Rocket.Chat no podrà restaurar l'accés a cap sala xifrada que no tingui membres en línia.", - "E2E_Reset_Key_Explanation": "Aquesta opció eliminarà la seva clau I2I actual i tancarà la sessió.
    Quan torni a iniciar sessió, Rocket.Chat li generarà una nova clau i restablirà el seu accés a qualsevol sala xifrada que tingui un o més membres en línia.
    A causa de la naturalesa de l'xifrat E2E, Rocket.Chat no podrà restaurar l'accés a cap sala xifrada que no tingui membres en línia.", + "E2E_password_reveal_text": "Ara podeu crear grups privats xifrats i missatges directes. També podeu canviar els grups privats o DM existents a xifrats.

    Aquest és un xifratge d'extrem a extrem, de manera que la clau per codificar / descodificar els seus missatges no es desarà al servidor. Per això, heu de desar aquesta contrasenya en un lloc segur. Se us demanarà que l'introduïu en altres dispositius on vulgueu utilitzar el xifratge e2e. Obtingueu més informació aquí!

    La vostra contrasenya és:% s

    Aquesta és una contrasenya generada automàticament, podeu configurar una nova contrasenya per a la vostra clau de xifrat en qualsevol moment des de qualsevol navegador que hagi introduït la contrasenya existent.
    Aquesta contrasenya només s'emmagatzema en aquest navegador fins que la deseu i descarteu aquest missatge.", + "E2E_Reset_Email_Content": "S'ha desconnectat automàticament. Quan torneu a iniciar sessió, Rocket.Chat generarà una nova clau i restaurarà el vostre accés a qualsevol sala xifrada que tingui un o més membres en línia. A causa de la naturalesa del xifratge E2E, Rocket.Chat no podrà restaurar l'accés a cap sala xifrada que no tingui membres en línia.", + "E2E_Reset_Key_Explanation": "Aquesta opció eliminarà la clau E2E actual i tancarà la sessió.
    Quan torneu a iniciar sessió, Rocket.Chat us generarà una nova clau i restaurarà l'accés a qualsevol sala xifrada que tingui un o més membres en línia.
    A causa de la naturalesa del xifratge E2E, Rocket.Chat no podrà restaurar l'accés a cap sala xifrada que no tingui membres en línia.", "E2E_Reset_Other_Key_Warning": "Restablir la clau E2E actual tancarà la sessió de l'usuari. Quan l'usuari torna a iniciar sessió, Rocket.Chat generarà una nova clau i restaurarà l'accés de l'usuari a qualsevol sala xifrada que tingui un o més membres en línia. A causa de la naturalesa de l'xifrat E2E, Rocket.Chat no podrà restaurar l'accés a cap sala xifrada que no tingui membres en línia.", "ECDH_Enabled": "Habiliteu el xifrat de segona capa per al transport de dades", "Edit": "Edita", - "Edit_Business_Hour": "Edita l'horari comercial", + "Edit_Business_Hour": "Edita l'horari d'oficina ", "Edit_Canned_Response": "Edita resposta predefinida", "Edit_Canned_Responses": "Edita respostes emmagatzemades", "Edit_Custom_Field": "Edita camp personalitzat", @@ -1518,7 +1527,7 @@ "Edit_Priority": "Edita la prioritat", "Edit_Status": "Edita l'estat", "Edit_Tag": "Edita l’etiqueta", - "Edit_Trigger": "Edita disparador", + "Edit_Trigger": "Edita activador", "Edit_Unit": "Edita la unitat", "Edit_User": "Edita l'usuari", "edit-livechat-room-customfields": "Edita camps personalitzats de Livechat Room", @@ -1532,14 +1541,14 @@ "edit-other-user-e2ee": "Edita el xifrat E2E d'un altre usuari", "edit-other-user-e2ee_description": "Permís per modificar el xifrat I2I d'un altre usuari.", "edit-other-user-info": "Editar la informació d'un altre usuari", - "edit-other-user-info_description": "Permís per canviar el nom, el nom d'usuari o l'adreça de correu-e d'altres usuaris", + "edit-other-user-info_description": "Permís per canviar el nom, el nom d'usuari o l'adreça electrònica d'un altre usuari.", "edit-other-user-password": "Editar la contrasenya d'un altre usuari", "edit-other-user-password_description": "Permís per modificar la contrasenya d'altres usuaris. Requereix el permís edit-other-user-info.", "edit-other-user-totp": "Edita el doble factor TOTP d'un altre usuari", "edit-other-user-totp_description": "Permís per editar el TOTP de dos factors d'un altre usuari", "edit-privileged-setting": "Edita la configuració privilegiada", "edit-privileged-setting_description": "Permís per editar la configuració", - "edit-room": "Editar sala", + "edit-room": "Editar Room", "edit-room_description": "Permís per editar el nom d'una sala, el tema, el tipus (privada o pública) o l'estat (actiu o arxivat)", "edit-room-avatar": "Edita Room Avatar", "edit-room-avatar_description": "Permís per editar l'avatar d'una sala.", @@ -1558,10 +1567,10 @@ "Email_already_exists": "L'adreça de correu electrònic ja existeix", "Email_body": "Cos del missatge", "Email_Change_Disabled": "El canvi de correu electrònic està desactivat", - "Email_Changed_Description": "Podeu utilitzar els següents marcadors de posició:
  • [e] per al correu electrònic de l'usuari.
  • [Site_Name] i [ Site_URL] per al nom de l'aplicació i la URL respectivament.
    • ", + "Email_Changed_Description": "Podeu utilitzar les adreces d'interès següents:
      • [email] per al correu electrònic de l'usuari.
      • [Site_Name] i [Site_URL] per al nom de l'aplicació i l'URL respectivament.
      ", "Email_Changed_Email_Subject": "[Site_name] - La direcció de correu electrònic ha estat modificada", "Email_changed_section": "Adreça de correu electrònic modificada", - "Email_Footer_Description": "És possible utilitzar els marcadors:
      • [Site_Name] i [Site_URL] pel nom del lloc web i de l'adreça URL, respectivament.
      ", + "Email_Footer_Description": "Podeu utilitzar les adreces d'interès següents:
      • [Site_Name] i [Site_URL] per al nom de l'aplicació i l'URL respectivament.
      ", "Email_from": "De", "Email_Header_Description": "És possible utilitzar els marcadors:
      • [Site_Name] i [Site_URL] pel nom del lloc web i de l'adreça URL, respectivament.
      ", "Email_Inbox": "Safata d'entrada de correu electrònic", @@ -1588,7 +1597,7 @@ "Enable_message_parser_early_adoption": "Habilitar l'analitzador de missatges", "Enable_message_parser_early_adoption_alert": "Aquesta és una característica experimental i seguirà sent-ho al menys fins a la versió 3.19.0, aquesta opció és per ajudar-nos amb proves i casos extrems. Tan aviat com no trobem més problemes, eliminarem aquesta opció i migrarem a la nova solució.", "See_on_Engagement_Dashboard": "Consulteu el tauler de participació", - "Enable": "Activa", + "Enable": "Habilitar", "Enable_Auto_Away": "Activa Auto Away", "Enable_CSP": "Habilitar política de seguretat de contingut", "Enable_CSP_Description": "No desactiveu aquesta opció a menys que tingui una compilació personalitzada i tingui problemes a causa de scripts en línia", @@ -1604,11 +1613,13 @@ "Encrypted": "Xifrat", "Encrypted_channel_Description": "Canal xifrat d'extrem a extrem. La cerca no funcionarà amb canals xifrats i és possible que les notificacions no mostrin el contingut dels missatges.", "Encrypted_message": "Missatge xifrat", - "Encrypted_setting_changed_successfully": "La configuració encriptada es va modificar correctament", + "Encrypted_setting_changed_successfully": "La configuració encriptada es va canviar correctament", "Encrypted_not_available": "No disponible per a Channels públics", - "Encryption_key_saved_successfully": "La seva clau d'encriptació es va guardar correctament", - "EncryptionKey_Change_Disabled": "No pot establir una contrasenya per a la clau de xifrat perquè la seva clau privada no està present en aquest client. Per establir una nova contrasenya, necessita carregar la seva clau privada amb el vostre contrasenya existent o utilitzar un client on la clau ja estigui carregada.", + "Encryption_key_saved_successfully": "la vostra clau de xifrat es va guardar correctament.", + "EncryptionKey_Change_Disabled": "No podeu establir una contrasenya per a la vostra clau de xifratge perquè la vostra clau privada no és present en aquest client. Per establir una contrasenya nova, heu de carregar la vostra clau privada utilitzant la vostra contrasenya existent o utilitzar un client on la clau ja estigui carregada.", "End": "Fi", + "End_call": "Finalitzar trucada", + "Expand_view": "Expandir vista", "End_OTR": "Finalitza OTR", "Engagement_Dashboard": "Tauler de participació", "Enter": "Entra", @@ -1630,7 +1641,7 @@ "Enter_your_E2E_password": "Introduïu la vostra contrasenya E2E", "Enterprise": "Empresa", "Enterprise_License": "Llicència d’empresa", - "Enterprise_License_Description": "Si el teu espai de treball està registrat i disposa d'una llicència proporcionada per Rocket.Chat Cloud no cal actualitzar aquesta llicència..", + "Enterprise_License_Description": "Si el vostre espai de treball està registrat i la llicència la proporciona Rocket.Chat Cloud, no cal que actualitzeu manualment la llicència aquí.", "Entertainment": "Entreteniment", "Error": "Error", "Error_404": "Error: 404", @@ -1656,14 +1667,14 @@ "error-cannot-delete-app-user": "No es permet esborrar l'usuari de l'aplicació, desinstal l'aplicació corresponent per eliminar-la.", "error-cant-invite-for-direct-room": "No es pot convidar a l'usuari a sales directes", "error-channels-setdefault-is-same": "La configuració predeterminada de canal és la mateixa a la qual es canviaria..", - "error-channels-setdefault-missing-default-param": "El bodyParam 'default' és obligatori", + "error-channels-setdefault-missing-default-param": "El bodyParam 'predeterminat' és obligatori", "error-could-not-change-email": "No s'ha pogut canviar el correu electrònic", "error-could-not-change-name": "No s'ha pogut canviar el nom", "error-could-not-change-username": "No s'ha pogut canviar el nom d'usuari", "error-custom-field-name-already-exists": "El nom de camp personalitzat ja existeix", "error-delete-protected-role": "No es pot eliminar un rol protegit", "error-department-not-found": "Departament no trobat", - "error-direct-message-file-upload-not-allowed": "Compartició d'arxius no permesa als missatges directes", + "error-direct-message-file-upload-not-allowed": "No es permet compartir fitxers en missatges directes", "error-duplicate-channel-name": "Un canal amb el nom '__channel_name__' ja existeix", "error-edit-permissions-not-allowed": "No es permet editar permisos", "error-email-domain-blacklisted": "El domini de l'adreça electrònica és a la llista negra", @@ -1672,13 +1683,13 @@ "error-field-unavailable": "__field__ ja s'utilitza :(", "error-file-too-large": "L'arxiu és massa gran", "error-forwarding-chat": "S'ha produït un error a l'enviar el xat. Torna-ho a intentar més tard.", - "error-forwarding-chat-same-department": "El departament seleccionat i l'actual departament de la sala són els mateixos", + "error-forwarding-chat-same-department": "El departament seleccionat i el departament sala actual són el mateix", "error-forwarding-department-target-not-allowed": "No es permet el reenviament a el departament de destinació.", "error-guests-cant-have-other-roles": "Els usuaris visitants no poden tenir cap altre rol.", "error-import-file-extract-error": "No s'ha pogut extreure el fitxer d'importació.", "error-import-file-is-empty": "L'arxiu importat sembla estar buit.", "error-import-file-missing": "No s'ha trobat el fitxer a importar a la ruta especificada.", - "error-importer-not-defined": "L'importador no s'ha definit correctament, no es troba la classe d'importació.", + "error-importer-not-defined": "L'importador no es va definir correctament, manca la classe Import.", "error-input-is-not-a-valid-field": "__input__ no és un __field__ vàlid", "error-invalid-account": "Compte no vàlid", "error-invalid-actionlink": "Enllaç d'acció (action link) invàlid", @@ -1744,9 +1755,9 @@ "error-password-policy-not-met-minLength": "La contrasenya no compleix amb la política del servidor de durada mínima (la contrasenya és massa curta).", "error-password-policy-not-met-oneLowercase": "La contrasenya no compleix amb la política del servidor d'almenys un caràcter en minúscules", "error-password-policy-not-met-oneNumber": "La contrasenya no compleix la política del servidor d'almenys un caràcter numèric", - "error-password-policy-not-met-oneSpecial": "La contrasenya no compleix amb la política del servidor d'almenys un caràcter especial", + "error-password-policy-not-met-oneSpecial": "La contrasenya no compleix la política del servidor d'almenys un caràcter especial", "error-password-policy-not-met-oneUppercase": "La contrasenya no compleix amb la política del servidor d'almenys un caràcter en majúscula", - "error-password-policy-not-met-repeatingCharacters": "La contrasenya no compleix la política del servidor de caràcters repetits prohibits (teniu massa dels mateixos caràcters al costat de l'altre)", + "error-password-policy-not-met-repeatingCharacters": "La contrasenya no compleix la política del servidor de caràcters repetits prohibits (té massa caràcters iguals un al costat de l'altre)", "error-password-same-as-current": "Va ingressar la mateixa contrasenya que la contrasenya actual", "error-personal-access-tokens-are-current-disabled": "Les claus d'accés personals estan actualment desactivades", "error-pinning-message": "No s'ha pogut fixar el missatge", @@ -1783,7 +1794,7 @@ "error-you-are-last-owner": "Ets l'últim propietari. Si us plau, estableix un nou propietari abans de sortir de la sala.", "Errors_and_Warnings": "Errors i advertències", "Esc_to": "Esc a", - "Estimated_due_time": "Temps previst previst (temps en minuts)", + "Estimated_due_time": "Temps estimat despera (temps en minuts)", "Estimated_due_time_in_minutes": "Temps de venciment previst (temps en minuts)", "Event_Trigger": "Disparador d'esdeveniments", "Event_Trigger_Description": "Selecciona quin tipus d'esdeveniment desencadenarà aquesta integració WebHook de sortida", @@ -1791,7 +1802,7 @@ "every_10_seconds": "Una vegada cada 10 segons", "every_30_minutes": "Cada 30 minuts", "every_day": "Una vegada al dia", - "every_hour": "Cada hora", + "every_hour": "Un cop cada hora", "every_minute": "Una vegada cada minut", "every_second": "Una vegada per segon", "every_six_hours": "Cada 6 hores", @@ -1809,9 +1820,9 @@ "Experimental_Feature_Alert": "Aquesta és una funció experimental! Recordeu que pot canviar, trencar-se o fins i tot eliminar-se en el futur sense previ avís.", "Expiration": "Caducitat", "Expiration_(Days)": "Caducitat (dies)", - "Export_as_file": "Exporta com a fitxer", - "Export_Messages": "Exporta missatges", - "Export_My_Data": "Exportar les meves dades (jSOM)", + "Export_as_file": "Exporta com axiu", + "Export_Messages": "Exportar missatges", + "Export_My_Data": "Exportar les meves dades (jSON)", "expression": "Expressió", "Extended": "Ampliat", "External": "Extern", @@ -1830,13 +1841,15 @@ "Failed_To_Load_Import_Data": "Error al carregar importació de dades", "Failed_To_Load_Import_History": "Error a l'carregar importació de històric", "Failed_To_Load_Import_Operation": "Error al cargar operación de importación", - "Failed_To_Start_Import": "Error al iniciar operación de importación", - "Failed_to_validate_invite_token": "Error al validar token de invitació", + "Failed_To_Start_Import": "Error al iniciar l'operació d'importació", + "Failed_to_validate_invite_token": "Error al validar el token d'invitació", "False": "No", "Favorite": "Preferit", "Favorite_Rooms": "Habilita sales favorites", "Favorites": "Favorits", + "Feature_depends_on_selected_call_provider_to_be_enabled_from_administration_settings": "Aquesta funció depèn del proveïdor de trucades seleccionat anteriorment que s'habilitarà des de la configuració d'administració.", "Feature_Depends_on_Livechat_Visitor_navigation_as_a_message_to_be_enabled": "Aquesta funció depèn de \"Enviar l'historial de navegació del visitant com a missatge\" per estar habilitat.", + "Feature_Limiting": "Limitació de funcions", "Features": "Característiques", "Features_Enabled": "Funcionalitats habilitades", "Feature_Disabled": "Característica deshabilitada", @@ -1881,10 +1894,10 @@ "FEDERATION_Discovery_Method_Description": "Pot utilitzar el hub o un SRV i una entrada TXT en els seus registres DNS.", "FEDERATION_Domain": "Domini", "FEDERATION_Domain_Alert": "No canvieu això després d’habilitar la funció, encara no podem manejar els canvis de domini.", - "FEDERATION_Domain_Description": "Afegeix el domini al que aquest servidor hauria d’estar vinculat, per exemple: @ rocket.chat.", - "FEDERATION_Enabled": "Intentar integrar la federació de suport.", + "FEDERATION_Domain_Description": "Afegiu el domini al qual ha d'estar vinculat aquest servidor, per exemple: @rocket.chat", + "FEDERATION_Enabled": "IIntenteu integrar el suport de la federació.", "FEDERATION_Enabled_Alert": "La federació de suport està en progrés. El seu ús en un entorn de producció no es recomana de moment.", - "FEDERATION_Error_user_is_federated_on_rooms": "No es pot eliminar als usuaris federats que pertenecen a les sales", + "FEDERATION_Error_user_is_federated_on_rooms": "No podeu eliminar usuaris federats que pertanyen a sales", "FEDERATION_Hub_URL": "URL del Hub", "FEDERATION_Hub_URL_Description": "Configureu la URL del concentrador, per exemple: https://hub.rocket.chat. També es poden acceptar ports.", "FEDERATION_Public_Key": "Clau pública", @@ -1892,10 +1905,10 @@ "FEDERATION_Room_Status": "Estat de la federació", "FEDERATION_Status": "Estat", "FEDERATION_Test_Setup": "Configuració de prova", - "FEDERATION_Test_Setup_Error": "No es pot trobar el servidor utilitzant la seva configuració, revisar la seva configuració.", + "FEDERATION_Test_Setup_Error": "No s'ha pogut trobar el vostre servidor usant la vostra configuració, reviseu-ne la configuració.", "FEDERATION_Test_Setup_Success": "La configuració de la seva federació està funcionant i altres servidors poden trobar-se!", "FEDERATION_Unique_Id": "Identificador únic", - "FEDERATION_Unique_Id_Description": "Aquest és l'ID únic de la seva federació, que s'utilitza per identificar el seu parell a la xarxa.", + "FEDERATION_Unique_Id_Description": "Aquest és l'ID únic de la vostra federació, que s'utilitza per identificar el vostre parell a la xarxa.", "Field": "Camp", "Field_removed": "Camp eliminat", "Field_required": "Camp obligatori", @@ -1935,12 +1948,12 @@ "FileUpload_GoogleStorage_Bucket": "Nom del Bucket Google Storage", "FileUpload_GoogleStorage_Bucket_Description": "El nom del bucket on els arxius s'haurien de pujar.", "FileUpload_GoogleStorage_Proxy_Avatars": "Avatars Proxy", - "FileUpload_GoogleStorage_Proxy_Avatars_Description": "Transmissions d'arxius proxy avatar a través del seu servidor en lloc d'accés directe a l'URL de l'actiu", + "FileUpload_GoogleStorage_Proxy_Avatars_Description": "Transmissions de fitxers d'avatar de servidor intermediari a través del servidor en lloc d'accés directe a la URL de l'actiu", "FileUpload_GoogleStorage_Proxy_Uploads": "Càrregues proxy", "FileUpload_GoogleStorage_Proxy_Uploads_Description": "Proxy carrega les transmissions d'arxius a través del vostre servidor en lloc d'accedir directament a l'URL de l'actiu", "FileUpload_GoogleStorage_Secret": "Secret Google Storage", "FileUpload_GoogleStorage_Secret_Description": "Si us plau, segueix aquestes instruccions i enganxa el resultat aquí.", - "FileUpload_json_web_token_secret_for_files": "Carregar Secret del token web JSON", + "FileUpload_json_web_token_secret_for_files": "Pujar fitxer Secret del token web Json", "FileUpload_json_web_token_secret_for_files_description": "File Upload json web Token Secret (s'utilitza per poder accedir als arxius carregats sense autenticació)", "FileUpload_MaxFileSize": "Mida màxima de pujada (en bytes)", "FileUpload_MaxFileSizeDescription": "Establiu-lo a -1 per eliminar la limitació de la mida del fitxer.", @@ -1952,14 +1965,14 @@ "FileUpload_MediaTypeWhiteListDescription": "Llista de tipus d'arxiu separada per comes. Deixa-la en blanc per acceptar tots els tipus.", "FileUpload_ProtectFiles": "Protegir els arxius pujats", "FileUpload_ProtectFilesDescription": "Només els usuaris identificats hi tindran accés", - "FileUpload_RotateImages": "Rotar imatges a l'carregar", + "FileUpload_RotateImages": "Rotar imatges en carregar", "FileUpload_RotateImages_Description": "Habilitar aquesta configuració pot causar pèrdua de qualitat d'imatge", "FileUpload_S3_Acl": "Acl", "FileUpload_S3_AWSAccessKeyId": "Access Key", - "FileUpload_S3_AWSSecretAccessKey": "Secret Key", + "FileUpload_S3_AWSSecretAccessKey": "Clau Secreta", "FileUpload_S3_Bucket": "Bucket Name", "FileUpload_S3_BucketURL": "Bucket URL", - "FileUpload_S3_CDN": "Domini CDN per descàrregues", + "FileUpload_S3_CDN": "Domini CDN per a descàrregues", "FileUpload_S3_ForcePathStyle": "Force Path Style", "FileUpload_S3_Proxy_Avatars": "Avatars Proxy", "FileUpload_S3_Proxy_Avatars_Description": "Proxy transmissions d'arxius d'avatar a través del seu servidor en lloc d'accés directe a l'URL de l'actiu", @@ -1972,12 +1985,12 @@ "FileUpload_Storage_Type": "Tipus d'emmagatzematge", "FileUpload_Webdav_Password": "Contrasenya de WebDAV", "FileUpload_Webdav_Proxy_Avatars": "Avatars Proxy", - "FileUpload_Webdav_Proxy_Avatars_Description": "Transmissions d'arxius d'avatar a través del seu servidor en lloc d'accés directe a l'URL de l'actiu", + "FileUpload_Webdav_Proxy_Avatars_Description": "Transmissions de fitxers d'avatar de servidor intermediari a través del servidor en lloc d'accés directe a la URL de l'actiu", "FileUpload_Webdav_Proxy_Uploads": "Càrregues proxy", "FileUpload_Webdav_Proxy_Uploads_Description": "Transmissions d'arxius de càrrega proxy a través del seu servidor en lloc d'accés directe a l'URL de l'actiu", "FileUpload_Webdav_Server_URL": "URL d'accés al servidor WebDAV", "FileUpload_Webdav_Upload_Folder_Path": "Carrega la ruta de la carpeta", - "FileUpload_Webdav_Upload_Folder_Path_Description": "Ruta de la carpeta WebDAV en la qual s'han de carregar els arxius", + "FileUpload_Webdav_Upload_Folder_Path_Description": "Ruta de la carpeta WebDAV on s'han de carregar els arxius", "FileUpload_Webdav_Username": "Nom d'usuari de WebDAV", "Filter": "Filter", "Filters": "Filtres", @@ -1998,24 +2011,24 @@ "For_more_details_please_check_our_docs": "Per obtenir més informació, consulteu els nostres documents.", "For_your_security_you_must_enter_your_current_password_to_continue": "Per a la seva seguretat, ha de tornar a introduir la contrasenya per continuar", "Force_Disable_OpLog_For_Cache": "Forçar la desactivació de OpLog per la caché", - "Force_Disable_OpLog_For_Cache_Description": "No s'utilitzarà OpLog per sincronitzar la cache tot i estar disponible", + "Force_Disable_OpLog_For_Cache_Description": "No utilitzarà OpLog per sincronitzar la memòria cache fins i tot quan estigui disponible", "Force_Screen_Lock": "Forçar el bloqueig de pantalla", "Force_Screen_Lock_After": "Forçar el bloqueig de pantalla després de", - "Force_Screen_Lock_After_description": "El temps per a sol·licitar la contrasenya de nou després de finalitzar l'última sessió, en segons.", - "Force_Screen_Lock_description": "Quan estigui habilitat, obligarà els seus usuaris a utilitzar un PIN / BIOMETRIA / FaceID per desbloquejar l'aplicació.", + "Force_Screen_Lock_After_description": "El temps per sol·licitar la contrasenya novament després de finalitzar la darrera sessió, en segons.", + "Force_Screen_Lock_description": "Quan estigui habilitat, obligarà els usuaris a utilitzar un PIN / BIOMETRIA / FACEID per desbloquejar l'aplicació.", "Force_SSL": "Força SSL", - "Force_SSL_Description": "*Atenció!* _Force SSL_ mai ha de ser usat amb servidor intermediari invers. Si s'utilitza un proxy invers, s'ha de fer la redirecció AL PROXY. Aquesta opció existeix per a les instal·lacions tipus Heroku, que no permeten la configuració de redireccions al proxy invers.", + "Force_SSL_Description": "* Precaució! * _Force SSL_ mai ha d'usar-se amb proxy invers. Si teniu un servidor intermediari invers, heu de fer la redirecció ALLÁ. Aquesta opció existeix per a implementacions com Heroku, que no permet la configuració de redireccionament al servidor intermediari invers.", "Force_visitor_to_accept_data_processing_consent": "Obligar el visitant a acceptar el consentiment de l'processament de dades", "Force_visitor_to_accept_data_processing_consent_description": "Els visitants no poden començar a xatejar sense el seu consentiment.", "Force_visitor_to_accept_data_processing_consent_enabled_alert": "L'acord amb el processament de dades s'ha de basar en una comprensió transparent de l'motiu de l'processament. A causa d'això, ha de completar la configuració a continuació que es mostrarà als usuaris per proporcionar les raons per recopilar i processar la seva informació personal.", "force-delete-message": "Forçar esborrar missatge", "force-delete-message_description": "Permís per esborrar un missatge ignorant totes les restriccions", "Forgot_password": "Heu oblidat la contrasenya?", - "Forgot_Password_Description": "És possible utilitzar els marcadors:
      • [Forgot_Password_Url] per a l'adreça URL de recuperació de contrasenya.
      • [name], [fname], [lname] per al nom complet de l'usuari, nom o cognom, respectivament.
      • [email] per a l'adreça de correu electrònic de l'usuari.
      • [Site_Name] i [Site_URL] pel nom del lloc web i de l'adreça URL, respectivament.
      ", + "Forgot_Password_Description": "Podeu utilitzar les adreces d'interès següents:
      • [Forgot_Password_Url] per a la URL de recuperació de contrasenya.
      • [name], [fname] , [lname] per al nom complet, nom o cognom de l'usuari, respectivament.
      • [email] per al correu electrònic de l'usuari.
      • [Site_Name ] i [Site_URL] per al nom de l'aplicació i l'URL respectivament.
      ", "Forgot_Password_Email": "Fes clic aquí per restablir la teva contrasenya.", "Forgot_Password_Email_Subject": "[Site_Name] - Recuperació de contrasenya", "Forgot_password_section": "No recordo la contrasenya", - "Forward": "Remetre", + "Forward": "Reenviar", "Forward_chat": "Remetre xat", "Forward_to_department": "Remetre al departament", "Forward_to_user": "Remetre a l'usuari", @@ -2051,7 +2064,7 @@ "Global_purge_override_warning": "Hi ha una política de retenció global. Si deixa desactivada l'opció \"Anul·lar la política de retenció global\", només pot aplicar una política que sigui més estricta que la política global.", "Global_Search": "Cerca global", "Go_to_your_workspace": "Aneu a l'espai de treball", - "GoogleCloudStorage": "Emmagatzematge Google Cloud", + "GoogleCloudStorage": "Emmagatzematge al núvol de Google", "GoogleNaturalLanguage_ServiceAccount_Description": "Arxiu JSON amb la clau del compte de servei (\"Service account key\"). Pots trobar més informació [aquí](https://cloud.google.com/natural-language/docs/common/auth#set_up_a_service_account)", "GoogleTagManager_id": "ID de Google Tag Manager", "Government": "Govern", @@ -2071,9 +2084,9 @@ "Header_and_Footer": "Encapçalament i peu ", "Pharmaceutical": "Farmacèutica", "Healthcare": "Sanitat", - "Helpers": "Ajuda", + "Helpers": "Ajudants", "Here_is_your_authentication_code": "Aquest és el seu codi d'autenticació:", - "Hex_Color_Preview": "Previsualització del color", + "Hex_Color_Preview": "Vista prèvia de color hexadecimal", "Hi": "Hola", "Hi_username": "Hola __name__", "Hidden": "Ocult", @@ -2100,11 +2113,11 @@ "hours": "hores", "Hours": "Hores", "How_friendly_was_the_chat_agent": "Ha sigut amable l'interlocutor?", - "How_knowledgeable_was_the_chat_agent": "Era un bon expert?", - "How_long_to_wait_after_agent_goes_offline": "Quant temps esperar un cop l'agent es posa fora de línia", + "How_knowledgeable_was_the_chat_agent": "Era un bon expert, en sabia?", + "How_long_to_wait_after_agent_goes_offline": "Quant de temps esperar després que l'agent es desconnecti", "How_long_to_wait_to_consider_visitor_abandonment": "Quant de temps esperar per considerar l'abandonament dels visitans?", "How_long_to_wait_to_consider_visitor_abandonment_in_seconds": "Quant de temps esperar per considerar l'abandonament dels visitans?", - "How_responsive_was_the_chat_agent": "Heu rebut respostes ràpides?", + "How_responsive_was_the_chat_agent": "Què tan receptiu va ser lagent de xat?", "How_satisfied_were_you_with_this_chat": "Ha quedat satisfet amb aquesta conversa?", "How_to_handle_open_sessions_when_agent_goes_offline": "Com gestionar sessions obertes quan l'agent es desconnecta", "I_Saved_My_Password": "He desat la meva contrasenya", @@ -2115,22 +2128,22 @@ "If_you_are_sure_type_in_your_password": "Si n'està segur escrigui la contrasenya:", "If_you_are_sure_type_in_your_username": "Si n'està segur escrigui el seu nom d'usuari:", "If_you_didnt_ask_for_reset_ignore_this_email": "Si no va sol·licitar el restabliment de la contrasenya, pot ignorar aquest correu electrònic.", - "If_you_didnt_try_to_login_in_your_account_please_ignore_this_email": "Si no va intentar iniciar la sessió al compte, ignori aquest correu electrònic.", - "If_you_dont_have_one_send_an_email_to_omni_rocketchat_to_get_yours": "Si no n'hi ha, envieu un correu electrònic a [omni@rocket.chat] (mailto: omni@rocket.chat) per obtenir el vostre.", + "If_you_didnt_try_to_login_in_your_account_please_ignore_this_email": "Si no heu intentat iniciar sessió al vostre compte, ignoreu aquest correu electrònic.", + "If_you_dont_have_one_send_an_email_to_omni_rocketchat_to_get_yours": "Si no en teniu un, envieu un correu electrònic a [omni@rocket.chat] (mailto: omni@rocket.chat) per obtenir el vostre.", "Iframe_Integration": "Integració Iframe", "Iframe_Integration_receive_enable": "Activa recepció", "Iframe_Integration_receive_enable_Description": "Permetre que la finestra pare enviï ordres a Rocket.Chat.", "Iframe_Integration_receive_origin": "Rebre orígens", - "Iframe_Integration_receive_origin_Description": "Origens amb prefix del protocol, separats per comes, dels quals es permet rebre comandes. Exemple 'http://localhost, https://localhost', o * per permetre rebre de qualsevol lloc.", + "Iframe_Integration_receive_origin_Description": "Orígens amb prefix de protocol, separats per comes, que poden rebre ordres, p. Ex. 'https:// localhost, http://localhost', o * per permetre rebre des de qualsevol lloc.", "Iframe_Integration_send_enable": "Activa enviament", "Iframe_Integration_send_enable_Description": "Envia esdeveniments a la finestra pare", "Iframe_Integration_send_target_origin": "Envia l'origen a l'objectiu", - "Iframe_Integration_send_target_origin_Description": "Origens amb prefix del protocol on les comandes són enviades. Exemple 'https://localhost', o * per permetre enviar a qualsevol lloc.", + "Iframe_Integration_send_target_origin_Description": "Origen amb prefix de protocol, al qual s'envien les ordres, p. Ex. 'https://localhost', o * per permetre l'enviament a qualsevol lloc.", "Iframe_Restrict_Access": "Restringir l'accés dins de qualsevol iframe", "Iframe_Restrict_Access_Description": "Aquesta configuració habilita / inhabilita les restriccions per carregar el RC dins de qualsevol iframe", "Iframe_X_Frame_Options": "Opcions de X-Frame-Options", "Iframe_X_Frame_Options_Description": "Opcions de X-Frame-Options. [Podeu consultar més detalls aquí.] (Https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options#Syntax)", - "Ignore": "Ignora", + "Ignore": "Ignorar", "Ignored": "Ignorat", "Images": "Imatges", "IMAP_intercepter_already_running": "L'interceptador IMAP ja està executant-se", @@ -2149,13 +2162,13 @@ "Importer_finishing": "Finalitza la importació.", "Importer_From_Description": "Importa les dades de __from__ a Rocket.Chat.", "Importer_HipChatEnterprise_BetaWarning": "Tingueu en compte que aquest sistema d'importació encara està en desenvolupament. Si us plau, notifiqueu-nos a GitHub els errors que es produeixin:", - "Importer_HipChatEnterprise_Information": "L'arxiu pujat ha de ser un tar.gz desencriptat. Si us plau, llegiu la documentació per a més informació:", + "Importer_HipChatEnterprise_Information": "L'arxiu carregat ha de ser un tar.gz desxifrat, llegiu la documentació per obtenir més informació:", "Importer_import_cancelled": "Importació cancel·lada.", "Importer_import_failed": "S'ha produït un error durant la importació.", "Importer_importing_channels": "Important els canals.", "Importer_importing_files": "Importació d'arxius.", "Importer_importing_messages": "Important els missatges.", - "Importer_importing_started": "Començant la importació.", + "Importer_importing_started": "Iniciant la importació.", "Importer_importing_users": "Important els usuaris.", "Importer_not_in_progress": "L'importador actualment no s'està executant.", "Importer_not_setup": "L'importador no està configurat correctament ja que no ha retornat cap dada.", @@ -2186,7 +2199,7 @@ "importer_status_uploading": "Pujant arxiu", "importer_status_user_selection": "Preparat per seleccionar què voleu importar", "Importer_Upload_FileSize_Message": "La configuració del servidor permet pujar arxius de mida fins __maxFileSize__.", - "Importer_Upload_Unlimited_FileSize": "La configuració del seu servidor permet la pujada d'arxius de qualsevol mida.", + "Importer_Upload_Unlimited_FileSize": "La configuració del vostre servidor permet la càrrega de fitxers de qualsevol mida.", "Importing_channels": "Important els canals", "Importing_Data": "Importació de dades", "Importing_messages": "Important els missatges.", @@ -2205,7 +2218,7 @@ "Install": "Instal·lar", "Install_Extension": "Instal·lar complement", "Install_FxOs": "Instal·lar el Rocket.Chat al Firefox", - "Install_FxOs_done": "Perfecte! Ja pots utilitzar el Rocket.Chat mitjançant la icona de l'escriptori.", + "Install_FxOs_done": "Excel·lent! Ara podeu utilitzar Rocket.Chat a través de la icona a la pantalla inicial. Diverteix-te amb Rocket.Chat!", "Install_FxOs_error": "Sentim que no hagi funcionat bé! S'ha topat amb el següent error:", "Install_FxOs_follow_instructions": "Si us plau, confirma la instal·lació de l'aplicació al teu dispositiu (polsi \"Instal·lar\" quan se us demani).", "Install_package": "Instal·leu el paquet", @@ -2237,23 +2250,23 @@ "Integration_Outgoing_WebHook_History_Http_Response_Error": "Error HTTP de resposta", "Integration_Outgoing_WebHook_History_Messages_Sent_From_Prepare_Script": "Missatges enviats durant el pas de preparació", "Integration_Outgoing_WebHook_History_Messages_Sent_From_Process_Script": "Missatges enviats durant el procés de la resposta", - "Integration_Outgoing_WebHook_History_Time_Ended_Or_Error": "Quan ha acabat o emès un error", + "Integration_Outgoing_WebHook_History_Time_Ended_Or_Error": "Hora de finalització o error", "Integration_Outgoing_WebHook_History_Time_Triggered": "Quan s'ha disparat la integració", "Integration_Outgoing_WebHook_History_Trigger_Step": "Darrer pas del disparador", - "Integration_Outgoing_WebHook_No_History": "Aquesta integració WebHook de sortida encara no té cap historial registrat.", + "Integration_Outgoing_WebHook_No_History": "Aquesta integració de webhook sortint encara no té cap historial registrat.", "Integration_Retry_Count": "Comptador de reintents", - "Integration_Retry_Count_Description": "Quantes vegades s'ha de reintentar la integració si la petició a l'adreça URL falla?", + "Integration_Retry_Count_Description": "Quantes vegades s'ha d'intentar la integració si falla la trucada a la URL?", "Integration_Retry_Delay": "Temps de reintent", "Integration_Retry_Delay_Description": "Quin algorisme de retard de reintent s'ha d'utilitzar? 10^x o 2^x o x*2", "Integration_Retry_Failed_Url_Calls": "Reintenta peticions d'URL fallades", - "Integration_Retry_Failed_Url_Calls_Description": "Si la petició a l'adreça URL falla, la integració ha d'esperar un temps raonable abans de reintentar-la?", + "Integration_Retry_Failed_Url_Calls_Description": "Hauríeu d'intentar la integració una quantitat de temps raonable si falla la trucada a la URL?", "Integration_Run_When_Message_Is_Edited": "Executa en edicions", "Integration_Run_When_Message_Is_Edited_Description": "Aquesta integració s'ha d'executar quan el missatge s'edita? Desactivar aquesta opció farà que la integració només s'executi en missatges nous .", "Integration_updated": "La integració s'ha actualitzat.", - "Integration_Word_Trigger_Placement": "Posició indeterminada de paraula", + "Integration_Word_Trigger_Placement": "Col·locació de paraules a qualsevol lloc", "Integration_Word_Trigger_Placement_Description": "S'hauria d'activar el disparador quan la paraula es troba en un lloc de la frase fora del començament?", "Integrations": "Integracions", - "Integrations_for_all_channels": "Introdueix all_public_channels per escoltar a totes les sales públiques, all_private_groups per escoltar a tots els grups privats i all_direct_messages per escoltar tots els missatges directes.", + "Integrations_for_all_channels": "Introduïu all_public_channels per escoltar a tots els canals públics, all_private_groups per escoltar a tots els grups privats i all_direct_messages per escoltar tots els missatges directes", "Integrations_Outgoing_Type_FileUploaded": "Arxiu pujat", "Integrations_Outgoing_Type_RoomArchived": "Sala arxivada", "Integrations_Outgoing_Type_RoomCreated": "Sala creada (pública i privada)", @@ -2291,7 +2304,7 @@ "Invitation": "Invitació", "Invitation_Email_Description": "Podeu utilitzar els següents marcadors:
      • [email] per a l'adreça del receptor del missatge.
      • [Site_Name] i [Site_URL] per al nom de l'aplicació i l'adreça URL, respectivament.
      ", "Invitation_HTML": "HTML de la invitació", - "Invitation_HTML_Default": "

      Se us ha convidat a [Site_Name]

      Aneu a [Site_URL] i proveu la millor solució de col·laboració a distància de codi lliure!

      ", + "Invitation_HTML_Default": "

      Se us ha convidat a [Site_Name]

      Aneu a [Site_URL] i provar la millor solució de xat de codi obert disponibles actualment!

      ", "Invitation_Subject": "Assumpte de la invitació", "Invitation_Subject_Default": "Se us ha convidat a [Site_Name]", "Invite": "Invitació", @@ -2301,10 +2314,10 @@ "Invite_user_to_join_channel_all_to": "Invita a tots els usuaris d'aquest canal a unir-se a [#channel]", "Invite_Users": "Convidar usuaris", "IP": "IP", - "IRC_Channel_Join": "Resposta de la comanda JOIN.", + "IRC_Channel_Join": "Sortida de l'ordre JOIN.", "IRC_Channel_Leave": "Resposta de la comanda PART.", "IRC_Channel_Users": "Resposta de la comanda NAMES.", - "IRC_Channel_Users_End": "Final de la resposta de la comanda NAMES.", + "IRC_Channel_Users_End": "Final de la sortida de l'ordre NAMES.", "IRC_Description": "Internet Relay Chat (IRC) és una eina de comunicació grupal basada en text. Els usuaris s'uneixen a canals o sales amb noms exclusius per a una discussió oberta. IRC també admet missatges privats entre usuaris individuals i capacitats per a compartir arxius. Aquest paquet integra aquestes capes de funcionalitat amb Rocket.Chat.", "IRC_Enabled": "Integrar suport IRC. Canviar aquest valor requereix reiniciar el Rocket.Chat.", "IRC_Enabled_Alert": "El suport d'IRC és un treball en progrés. No es recomana el seu ús en un sistema de producció en aquest moment.", @@ -2312,7 +2325,7 @@ "IRC_Federation_Disabled": "La Federació IRC està desactivada.", "IRC_Hostname": "Servidor d'IRC on connectar.", "IRC_Login_Fail": "Resposta en cas de connexió al servidor d'IRC fallida.", - "IRC_Login_Success": "Resposta en cas de connexió al servidor d'IRC reeixida.", + "IRC_Login_Success": "Sortida després d'una connexió amb èxit al servidor IRC.", "IRC_Message_Cache_Size": "Límit de memòria d'intercanvi (cache) per manegar els missatges sortints.", "IRC_Port": "Port on unir-se al servidor d'IRC.", "IRC_Private_Message": "Resposta de la comanda PRIVMSG.", @@ -2330,32 +2343,34 @@ "italics": "cursiva", "Items_per_page:": "Elements per pàgina:", "Jitsi_Application_ID": "ID d'aplicació (iss)", - "Jitsi_Application_Secret": "Secret d'aplicació", - "Jitsi_Chrome_Extension": "ID d'extensió del Chrome", + "Jitsi_Application_Secret": "Secret de l'aplicació", + "Jitsi_Chrome_Extension": "ID de l'extensió del Chrome", "Jitsi_Enable_Channels": "Activa als canals", "Jitsi_Enable_Teams": "Habilitar per equips", "Jitsi_Enabled_TokenAuth": "Activa l'autenticació JWT", - "Jitsi_Limit_Token_To_Room": "Limita el token a Jitsi Room", + "Jitsi_Limit_Token_To_Room": "Limitar token a la Room Jitsi", "Job_Title": "Títol professional", "join": "Unir-se", + "Join_call": "Unir-se a la trucada", "Join_audio_call": "Unir-se a la trucada", "Join_Chat": "Uneix-te al xat", "Join_default_channels": "Unir-se als canals predeterminats", "Join_the_Community": "Uneix-te a la comunitat", "Join_the_given_channel": "Unir-se al canal proporcionat", "Join_video_call": "Unir-se a la videotrucada", + "Join_my_room_to_start_the_video_call": "Uneix-te a la meva sala per iniciar la videotrucada", "join-without-join-code": "Unir-se sense el codi", "join-without-join-code_description": "Permís per unir-se a canals amb codi d'unió actiu sense tenir-lo", "Joined": "Unit", "Joined_at": "Inscrit a", - "Jump": "Vés", - "Jump_to_first_unread": "Vés al primer no llegit", + "Jump": "Saltar", + "Jump_to_first_unread": "Anar al primer no llegit", "Jump_to_message": "Vés al missatge", "Jump_to_recent_messages": "Vés als missatges recents", "Just_invited_people_can_access_this_channel": "Només les persones convidades poden accedir a aquest canal.", "Katex_Dollar_Syntax": "Permetre Dòlar Sintaxi", "Katex_Dollar_Syntax_Description": "Permetre l'ús de $$ bloc katex $$ $ i $ Katex línia sintaxi", - "Katex_Enabled": "Katex actiu", + "Katex_Enabled": "Katex Habilitada", "Katex_Enabled_Description": "Permetre l'ús de katex per a la composició tipogràfica de matemàtiques en els missatges", "Katex_Parenthesis_Syntax": "Permetre Parèntesi Sintaxi", "Katex_Parenthesis_Syntax_Description": "Permetre l'ús de \\ [bloc katex \\] \\ sintaxi i (en línia katex \\)", @@ -2372,7 +2387,7 @@ "Keyboard_Shortcuts_Mark_all_as_read": "Marca tots els missatges (en tots els canals) com a llegits", "Keyboard_Shortcuts_Move_To_Beginning_Of_Message": "Moure's al principi del missatge", "Keyboard_Shortcuts_Move_To_End_Of_Message": "Moure's al final del missatge", - "Keyboard_Shortcuts_New_Line_In_Message": "Nova línia al camp d'entrada de missatge", + "Keyboard_Shortcuts_New_Line_In_Message": "Nova línia a l'entrada de redacció del missatge", "Keyboard_Shortcuts_Open_Channel_Slash_User_Search": "Canal obert / Cerca d'usuari", "Keyboard_Shortcuts_Title": "Dreceres de teclat", "Knowledge_Base": "Centre de suport", @@ -2405,25 +2420,25 @@ "Language_Swedish": "Suec", "Language_Version": "Versió en català", "Last_7_days": "Els darrers 7 dies", - "Last_30_days": "Darrers 30 dies", + "Last_30_days": "Últims 30 Dies", "Last_90_days": "Darrers 90 dies", "Last_active": "Darrer actiu", "Last_Chat": "Darrer xat", "Last_login": "Darrer inici de sessió", "Last_Message": "Últim missatge", "Last_Message_At": "Últim missatge a", - "Last_seen": "Vist per darrer cop", + "Last_seen": "Última vegada vist", "Last_Status": "Darrer estat", "Last_token_part": "Darrera part del token", "Last_Updated": "Última actualització", - "Launched_successfully": "S'ha iniciat amb èxit", + "Launched_successfully": "Llançat amb èxit", "Layout": "Disseny", "Layout_Home_Body": "Cos de pàgina d'inici", "Layout_Home_Title": "Títol de pàgina d'inici", "Layout_Legal_Notice": "Avís legal", "Layout_Login_Terms": "Termes d'inici de sessió", "Layout_Privacy_Policy": "Política de privacitat", - "Layout_Show_Home_Button": "Mostra el botó inici\"", + "Layout_Show_Home_Button": "Mostra el \"botó inici\"", "Layout_Sidenav_Footer": "Peu de la barra de navegació lateral", "Layout_Sidenav_Footer_description": "La mida del peu és de 260 x 70 px", "Layout_Terms_of_Service": "Avís legal", @@ -2458,7 +2473,7 @@ "LDAP_Authentication_UserDN": "User DN", "LDAP_Authentication_UserDN_Description": "Usuari LDAP que fa cerques d'usuari per identificar altres usuaris quan inicien sessió.
      Aquest és un compte que s'acostuma a crear específicament per a fer les integracions de tercers. Utilitza un nom complet i qualificat, com `cn=Administrator,cn=Users,dc=Example,dc=com`.", "LDAP_Avatar_Field": "Camp d’avatar d’usuari", - "LDAP_Avatar_Field_Description": "Què camp s'utilitzarà com * avatar * per als usuaris. Deixi-ho en blanc per a utilitzar `thumbnailPhoto` primer i` jpegPhoto` com a alternativa.", + "LDAP_Avatar_Field_Description": "Quin camp s'utilitzarà com a *avatar* per als usuaris. Deixeu-lo en blanc per utilitzar `thumbnailPhoto` primer i `jpegPhoto` com a respatller.", "LDAP_Background_Sync": "Sincronització de fons", "LDAP_Background_Sync_Avatars": "Sincronització de fons d'avatar", "LDAP_Background_Sync_Avatars_Description": "Habiliteu un procés en segon pla separat per sincronitzar els avatars dels usuaris.", @@ -2470,26 +2485,26 @@ "LDAP_Background_Sync_Keep_Existant_Users_Updated": "Actualització de fons de sincronització dels usuaris existents", "LDAP_Background_Sync_Keep_Existant_Users_Updated_Description": "Sincronitzarà l'avatar, els camps, el nom d'usuari, etc. (Segons la seva configuració) de tots els usuaris ja importats d'LDAP en cada ** Interval de sincronització **", "LDAP_BaseDN": "Base DN", - "LDAP_BaseDN_Description": "El nom distingit (DN) completament qualificat d'un subarbre LDAP que voleu cercar usuaris i grups. Podeu afegir tants com vulgui; però, cada grup ha d'estar definit en la mateixa base de domini que els usuaris que li pertanyen. Exemple: `ou = Usuaris + ou = Projectes, dc = exemple, dc = com`. Si s'especifica grups d'usuaris restringits, només els usuaris que pertanyen a aquests grups estaran dins de l'abast. Li recomanem que especifiqui el nivell superior de la seva vista de directori LDAP com a base del seu domini i utilitzi el filtre de cerca per controlar l'accés.", + "LDAP_BaseDN_Description": "El nom distingit (DN) complet d'un subarbre LDAP on voleu cercar usuaris i grups. Podeu afegir tants com vulgueu; no obstant això, cada grup ha d'estar definit a la mateixa base de domini que els usuaris que hi pertanyen. Exemple: `ou = Usuaris + ou = Projectes, dc = Exemple, dc = com`. Si especifiqueu grups d'usuaris restringits, només els usuaris que pertanyen a aquests grups estaran dins de l'abast. Us recomanem que especifiqueu el nivell superior del vostre arbre de directoris LDAP com a base del vostre domini i utilitzeu el filtre de cerca per controlar l'accés.", "LDAP_CA_Cert": "CA Cert", "LDAP_Connect_Timeout": "Temps d'espera connexió (ms)", "LDAP_DataSync_AutoLogout": "Usuaris desactivats de tancament de sessió automàtic", "LDAP_Default_Domain": "Domini predeterminat", - "LDAP_Default_Domain_Description": "Si es proporciona el domini per defecte s'utilitzarà per crear un correu electrònic únic per als usuaris en què el correu electrònic no s'ha importat des de LDAP. El correu electrònic es muntarà com a `nomusuari@dominiperdefecte` o `nom_usuari_unic@dominiperdefecte`.
      Exemple: `rocket.chat`", - "LDAP_Enable": "Activa", + "LDAP_Default_Domain_Description": "si es proporciona, el domini per defecte s'utilitzarà per crear un correu electrònic únic per als usuaris en què el correu electrònic no s'ha importat des de LDAP. El correu electrònic es muntarà com a `username @ default_domain` o ` unique_id @ default_domain`.
      Exemple: `rocket.chat`", + "LDAP_Enable": "Habilitar", "LDAP_Enable_Description": "Intentar utilitzar LDAP com a mètode d'autenticació", "LDAP_Enable_LDAP_Groups_To_RC_Teams": "Habiliteu el mapeig de l'equip de LDAP a Rocket.Chat", "LDAP_Encryption": "Xifrat", "LDAP_Encryption_Description": "Mètode de xifrat utilitzat per a la comunicació segura cap al servidor LDAP. Alguns exemples 'sense xifrat', 'SSL / LDAPS (xifrat des de l'inici), i' StartTLS '(actualitzar a comunicacions xifrades una vegada connectat).", "LDAP_Find_User_After_Login": "Cerca l'usuari després d'iniciar sessió", "LDAP_Find_User_After_Login_Description": "Realitzarà una recerca de l'DN de l'usuari després de la vinculació per garantir que la vinculació es va realitzar correctament i evitarà l'inici de sessió amb contrasenyes buides quan ho permeti la configuració d'AD.", - "LDAP_Group_Filter_Enable": "Activa el filtre de grups d'usuaris LDAP", + "LDAP_Group_Filter_Enable": "Habilita el filtre de grup d'usuaris LDAP", "LDAP_Group_Filter_Enable_Description": "Restringir l'accés als usuaris en un grup LDAP
      Útil per permetre que els servidors OpenLDAP sense un filtre * memberOf * restringeixin l'accés per grups", - "LDAP_Group_Filter_Group_Id_Attribute": "Atribut ID de grup (Group ID)", + "LDAP_Group_Filter_Group_Id_Attribute": "Atribut ID de grup", "LDAP_Group_Filter_Group_Id_Attribute_Description": "Exemple: *OpenLDAP:*cn", "LDAP_Group_Filter_Group_Member_Attribute": "Atribut Membre de grup (Group Member)", "LDAP_Group_Filter_Group_Member_Attribute_Description": "Exemple: *OpenLDAP:*uniqueMember", - "LDAP_Group_Filter_Group_Member_Format": "Format Membre de grup (Group Member)", + "LDAP_Group_Filter_Group_Member_Format": "Format de membre del grup", "LDAP_Group_Filter_Group_Member_Format_Description": "Exemple: *OpenLDAP:*uid=#{username},ou=users,o=Company,c=com", "LDAP_Group_Filter_Group_Name": "Nom del grup", "LDAP_Group_Filter_Group_Name_Description": "Nom del grup on pertany l'usuari", @@ -2504,8 +2519,8 @@ "LDAP_Internal_Log_Level": "Nivell de log intern", "LDAP_Login_Fallback": "Inici de sessió alternativa (fallback)", "LDAP_Login_Fallback_Description": "Si l'inici de sessió LDAP no funciona, intenta iniciar-la amb el sistema de comptes per defecte/local. Útil si el servei LDAP no està disponible per algun motiu.", - "LDAP_Merge_Existing_Users": "Uneix usuaris existents", - "LDAP_Merge_Existing_Users_Description": "* Precaució! * A l'importar un usuari de LDAP i ja existeix un usuari amb el mateix nom d'usuari, la informació i la contrasenya d'LDAP s'establiran en l'usuari existent.", + "LDAP_Merge_Existing_Users": "Fusiona els usuaris existents", + "LDAP_Merge_Existing_Users_Description": "* Precaució! * Quan s'importa un usuari de LDAP i ja existeix un usuari amb el mateix nom d'usuari, la informació i la contrasenya de LDAP s'establiran a l'usuari existent.", "LDAP_Port": "Port", "LDAP_Port_Description": "Port per accedir a LDAP. Ex. `389` o `636` per LDAPS", "LDAP_Prevent_Username_Changes": "Impedir que els usuaris d'LDAP canviïn el nom d'usuari de Rocket.Chat", @@ -2513,11 +2528,11 @@ "LDAP_Reconnect": "Reconnecta", "LDAP_Reconnect_Description": "Proveu tornar a connectar-se automàticament quan la connexió s'interrompi per algun motiu mentre executa operacions", "LDAP_Reject_Unauthorized": "Rebutja no autoritzat", - "LDAP_Reject_Unauthorized_Description": "Desactiveu aquesta opció per permetre certificats que no es poden verificar. En general, els certificats autofirmados requeriran que aquesta opció estigui desactivada per funcionar", + "LDAP_Reject_Unauthorized_Description": "Desactiveu aquesta opció per permetre certificats que no es poden verificar. En general, els certificats autosignats requeriran que aquesta opció estigui desactivada per funcionar", "LDAP_Search_Page_Size": "Mida de la pàgina de cerca", "LDAP_Search_Page_Size_Description": "El nombre màxim d'entrades que cada pàgina de resultats tornarà a processar", "LDAP_Search_Size_Limit": "Límit de la mida de la cerca", - "LDAP_Search_Size_Limit_Description": "El nombre màxim d'entrades a tornar.
      **Atenció** Aquest número hauria de ser superior a **Mida de la pàgina de cerca**", + "LDAP_Search_Size_Limit_Description": "El nombre màxim d'entrades per tornar.
      ** Atenció ** Aquest número ha de ser més gran que ** Mida de la pàgina de cerca **", "LDAP_Sync_Custom_Fields": "Sincronitzar camps personalitzats", "LDAP_CustomFieldMap": "Assignació de camps personalitzats", "LDAP_Sync_AutoLogout_Enabled": "Habilitar tancament de sessió automàtic", @@ -2551,15 +2566,19 @@ "LDAP_Sync_User_Data_Roles_Filter_Description": "El filtre de cerca LDAP que s'usa per verificar si un usuari està en un grup.", "LDAP_Sync_User_Data_RolesMap": "Mapa de grup de dades d'usuari", "LDAP_Sync_User_Data_RolesMap_Description": "Mapeja els grups LDAP als rols d'usuari de Rocket.Chat
      Com a exemple, `{\"rocket-admin\":\"admin\", \"tech-support\":\"support\"}` mapejarà el grup LDAP de rocket- admin a el paper de \"admin\" de Rocket.", + "LDAP_Teams_BaseDN": "Equips LDAP BaseDN", + "LDAP_Teams_BaseDN_Description": "El LDAP BaseDN utilitza't per a cercar equips d'usuari.", + "LDAP_Teams_Name_Field": "Atribut Nom de l'equip LDAP", + "LDAP_Teams_Name_Field_Description": "L'atribut LDAP que Rocket.Chat ha d'utilitzar per carregar el nom de l'ordinador. Podeu especificar més d'un nom d'atribut possible si els separa amb una coma.", "LDAP_Timeout": "Temps d'espera (ms)", "LDAP_Timeout_Description": "Quants mil·lisegons esperen un resultat de cerca abans de tornar un error", "LDAP_Unique_Identifier_Field": "Camp d'identificador únic", - "LDAP_Unique_Identifier_Field_Description": "Aquest camp s'utilitzarà per vincular l'usuari LDAP amb l'usuari Rocket.Chat. Pot proporcionar diversos valors separats per coma per intentar obtenir el valor del registre LDAP.
      El valor per defecte és `objectGUID,ibm-entryUUID,GUID,dominoUNID,nsuniqueId,uidNumber`", + "LDAP_Unique_Identifier_Field_Description": "Quin camp s'utilitzarà per enllaçar l'usuari LDAP i l'usuari de Rocket.Chat. Podeu informar diversos valors separats per comes per intentar obtenir el valor del registre LDAP.
      El valor per defecte és `objectGUID, ibm-entryUUID, GUID, dominoUNID, nsuniqueId, uidNumber`", "LDAP_User_Found": "Usuari LDAP trobat", "LDAP_User_Search_AttributesToQuery": "Atributs per consulta", "LDAP_User_Search_AttributesToQuery_Description": "Especifiqueu quins atributs han de tornar-en les consultes LDAP, separant-los amb comes. Valors predeterminats per a tot. `*` Representa tots els atributs regulars i `+` representa tots els atributs operatius. Assegureu-vos d'incloure tots els atributs que utilitzen totes les opcions de sincronització de Rocket.Chat.", "LDAP_User_Search_Field": "Camp de cerca", - "LDAP_User_Search_Field_Description": "L'atribut LDAP que identifica l'usuari LDAP que intenta l'autenticació. Aquest camp ha de ser \"sAMAccountName\" per a la majoria de les instal·lacions d'Active Directory, però pot ser \"uid\" per a altres solucions LDAP, com OpenLDAP. Feu servir `mail` per identificar els usuaris per correu electrònic o qualsevol atribut que vulgueu.
      Podeu usar diversos valors separats per comes per permetre que els usuaris iniciïn sessió fent servir múltiples identificadors com a nom d'usuari o correu electrònic.", + "LDAP_User_Search_Field_Description": "L'atribut LDAP que identifica l'usuari LDAP que intenta autenticació. Aquest camp ha de ser \"sAMAccountName\" per a la majoria de les instal·lacions d'Active Directory, però pot ser \"uid\" per a altres solucions LDAP, com ara OpenLDAP. Podeu utilitzar `mail` per identificar els usuaris per correu electrònic o qualsevol atribut que vulgueu.
      Podeu utilitzar diversos valors separats per comes per permetre que els usuaris iniciïn sessió usant múltiples identificadors com a nom d'usuari o correu electrònic.", "LDAP_User_Search_Filter": "Filter", "LDAP_User_Search_Filter_Description": "Si s'especifica, només els usuaris que compleixin aquest filtre podran iniciar sessió. Si no s'especifica cap filtre, tots els usuaris del domini base podran fer-ho.
      Exemple per Active Directory `memberOf=cn=ROCKET_CHAT,ou=General Groups`.
      Exemple per OpenLDAP (cerca de patró extensible) `ou:dn:=ROCKET_CHAT`.", "LDAP_User_Search_Scope": "Scope", @@ -2572,7 +2591,7 @@ "Lead_capture_phone_regex": "Regex de telèfon de captura clients potencials", "Leave": "Sortir ", "Leave_a_comment": "Deixar un comentari", - "Leave_Group_Warning": "Segur que vols abandonar el grup \"%s\"?", + "Leave_Group_Warning": "Segur que vols deixar el grup \"%s\"?", "Leave_Livechat_Warning": "Segur que vols sortir de l'LiveChat amb \"% s\"?", "Leave_Private_Warning": "Segur que vols sortir de la conversa amb \"%s\"?", "Leave_room": "Sortir ", @@ -2623,7 +2642,7 @@ "Livechat_Inquiry_Already_Taken": "Sol·licitud de LiveChat ja atesa", "Livechat_Installation": "Instal·lació de Livechat", "Livechat_last_chatted_agent_routing": "Agent preferit en l'últim xat", - "Livechat_last_chatted_agent_routing_Description": "La configuració de l'últim agent amb el qual va conversar assigna xats a l'agent que va interactuar anteriorment amb el mateix visitant si l'agent està disponible quan s'inicia el xat.", + "Livechat_last_chatted_agent_routing_Description": "La configuració del darrer agent amb què va conversar assigna xats a l'agent que va interactuar anteriorment amb el mateix visitant si l'agent està disponible quan s'inicia el xat.", "Livechat_managers": "Supervisors de LiveChat", "Livechat_Managers": "Administradors", "Livechat_max_queue_wait_time_action": "Com gestionar els xats a la cua quan s'arriba al temps màxim d'espera", @@ -2632,7 +2651,7 @@ "Livechat_message_character_limit": "Límit de caràcters de missatge de LiveChat", "Livechat_monitors": "Monitors de Livechat", "Livechat_Monitors": "Monitors", - "Livechat_offline": "LiveChat fora de línia", + "Livechat_offline": "LiveChat desconectat", "Livechat_offline_message_sent": "Missatge de LiveChat enviat sense connexió", "Livechat_OfflineMessageToChannel_enabled": "Enviar missatges sense connexió d'LiveChat a un canal", "Omnichannel_on_hold_chat_resumed": "Represa de xat en espera: __comment__", @@ -2664,6 +2683,7 @@ "Livechat_Triggers": "Activadors LiveChat", "Livechat_user_sent_chat_transcript_to_visitor": "__agent__ va enviar la transcripció de xat a __guest__", "Livechat_Users": "Usuaris de LiveChat ", + "Livechat_Calls": "Trucades Livechat", "Livechat_visitor_email_and_transcript_email_do_not_match": "El correu electrònic del visitant i el de la transcripció no coincideixen", "Livechat_visitor_transcript_request": "__guest__ ha sol·licitat la transcripció del xat", "LiveStream & Broadcasting": "Transmissió en directe i transmissió", @@ -2732,7 +2752,7 @@ "mail-messages": "Missatges via correu-e", "mail-messages_description": "Permís per utilitzar l'opció d'enviament de missatges via correu-e", "Mailer": "Missatge correu-e", - "Mailer_body_tags": "És necessari utilitzar [unsubscribe] per a l'enllaç d'anul·lació de la subscripció.
      És possible utilitzar [name], [fname], [lname] per al nom complet de l'usuari, nom o cognom, respectivament.
      També [email] per a l'adreça de correu electrònic de l'usuari.", + "Mailer_body_tags": "Vostè ha de utilitzar [unsubscribe] per a l'enllaç de cancel·lació de subscripció.
      Podeu utilitzar [name], [fname], [lname] per al nom complet, nom o cognom de l'usuari, respectivament.
      Podeu utilitzar [email] per al correu electrònic de l'usuari.", "Mailing": "Enviament", "Make_Admin": "Fes admin", "Make_sure_you_have_a_copy_of_your_codes_1": "Assegureu-vos de tenir una còpia dels codis:", @@ -2753,7 +2773,7 @@ "manage-integrations_description": "Permís per gestionar les integracions del servidor", "manage-livechat-agents": "Administrar agents de LiveChat", "manage-livechat-agents_description": "Permís per gestionar agents Livechat", - "manage-livechat-departments": "Gestioneu els departaments de LiveChat", + "manage-livechat-departments": "Administrar departaments de LiveChat", "manage-livechat-departments_description": "Permís per gestionar departaments Livechat", "manage-livechat-managers": "Administrar administradors de LiveChat", "manage-livechat-managers_description": "Permís per gestionar gestors Livechat", @@ -2761,11 +2781,11 @@ "manage-oauth-apps_description": "Permís per gestionar les apps Oauth del servidor", "manage-outgoing-integrations": "Administrar les integracions sortints", "manage-outgoing-integrations_description": "Permís per gestionar les integracions sortints del servidor", - "manage-own-incoming-integrations": "Gestionar les pròpies integracions entrants", + "manage-own-incoming-integrations": "Administrar les pròpies integracions entrants", "manage-own-incoming-integrations_description": "Permís per permetre als usuaris crear i editar les seves pròpies integracions entrants o webhooks", "manage-own-integrations": "Gestionar les pròpies integracions", "manage-own-integrations_description": "Permís per permetre als usuaris crear i editar les seves pròpies integracions o webhooks", - "manage-own-outgoing-integrations": "Gestioneu les pròpies integracions sortints", + "manage-own-outgoing-integrations": "Administrar les pròpies integracions sortints", "manage-own-outgoing-integrations_description": "Permís per permetre als usuaris crear i editar les seves pròpies integracions de sortida o webhooks", "manage-selected-settings": "Canvieu alguns paràmetres", "manage-selected-settings_description": "Permís per canviar la configuració que es concedeix explícitament per canviar-la", @@ -2798,8 +2818,9 @@ "Markdown_Marked_Smartypants": "Habilita Smartypants marcats", "Markdown_Marked_Tables": "Activa les Taules Marcades", "Markdown_Parser": "Parsejador Markdown", - "Markdown_SupportSchemesForLink": "Markdown detecta scheme:// com a enllaç", + "Markdown_SupportSchemesForLink": "Esquemes de suport de Markdown per a enllaç", "Markdown_SupportSchemesForLink_Description": "Llista dels scheme:// permesos separats per comes", + "Marketplace": "Mercat", "Marketplace_view_marketplace": "Veure Marketplace", "MAU_value": "MAU __value__", "Max_length_is": "La llargada màxima és %s", @@ -2823,17 +2844,17 @@ "Mentions_default": "Mencions (per defecte)", "Mentions_only": "Només mencions", "Merge_Channels": "Combina Channels", - "message": "Missatge", + "message": "missatge", "Message": "Missatge", "Message_AllowBadWordsFilter": "Permet el filtratge de paraulotes", "Message_AllowConvertLongMessagesToAttachment": "Permetre la conversió dels missatges llargs en arxius adjunts", "Message_AllowDeleting": "Permet l'eliminació de missatges", "Message_AllowDeleting_BlockDeleteInMinutes": "Bloqueja l'eliminació de missatges després de (n) minuts", "Message_AllowDeleting_BlockDeleteInMinutes_Description": "Introdueix 0 per desactivar el bloqueig.", - "Message_AllowDirectMessagesToYourself": "Permetre missatges directes al propi usuari", + "Message_AllowDirectMessagesToYourself": "Permetre que els usuaris s'enviïn missatges directes a vostè mateix", "Message_AllowEditing": "Permet l'edició de missatges", "Message_AllowEditing_BlockEditInMinutes": "Bloqueja l'edició de missatges després de (n) minuts", - "Message_AllowEditing_BlockEditInMinutesDescription": "Introdueix 0 per desactivar el bloqueig.", + "Message_AllowEditing_BlockEditInMinutesDescription": "Introduïu 0 per desactivar el bloqueig.", "Message_AllowPinning": "Permet que es fixin missatges", "Message_AllowPinning_Description": "Permet que els missatges es puguin fixar a qualsevol canal.", "Message_AllowSnippeting": "Permet retalls de missatges (snippeting)", @@ -2843,7 +2864,7 @@ "Message_AlwaysSearchRegExp": "Sempre cercar utilitzant RegExp", "Message_AlwaysSearchRegExp_Description": "Recomanem activar-ho si el teu idioma no està suportat per la cerca de text MongoDB.", "Message_Attachments": "Adjunts al missatge", - "Message_Attachments_GroupAttach": "Agrupa els botons d'adjuntar", + "Message_Attachments_GroupAttach": "Grup de botons de arxius adjunts", "Message_Attachments_GroupAttachDescription": "Això uneix les icones en un menú desplegable. Ocupen menys espai a la pantalla.", "Message_Attachments_Thumbnails_Enabled": "Habiliteu les miniatures d'imatges per estalviar ample de banda", "Message_Attachments_Thumbnails_Width": "Ample màxim de la miniatura (en píxels)", @@ -2901,7 +2922,7 @@ "Message_HideType_subscription_role_added": "Ocultar els missatges de \"Rol establert\"", "Message_HideType_subscription_role_removed": "Ocultar els missatges \"Rol no definit\"", "Message_HideType_uj": "Amaga missatges \"Usuari unit\"", - "Message_HideType_ul": "Amaga missatges \"Usuari surt\"", + "Message_HideType_ul": "Amagar missatges de \"Sortida d'usuari\"", "Message_HideType_ut": "Ocultar els missatges de \"L'usuari es va unir a la conversa\"", "Message_HideType_wm": "Ocultar els missatges de \"Benvinguda\"", "Message_Id": "Identificador del missatge", @@ -2909,7 +2930,7 @@ "message-impersonate": "Fer-se passar per altres usuaris", "message-impersonate_description": "Permís per fer-se passar per altres usuaris utilitzant un àlies de missatge", "Message_info": "Informació del missatge", - "Message_KeepHistory": "Mantenir l'historial per missatge", + "Message_KeepHistory": "Mantingueu l'historial d'edició per missatge", "Message_MaxAll": "Mida màxima de Channel per a TOTS els missatges", "Message_MaxAllowedSize": "Caràcters màxims permesos per missatge", "Message_pinning": "Fixació de missatges", @@ -2931,7 +2952,7 @@ "Message_TimeFormat_Description": "Veure: Moment.js", "Message_too_long": "Missatge massa llarg", "Message_UserId": "ID d'usuari", - "Message_VideoRecorderEnabled": "Gravadora de vídeo activa", + "Message_VideoRecorderEnabled": "Gravadora de vídeo habilitat", "Message_VideoRecorderEnabledDescription": "Requereix que els fitxers de tipus 'video/webm' siguin admesos a la configuració de 'Puja fitxers'.", "Message_view_mode_info": "Això canvia l'espai que ocupen els missatges en pantalla.", "MessageBox_view_mode": "Mode de visualització de el panell de missatges", @@ -2951,7 +2972,7 @@ "meteor_status_connecting": "Connectant...", "meteor_status_failed": "La connexió del servidor ha fallat", "meteor_status_offline": "Mode fora de línia", - "meteor_status_reconnect_in": "provant de nou en un segon ...", + "meteor_status_reconnect_in": "intentant de nou en un segon ...", "meteor_status_reconnect_in_plural": "provant de nou d'aquí a __count__ segons ...", "meteor_status_try_now_offline": "Connectar de nou", "meteor_status_try_now_waiting": "Prova-ho ara", @@ -2967,12 +2988,14 @@ "Mobex_sms_gateway_from_number": "De", "Mobex_sms_gateway_from_number_desc": "Adreça / número de telèfon d'origen en enviar un nou SMS al client de LiveChat", "Mobex_sms_gateway_from_numbers_list": "Llista de números des d’on enviar SMS", - "Mobex_sms_gateway_from_numbers_list_desc": "Llista de números separats per comes per utilitzar per enviar missatges nous, per exemple. 123456789, 123456788, 123456888", + "Mobex_sms_gateway_from_numbers_list_desc": "Llista de números separats per comes per utilitzar en l'enviament de missatges nous, per exemple 123456789, 123456788, 123456888", "Mobex_sms_gateway_password": "Contrasenya", - "Mobex_sms_gateway_restful_address": "Adreça Mobex SMS REST API", + "Mobex_sms_gateway_restful_address": "Adreça de l'API REST de SMS de Mobex", "Mobex_sms_gateway_restful_address_desc": "IP o Host del seu Mobex REST API. Per exemple, `http://192.168.1.1:8080` o `https://www.example.com:8080`", "Mobex_sms_gateway_username": "Nom d'usuari", "Mobile": "Mòbil", + "mobile-download-file": "Permetre la descàrrega de fitxers en dispositius mòbils", + "mobile-upload-file": "Permetre la càrrega de fitxers en dispositius mòbils", "Mobile_Push_Notifications_Default_Alert": "Alerta per defecte notificacions mòbil", "Monday": "dilluns", "Mongo_storageEngine": "Motor d'emmagatzematge Mongo", @@ -3003,7 +3026,8 @@ "Mute_Group_Mentions": "Silenci @all i @here mencions", "Mute_someone_in_room": "Silenciar algú a la sala", "Mute_user": "Silencia l'usuari", - "mute-user": "Silenciar usuari", + "Mute_microphone": "Silenciar micròfon", + "mute-user": "Usuari silenciat", "mute-user_description": "Permís per silenciar altres usuaris del mateix canal", "Muted": "Silenciat", "My Data": "Les meves dades", @@ -3086,7 +3110,7 @@ "No_previous_chat_found": "No s'ha trobat cap xat anterior", "No_results_found": "No s'han trobat resultats", "No_results_found_for": "No s'han trobat resultats per a:", - "No_snippet_messages": "Cap retall", + "No_snippet_messages": "Sense fragment", "No_starred_messages": "Cap missatge destacat.", "No_such_command": "Comanda `/__command__` no trobada.", "No_Threads": "No s'ha trobat cap fil", @@ -3102,7 +3126,7 @@ "Not_following": "No seguir", "Not_Following": "No seguir", "Not_found_or_not_allowed": "No trobat o no permès", - "Not_Imported_Messages_Title": "Els missatges següents no s’han importat correctament", + "Not_Imported_Messages_Title": "Els missatges següents no s'han importat correctament", "Not_in_channel": "No al canal", "Not_likely": "No es probable", "Not_started": "No iniciat", @@ -3113,9 +3137,9 @@ "Notification_Desktop_Default_For": "Mostra notificacions d'escriptori per", "Notification_Push_Default_For": "Notificacions mòbils push per", "Notification_RequireInteraction": "Requerir interacció per descartar la notificació d'escriptori", - "Notification_RequireInteraction_Description": "Funciona només amb versions de el navegador Chrome> 50. Utilitza el paràmetre requireInteraction per mostrar la notificació d'escriptori de forma indefinida fins que l'usuari interactuï amb ella.", + "Notification_RequireInteraction_Description": "Només funciona amb les versions del navegador Chrome> 50. Utilitza el paràmetre requireInteraction per mostrar la notificació d'escriptori de manera indefinida fins que l'usuari hi interactuï.", "Notifications": "Notificacions", - "Notifications_Max_Room_Members": "Nombre màxim de membres de la sala abans de desactivar totes les notificacions de missatges", + "Notifications_Max_Room_Members": "Nombre màxim de membres de la Room abans de desactivar totes les notificacions de missatges", "Notifications_Max_Room_Members_Description": "Nombre màxim de membres a la sala quan es desactiven les notificacions de tots els missatges. Els usuaris encara poden canviar la configuració de cada habitació per rebre totes les notificacions de forma individual. (0 per desactivar)", "Notifications_Muted_Description": "Si esculls silenciar-ho tot, no veuràs la sala destacada a la llista quan hi hagi nous missatges, excepte si són mencions. Silenciar les notificacions sobreescriurà les opcions de notificació.", "Notifications_Preferences": "Preferències de notificacions", @@ -3137,13 +3161,13 @@ "Number_of_federated_users": "Nombre d'usuaris federats", "Number_of_messages": "Nombre de missatges", "Number_of_most_recent_chats_estimate_wait_time": "Nombre d'xats recents per calcular el temps d'espera estimat", - "Number_of_most_recent_chats_estimate_wait_time_description": "Aquest número defineix el nombre d'últimes sales reservades que s'utilitzaran per calcular els temps d'espera de la cua.", + "Number_of_most_recent_chats_estimate_wait_time_description": "Aquest número defineix el nombre de les últimes sales ateses que es faran servir per calcular els temps d'espera de la cua.", "Number_of_users_autocomplete_suggestions": "Nombre de suggeriments d'emplenament dels usuaris", "OAuth Apps": "Aplicacions OAuth", "OAuth_Application": "Aplicació OAuth", "OAuth_Applications": "Aplicacions OAuth", "Objects": "Objectes", - "Off": "Desactiva", + "Off": "Desactivar", "Off_the_record_conversation": "Conversa fora de registre", "Off_the_record_conversation_is_not_available_for_your_browser_or_device": "La conversa sense registre no està disponible per al seu navegador o dispositiu", "Office_Hours": "Horari d'obertura", @@ -3160,7 +3184,7 @@ "Offline_message": "missatge fora de línia", "Offline_Message": "Missatge fora de línia", "Offline_Message_Use_DeepLink": "Utilitzeu el format d’URL d’enllaç profund", - "Offline_messages": "Missatges fora de línia", + "Offline_messages": "Missatges sense connexió", "Offline_success_message": "Missatge fora de línia correcte", "Offline_unavailable": "Fora de línia no disponible", "Ok": "D'acord", @@ -3170,6 +3194,8 @@ "Omnichannel": "LiveChat", "Omnichannel_Directory": "Directori de LiveChat", "Omnichannel_appearance": "Aparença de LiveChat", + "Omnichannel_calculate_dispatch_service_queue_statistics": "Calcular i enviar estadístiques de la cua d'espera Livechat", + "Omnichannel_calculate_dispatch_service_queue_statistics_Description": "Processar i enviar estadístiques de la cua despera, com la posició i el temps despera esperat. Si el * canal de xat en viu * no està en ús, es recomana desactivar aquesta configuració i evitar que el servidor realitzi processos innecessaris.", "Omnichannel_Contact_Center": "Centre de contacte LiveChat", "Omnichannel_contact_manager_routing": "Assignar nous converses a l'administrador de contactes", "Omnichannel_contact_manager_routing_Description": "Aquesta configuració assigna un xat a l'Administrador de contactes assignat, sempre que l'Administrador de contactes estigui en línia quan s'inicia el xat", @@ -3180,20 +3206,21 @@ "Omnichannel_External_Frame_URL": "URL de marc extern", "On": "Activa", "On_Hold_Chats": "En espera", + "On_Hold_conversations": "Converses en espera", "online": "en línia", "Online": "Connectat", "Only_authorized_users_can_write_new_messages": "Només els usuaris autoritzats poden escriure missatges nous", "Only_authorized_users_can_react_to_messages": "Només els usuaris autoritzats poden reaccionar als missatges", "Only_from_users": "Només elimini el contingut d'aquests usuaris (deixeu en blanc per eliminar el contingut de tots)", "Only_Members_Selected_Department_Can_View_Channel": "Només els membres de l'departament seleccionat poden veure els xats en aquest canal", - "Only_On_Desktop": "Mode ordinador d'escriptori (només envia amb Enter en ordinadors)", + "Only_On_Desktop": "Mode d'escriptori (només envia amb Enter a l'escriptori)", "Only_works_with_chrome_version_greater_50": "Funciona només amb versions de Google Chrome> 50", "Only_you_can_see_this_message": "Només tu pots veure aquest missatge", "Only_invited_users_can_acess_this_channel": "Només els usuaris convidats poden accedir a aquest Channel", "Oops_page_not_found": "Vaja, pàgina no trobada", "Oops!": "Ui!", "Open": "Obre", - "Open_channel_user_search": "`%s` - Obre canal / Cerca usuari", + "Open_channel_user_search": "`%s` - Obre Channell / Cerca usuari", "Open_conversations": "Converses obertes", "Open_Days": "Díes oberts", "Open_days_of_the_week": "Dies d'obertura", @@ -3214,10 +3241,10 @@ "Organization_Name": "Nom de l'Organització", "Organization_Type": "Tipus d'Organització", "Original": "Original", - "OS_Arch": "Arquitectura del sistema", + "OS_Arch": "Arquitectura del SO", "OS_Cpus": "Recompte de CPU", "OS_Freemem": "Memòria RAM lliure", - "OS_Loadavg": "Mitjanes de càrrega", + "OS_Loadavg": "Mitjana de Càrrega del SO", "OS_Platform": "Plataforma del SO", "OS_Release": "Versió del SO", "OS_Totalmem": "Memòria RAM total", @@ -3232,7 +3259,7 @@ "Outgoing_WebHook": "WebHook sortint", "Outgoing_WebHook_Description": "Extreu dades de Rocket.Chat en temps real.", "Output_format": "Format de sortida", - "Override_URL_to_which_files_are_uploaded_This_url_also_used_for_downloads_unless_a_CDN_is_given": "Sobreescriu l'adreça URL a la qual es pugen els arxius. Aquesta adreça també s'utilitza per a les descàrregues a menys que s'especifiqui un CDN", + "Override_URL_to_which_files_are_uploaded_This_url_also_used_for_downloads_unless_a_CDN_is_given": "Reemplaça la URL a la qual es carreguen els fitxers. Aquest URL també es fa servir per a baixades a no ser que es proporcioni un CDN", "Page_title": "Titol de la pàgina", "Page_URL": "Adreça URL de la pàgina", "Pages": "Pàgines", @@ -3276,18 +3303,18 @@ "PiwikAdditionalTrackers_Description": "Introduïu les URL addicionals de la pàgina web de Piwik i els SiteID en el següent format, si desitja rastrejar les mateixes dades en diferents llocs web: [ { \"trackerURL\" : \"https://my.piwik.domain2/\", \"siteId\" : 42 }, { \"trackerURL\" : \"https://my.piwik.domain3/\", \"siteId\" : 15 } ]", "PiwikAnalytics_cookieDomain": "Tots els subdominis", "PiwikAnalytics_cookieDomain_Description": "Segueix visitants per tots els subdominis", - "PiwikAnalytics_domains": "Amaga enllaços de sortida", + "PiwikAnalytics_domains": "Oculta els enllaços sortints", "PiwikAnalytics_domains_Description": "A l'informe \"Enllaços externs\", oculti els clics a URL d'àlies conegudes. Inseriu un domini per línia i no utilitzeu separadors.", "PiwikAnalytics_prependDomain": "Prefixa domini", - "PiwikAnalytics_prependDomain_Description": "Prefixa el domini del lloc al títol de la pàgina", - "PiwikAnalytics_siteId_Description": "L'ID de lloc a utilitzar per a la identificació d'aquest lloc. Exemple: 17", + "PiwikAnalytics_prependDomain_Description": "Anteposi el domini del lloc al títol de la pàgina quan faci el seguiment", + "PiwikAnalytics_siteId_Description": "La Identificació del lloc a utilitzar per a la identificació daquest lloc. Exemple: 17", "PiwikAnalytics_url_Description": "L'adreça URL on es troba el Piwik, assegureu-vos d'incloure la barra del final. Exemple: //piwik.rocket.chat/", "Placeholder_for_email_or_username_login_field": "Marcador de posició per al camp d'inici de sessió de correu electrònic o nom d'usuari", "Placeholder_for_password_login_confirm_field": "Confirma marcador de posició per al camp d'inici de sessió amb contrasenya", "Placeholder_for_password_login_field": "Marcador de posició per al camp d'inici de sessió amb contrasenya", "Please_add_a_comment": "Si us plau, afegeix un comentari", "Please_add_a_comment_to_close_the_room": "Si us plau, afegeix un comentari per tancar la sala", - "Please_answer_survey": "Si us plau, permeti'ns un moment per una breu enquesta sobre aquest xat", + "Please_answer_survey": "Si us plau preneu-vos un moment per respondre una breu enquesta sobre aquest xat", "Please_enter_usernames": "Sisplau, entra noms d'usuari...", "please_enter_valid_domain": "Si us plau introduiu un domini vàlid", "Please_enter_value_for_url": "Si us plau introdueix l'adreça URL del teu avatar.", @@ -3326,7 +3353,7 @@ "Presence": "Presència", "Preview": "Vista prèvia", "preview-c-room": "Previsualitzar canal públic", - "preview-c-room_description": "Permís per veure els continguts d'un canal públic abans d'unir-s'hi", + "preview-c-room_description": "PPermís per veure els continguts d´un canal públic abans d´unir-se", "Previous_month": "Mes anterior", "Previous_week": "Setmana anterior", "Priorities": "Prioritats", @@ -3375,10 +3402,10 @@ "Push_Notifications": "Notificaciones push", "Push_apn_cert": "Certificat APN", "Push_apn_dev_cert": "Certificat APN de desenvolupador (Dev)", - "Push_apn_dev_key": "Clau APN Dev (Key)", + "Push_apn_dev_key": "Clau de desenvolupament d'APN", "Push_apn_dev_passphrase": "Contrasenya APN Dev (Passphrase)", "Push_apn_key": "Clau APN (Key)", - "Push_apn_passphrase": "Contrasenya APN (Passphrase)", + "Push_apn_passphrase": "Frase de contrasenya d'APN", "Push_enable": "Activa", "Push_enable_gateway": "Activa porta d'enllaç", "Push_enable_gateway_Description": " Són els Ha d'acceptar per registrar el seu servidor (Assistent de configuració> Informació de l'organització> Registrar servidor) i els nostres termes de privacitat (Assistent de configuració> Informació del núvol> Acord de termes de privacitat de el servei en el núvol) per habilitar aquesta configuració i usar la nostra porta d'entrada. Fins i tot si aquesta configuració està activada, no funcionarà si el servidor no està registrat.", @@ -3387,7 +3414,7 @@ "Push_gcm_api_key": "Clau API GCM (Key)", "Push_gcm_project_number": "GCM Project Number", "Push_production": "Producció", - "Push_request_content_from_server": "Obtenir el contingut complet d'el missatge de servidor a l'rebre'l", + "Push_request_content_from_server": "Obtenir el contingut complet del missatge del servidor en rebre'l", "Push_Setting_Requires_Restart_Alert": "Canviar aquest valor requereix reiniciar Rocket.Chat.", "Push_show_message": "Mostra el missatge a la notificació", "Push_show_username_room": "Mostra Channel / grup / nom d'usuari en la notificació", @@ -3396,6 +3423,7 @@ "Query_description": "Condicions addicionals per a determinar a quins usuaris s'enviarà el missatge de correu-e. Els usuaris des-subscrits s'eliminen automàticament de la consulta. Ha de ser un objecte JSON vàlid. Exemple: \"{\"createdAt\":{\"$gt\":{\"$date\": \"2015-01-01T00:00:00.000Z\"}}}\"", "Query_is_not_valid_JSON": "La consulta no és JSON vàlid", "Queue": "Cua", + "Queue_delay_timeout": "Temps despera despera de processament de cua", "Queue_Time": "Temps de cua", "Queue_management": "Gestió de cues", "quote": "cita", @@ -3411,22 +3439,22 @@ "Read_by": "Llegit per", "Read_only": "Només lectura", "Read_only_changed_successfully": "Només lectura canviat correctament", - "Read_only_channel": "Canal de només lectura", + "Read_only_channel": "Channel Només lectura", "Read_only_group": "Grup de només lectura", "Real_Estate": "Béns arrels", "Real_Time_Monitoring": "Monitorització en temps real", "RealName_Change_Disabled": "El seu administrador de Rocket.Chat ha desactivat el canvi de noms", - "Reason_To_Join": "Raó per unir-se", + "Reason_To_Join": "Motiu per unir-se", "Receive_alerts": "Rebre alertes", "Receive_Group_Mentions": "Rebi mencions @all i @here", "Recent_Import_History": "Històric recent d'importació", "Record": "Gravar", "recording": "grabació", - "Redirect_URI": "URI de redireccionament (Redirect URI)", + "Redirect_URI": "URI de Redireccionament ", "Refresh": "Actualització", "Refresh_keys": "Refresca les claus", "Refresh_oauth_services": "Refresca serveis OAuth", - "Refresh_your_page_after_install_to_enable_screen_sharing": "Per poder compartir la pantalla refresqui la pàgina després de la instal·lació ", + "Refresh_your_page_after_install_to_enable_screen_sharing": "Actualitzar la pantalla després de la instal·lació per permetre compartir la pantalla", "Regenerate_codes": "Regenera codis", "Regexp_validation": "Validació per expressió regular", "Register": "Crea un compte nou", @@ -3456,7 +3484,7 @@ "Remove_Admin": "Treu admin", "Remove_as_leader": "Treure de líder", "Remove_as_moderator": "Treu de moderador", - "Remove_as_owner": "Treu de propietari", + "Remove_as_owner": "Eliminar com a propietari", "Remove_Channel_Links": "Eliminar enllaços de canals", "Remove_custom_oauth": "Esborra OAuth personalitzat", "Remove_from_room": "Treu-lo de la sala", @@ -3482,7 +3510,7 @@ "Reply_via_Email": "Respondre per correu electrònic", "ReplyTo": "Respondre a", "Report": "Reportar", - "Report_Abuse": "Informar d'un abús", + "Report_Abuse": "Reportar abús", "Report_exclamation_mark": "Informa!", "Report_sent": "Informe enviat", "Report_this_message_question_mark": "Informar d'aquest missatge?", @@ -3493,7 +3521,7 @@ "Request_more_seats_out_of_seats": "No podeu afegir membres perquè aquest espai de treball no té lloc, demani més llocs.", "Request_more_seats_sales_team": "Una vegada que enviï la seva sol·licitud, el nostre equip de vendes l'analitzarà i es comunicarà amb vostè en els pròxims dies.", "Request_more_seats_title": "Sol·licitar més llocs", - "Request_comment_when_closing_conversation": "Sol·licitar un comentari a l'tancar la conversa", + "Request_comment_when_closing_conversation": "Sol·licitar un comentari al tancar la conversa", "Request_comment_when_closing_conversation_description": "Si està activat, l'agent haurà de fer un comentari abans que es tanqui la conversa.", "Request_tag_before_closing_chat": "Sol·licitar etiqueta(es) abans de tancar la conversa", "Requested_At": "Sol·licitat en", @@ -3515,7 +3543,7 @@ "Responding": "Responent", "Response_description_post": "Els cossos buits o els cossos amb una propietat de text buida simplement seran ignorats. Les respostes que no siguin 200 es tornaran a intentar una quantitat raonable de vegades. Es publicarà una resposta amb l'àlies i l'avatar especificats anteriorment. Pot anul·lar aquesta informació com en l'exemple anterior.", "Response_description_pre": "Si el controlador desitja tornar a publicar una resposta al canal, el següent JSON s'ha de retornar com el cos de la resposta:", - "Restart": "Reinicia (restart)", + "Restart": "Reiniciar", "Restart_the_server": "Reinicia el servidor", "Retail": "Venda al detall", "Retention_setting_changed_successfully": "La configuració de la política de retenció s'ha canviat correctament", @@ -3542,15 +3570,15 @@ "RetentionPolicy_Precision": "Precisió del temporitzador", "RetentionPolicy_Precision_Description": "Amb quina freqüència ha de funcionar el comptador de poda. Establir això en un valor més precís fa que els canals amb temporitzadors de retenció ràpids funcionin millor, però podria costar potència de processament addicional en comunitats grans.", "RetentionPolicy_RoomWarning": "Els missatges anteriors a __time__ s'eliminen automàticament aquí", - "RetentionPolicy_RoomWarning_FilesOnly": "Els arxius anteriors a __time__ s'eliminaran automàticament aquí (els missatges romanen intactes)", + "RetentionPolicy_RoomWarning_FilesOnly": "Els fitxers anteriors a __time__ s'eliminen automàticament aquí (els missatges romanen intactes)", "RetentionPolicy_RoomWarning_Unpinned": "Els missatges no fixats anteriors a __time__ s'eliminaran automàticament aquí", - "RetentionPolicy_RoomWarning_UnpinnedFilesOnly": "Els arxius no fixats anteriors a __time__ s'eliminaran automàticament aquí (els missatges romanen intactes)", + "RetentionPolicy_RoomWarning_UnpinnedFilesOnly": "Els fitxers no fixats anteriors a __time__ s'eliminen automàticament aquí (els missatges romanen intactes)", "RetentionPolicyRoom_Enabled": "Esborrar missatges antics automàticament", "RetentionPolicyRoom_ExcludePinned": "Exclou els missatges fixats", "RetentionPolicyRoom_FilesOnly": "Esborri només arxius, mantingui missatges", "RetentionPolicyRoom_MaxAge": "Antiguitat màxima de l'missatge en dies (per defecte: __max__)", "RetentionPolicyRoom_OverrideGlobal": "Anul·lar la política de retenció global", - "RetentionPolicyRoom_ReadTheDocs": "Compte! Ajustar aquestes configuracions sense la major cura pot destruir tot l'historial de missatges. Llegiu la documentació abans d'activar la funció aquí .", + "RetentionPolicyRoom_ReadTheDocs": "Compte! Ajustar aquestes configuracions sense tenir més cura pot destruir tot l'historial de missatges. Llegiu la documentació abans d'activar la funció aquí .", "Retry": "processar de nou", "Retry_Count": "Comptador de reintents", "Return_to_home": "Tornar a inici", @@ -3580,9 +3608,9 @@ "Room_archivation_state_true": "Arxivada", "Room_archived": "Sala arxivada", "room_changed_announcement": "L'anunci de la sala s'ha canviat a: __room_announcement__ per __user_by__", - "room_changed_avatar": "Avatar de la sala canviat per __user_by__ ", + "room_changed_avatar": "Avatar de Room canviat per __user_by__ ", "room_changed_description": "Descripció de la sala canviada a: __room_description__ per __user_by__.", - "room_changed_privacy": "Tipus de sala canviat a: __room_type__ per __user_by__.", + "room_changed_privacy": "Tipus de Room canviat a: __room_type__ per __user_by__.", "room_changed_topic": "Tema de la sala canviat a: __room_topic__ per __user_by__.", "Room_default_change_to_private_will_be_default_no_more": "Aquest és un canal per defecte i canviar-lo a grup privat farà que deixi de ser-ho. Voleu continuar?", "Room_description_changed_successfully": "Descripció de la sala canviada correctament", @@ -3591,7 +3619,7 @@ "Room_has_been_archived": "La sala s'ha arxivat", "Room_has_been_deleted": "La sala s'ha eliminat", "Room_has_been_removed": "Room ha estat eliminat", - "Room_has_been_unarchived": "La sala s'ha desarxivat", + "Room_has_been_unarchived": "La Room s'ha desarxivat", "Room_Info": "Informació de la Room", "room_is_blocked": "Aquesta sala està bloquejada", "room_account_deactivated": "Aquest compte està desactivat", @@ -3606,7 +3634,7 @@ "Room_tokenpass_config_changed_successfully": "La configuració tokenpass de la sala canviada amb èxit", "Room_topic_changed_successfully": "El tema de la sala s'ha canviat correctament", "Room_type_changed_successfully": "El tipus de sala s'ha canviat correctament", - "Room_type_of_default_rooms_cant_be_changed": "Aquesta és una sala per defecte i no es pot canviar el tipus, si us plau consulta-ho amb l'administrador.", + "Room_type_of_default_rooms_cant_be_changed": "Aquesta és una sala per defecte i el tipus no es pot canviar, consulteu amb el vostre administrador.", "Room_unarchived": "La sala s'ha desarxivat", "Room_updated_successfully": "Rooms'ha actualitzat correctament.", "Room_uploaded_file_list": "Llista d'arxius pujats", @@ -3637,7 +3665,7 @@ "SAML_Custom_Authn_Context_Comparison": "Comparació del context d’Authn", "SAML_Custom_Authn_Context_description": "Deixi això buit per ometre el context d'autenticació de la sol·licitud. \n\nPer afegir múltiples contextos d'autenticació, afegiu els addicionals directament a la configuració __AuthnContext Template__.", "SAML_Custom_Cert": "Certificat personalitzat", - "SAML_Custom_Debug": "Activa la depuració", + "SAML_Custom_Debug": "Activar la depuració", "SAML_Custom_EMail_Field": "Nom del camp de correu electrònic", "SAML_Custom_Entry_point": "Punt d'entrada (Entry Point) personalitzat", "SAML_Custom_Generate_Username": "Generar nom d'usuari", @@ -3656,7 +3684,7 @@ "SAML_Custom_Public_Cert": "Contingut del certificat públic", "SAML_Custom_signature_validation_all": "Valida totes les signatures", "SAML_Custom_signature_validation_assertion": "Validar la signatura d'asserció", - "SAML_Custom_signature_validation_either": "Validar qualsevol signatura", + "SAML_Custom_signature_validation_either": "Validar qualsevol de les firmes", "SAML_Custom_signature_validation_response": "Validar la signatura de resposta", "SAML_Custom_signature_validation_type": "Tipus de validació de signatura", "SAML_Custom_signature_validation_type_description": "Aquesta configuració s'ignorarà si no es proporciona un certificat personalitzat.", @@ -3681,9 +3709,9 @@ "SAML_Metadata_Template_Description": "Les següents variables estan disponibles:\n - ** \\ _ \\ _ sloLocation \\ _ \\ _ **: L'URL de tancament de sessió simple de Rocket.Chat\n- ** \\ _ \\ _ issuer \\ _ \\ _ **: The value of the __Custom Issuer__ setting.\n- ** \\ _ \\ _ identifierFormat \\ _ \\ _ **: el valor de l'opció __Identifier Format __\n- ** \\ _ \\ _ certificateTag \\ _ \\ _ **: Si un certificat privat és configurat, això inclourà el __Metadata Certificate Template__, en cas contrari serà ignorado.\n- ** \\ _ \\ _ callbackUrl \\ _ \\ _ **: L'URL de crida de Rocket.Chat", "SAML_MetadataCertificate_Template": "Plantilla de certificat de metadades", "SAML_NameIdPolicy_Template": "Plantilla de política NameID", - "SAML_NameIdPolicy_Template_Description": "Podeu utilitzar qualsevol variable de la plantilla de sol·licitud d'autorització aquí.", + "SAML_NameIdPolicy_Template_Description": "Podeu utilitzar qualsevol variable de la Plantilla de sol·licitud d'autorització aquí.", "SAML_Role_Attribute_Name": "Nom de l'atribut de rol", - "SAML_Role_Attribute_Name_Description": "Si aquest atribut es troba a la resposta SAML, els seus valors s'utilitzaran com a noms de rol per als usuaris nous.", + "SAML_Role_Attribute_Name_Description": "Si aquest atribut es troba a la resposta SAML, els vostres valors es faran servir com a noms de rols per a nous usuaris.", "SAML_Role_Attribute_Sync": "Sincronitza els rols de l'usuari", "SAML_Role_Attribute_Sync_Description": "Sincronitzeu els rols d'usuari de SAML a l'iniciar sessió (sobreescriu els rols d'usuari local).", "SAML_Section_1_User_Interface": "Interfície d'usuari", @@ -3807,9 +3835,9 @@ "Set_as_favorite": "Establir com a favorit", "Set_as_leader": "Posar com a líder", "Set_as_moderator": "Fes-lo moderador", - "Set_as_owner": "Fes-lo propietari", + "Set_as_owner": "Establir com a propietari", "Set_random_password_and_send_by_email": "Establir una contrasenya aleatòria i envieu-la per correu electrònic", - "set-leader": "Líder d'establiment", + "set-leader": "Establir com a líder", "set-leader_description": "Permís per establir a altres usuaris com a líders d'un canal", "set-moderator": "Assignar moderador", "set-moderator_description": "Permís per assignar altres usuaris com a moderadors d'un canal", @@ -3821,7 +3849,7 @@ "set-readonly_description": "Permís per fer un canal de només lectura", "Settings": "Configuració", "Settings_updated": "S'ha actualitzat la configuració", - "Setup_Wizard": "Ajudant de configuració", + "Setup_Wizard": "Assistent de configuració", "Setup_Wizard_Info": "El guiarem per configurar el seu primer usuari administrador, configurar la seva organització i registrar el seu servidor per rebre notificacions push gratuïtes i més.", "Share_Location_Title": "Compartir localització?", "Share_screen": "Compartir pantalla", @@ -3835,13 +3863,13 @@ "Should_be_a_URL_of_an_image": "Ha de ser l'adreça URL d'una imatge.", "Should_exists_a_user_with_this_username": "Aquest usuari ja deu existir.", "Show_agent_email": "Mostra el correu electrònic de l'agent", - "Show_agent_info": "Mostra la informació de l'agent", + "Show_agent_info": "Mostra informació de l'agent", "Show_all": "Veure tots", "Show_Avatars": "Mostra Avatars", "Show_counter": "Mostra comptador", "Show_email_field": "Mostra el camp de correu electrònic", "Show_Message_In_Main_Thread": "Mostra els missatges del fil al fil principal", - "Show_more": "Veure més", + "Show_more": "Mostrar més", "Show_name_field": "Mostra el camp del nom", "show_offline_users": "Mostra els usuaris desconnectats", "Show_on_offline_page": "Mostra a la pàgina fora de línia", @@ -3868,9 +3896,9 @@ "Skip": "Salta", "Slack_Users": "CSV d'usuaris de Slack", "SlackBridge_APIToken": "API Tokens", - "SlackBridge_APIToken_Description": "Podeu configurar diversos servidors slack afegint un símbol API per línia.", + "SlackBridge_APIToken_Description": "Podeu configurar diversos servidors slack afegint un token d'API per línia.", "Slackbridge_channel_links_removed_successfully": "Els enllaços de canal de Slackbridge s'han eliminat correctament.", - "SlackBridge_error": "Hi ha hagut un error a SlackBridge mentre importava els missatges a %s: %s", + "SlackBridge_error": "SlackBridge va rebre un error en importar els seus missatges a %s:%s", "SlackBridge_finish": "SlackBridge ha finalitat la importació a %s. Si us plau, refresqueu per veure tots els missatges.", "SlackBridge_Out_All": "SlackBridge Out de tot", "SlackBridge_Out_All_Description": "Envia els missatges de tots els canals que existeixen a Slack i en els quals el bot s'ha unit", @@ -3892,16 +3920,16 @@ "Smarsh_Email": "Correu-e Smarsh", "Smarsh_Email_Description": "Adreça de correu-e Smarsh on enviar l'arxiu .eml.", "Smarsh_Enabled": "Smarsh actiu", - "Smarsh_Enabled_Description": "Activa o no el connector Smarsh eml (requereix el camp 'De' ple a Correu-e -> SMTP).", + "Smarsh_Enabled_Description": "Si el connector eml de Smarsh està habilitat o no (cal completar 'Des de correu electrònic' a Correu electrònic -> SMTP).", "Smarsh_Interval": "Interval Smarsh", - "Smarsh_Interval_Description": "Temps a esperar abans d'enviar els xats (requereix el camp 'De' ple a Correu-e -> SMTP).", - "Smarsh_MissingEmail_Email": "Adreça de correu-e desconeguda", - "Smarsh_MissingEmail_Email_Description": "Adreça de correu-e a mostrar per a un usuari quan no té cap adreça establerta. Normalment passa en els comptes de bots.", + "Smarsh_Interval_Description": "La quantitat de temps d'espera abans d'enviar els xats (cal completar 'Des de correu electrònic' a Correu electrònic -> SMTP).", + "Smarsh_MissingEmail_Email": "Falta correu electrònic", + "Smarsh_MissingEmail_Email_Description": "El correu electrònic que es mostra per a un compte d'usuari quan falta la vostra adreça de correu electrònic, generalment passa amb els comptes de bot.", "Smarsh_Timezone": "Zona horària Smarsh", "Smileys_and_People": "Emoticones i persones", "SMS": "SMS", "SMS_Default_Omnichannel_Department": "Departament de LiveChat (per defecte)", - "SMS_Default_Omnichannel_Department_Description": "Si s'estableix, tots els nous xats entrants iniciats per aquesta integració es redirigiran a aquest departament.", + "SMS_Default_Omnichannel_Department_Description": "Si s'estableix, tots els nous xats entrants iniciats per aquesta integració s'encaminaran a aquest departament. \nAquesta configuració es pot sobreescriure passant el paràmetre de consulta del departament a la sol·licitud.\nEx. https: // / api / v1 / livechat / sms-entrante / twilio? department = .\nNota: si utilitzeu el nom del departament, aleshores hauria de ser URL segur.", "SMS_Enabled": "Activa SMS", "SMTP": "SMTP", "SMTP_Host": "Host SMTP", @@ -3926,7 +3954,7 @@ "Star": "Iniciar", "Star_Message": "Destacar un missatge", "Starred_Messages": "Missatges destacats", - "Start": "Inicia", + "Start": "Iniciar", "Start_audio_call": "Inicia trucada", "Start_Chat": "Inicia el xat", "Start_of_conversation": "Inici de la conversa", @@ -3937,7 +3965,7 @@ "start-discussion": "Iniciar discussió", "start-discussion_description": "Permís per iniciar una discussió", "start-discussion-other-user": "Inicia la discussió (Un altre usuari)", - "start-discussion-other-user_description": "Permís per iniciar una discussió, que dóna permís a l'usuari per crear una discussió a partir d'un missatge enviat també per un altre usuari", + "start-discussion-other-user_description": "Permís per iniciar una discussió, que us dóna permís a l'usuari per crear una discussió a partir d'un missatge enviat per un altre usuari també.", "Started": "Començat", "Started_a_video_call": "Inicia una videoconferència", "Started_At": "Va començar a les", @@ -3967,7 +3995,7 @@ "Stats_Total_Livechat_Rooms": "Total de Rooms LiveChat", "Stats_Total_Messages": "Total de missatges", "Stats_Total_Messages_Channel": "Total de missatges a canals", - "Stats_Total_Messages_Direct": "Total de missatges a missatges directes", + "Stats_Total_Messages_Direct": "Total de missatges en missatges directes", "Stats_Total_Messages_Livechat": "Total de missatges a LiveChat", "Stats_Total_Messages_PrivateGroup": "Total de missatges a grups privats", "Stats_Total_Outgoing_Integrations": "Integracions sortints totals", @@ -3978,7 +4006,7 @@ "Stats_Total_Users": "Total d'usuaris", "Status": "Estat", "StatusMessage": "Missatge d'estat", - "StatusMessage_Change_Disabled": "El seu administrador de Rocket.Chat ha desactivat el canvi de missatges d'estat", + "StatusMessage_Change_Disabled": "El vostre administrador de Rocket.Chat ha desactivat el canvi de missatges d'estat", "StatusMessage_Changed_Successfully": "El missatge d'estat va canviar correctament.", "StatusMessage_Placeholder": "Què estàs fent en aquest moment?", "StatusMessage_Too_Long": "El missatge d'estat ha de tenir menys de 120 caràcters.", @@ -3986,7 +4014,7 @@ "Stop_call": "Aturar trucada", "Stop_Recording": "Atura gravació", "Store_Last_Message": "Desar l'últim missatge", - "Store_Last_Message_Sent_per_Room": "Desar l'últim missatge enviat a cada sala.", + "Store_Last_Message_Sent_per_Room": "Emmagatzemar el darrer missatge enviat a cada sala.", "Stream_Cast": "Stream Cast", "Stream_Cast_Address": "Adreça Stream Cast", "Stream_Cast_Address_Description": "IP o host del Stream Cast del teu Rocket.Chat central. Exemple: `192.168.1.1:3000` o `localhost:4000`.", @@ -3998,7 +4026,7 @@ "Success_message": "Missatge correcte", "Successfully_downloaded_file_from_external_URL_should_start_preparing_soon": "L'arxiu descarregat correctament des d'una URL externa hauria de començar a preparar-aviat", "Suggestion_from_recent_messages": "Suggeriment de missatges recents", - "Sunday": "diumenge", + "Sunday": "Diumenge", "Support": "Suport", "Survey": "Enquesta", "Survey_instructions": "Valoreu cada pregunta d'acord al nivell de satisfacció, sent 1 completament insatisfet i 5 completament satisfet.", @@ -4008,17 +4036,17 @@ "Sync_in_progress": "Sincronització en progrés", "Sync_Interval": "Interval de sincronització", "Sync_success": "Sincronització correcta", - "Sync_Users": "Sincronitza usuaris", + "Sync_Users": "Sincronitzar usuaris", "System_messages": "Missatges del sistema", "Tag": "Etiqueta", "Tags": "Etiquetes", - "Tag_removed": "Etiqueta suprimida", + "Tag_removed": "Etiqueta eliminada", "Tag_already_exists": "La etiqueta ya existe", "Take_it": "Agafa'l!", "Taken_at": "Pres en", "Target user not allowed to receive messages": "L'usuari objectiu no té permís per rebre missatges", "TargetRoom": "Sala de destí", - "TargetRoom_Description": "Sala que rebrà els missatges resultants de les execucions d'aquest esdeveniment. Només es permet una sala de destí i aquesta ha d'existir.", + "TargetRoom_Description": "La sala on s'enviaran els missatges que són el resultat de l'activació d'aquest esdeveniment. Només es permet una sala de destinació i hi ha d'haver.", "Team_Add_existing_channels": "Afegeix Channels existents", "Team_Add_existing": "Afegeix existents", "Team_Auto-join": "Unir-se automàticament", @@ -4088,8 +4116,8 @@ "Test_LDAP_Search": "Provar de cerca LDAP", "Texts": "Textos", "Thank_you_exclamation_mark": "Gràcies!", - "Thank_you_for_your_feedback": "Gràcies per la seva col·laboració", - "The_application_name_is_required": "Es requereix el nom de l'aplicació", + "Thank_you_for_your_feedback": "Gràcies pels seus comentaris", + "The_application_name_is_required": "El nom de laplicació és obligatori.", "The_channel_name_is_required": "Es requereix el nom del canal", "The_emails_are_being_sent": "Els missatges de correu-e s'estan enviant.", "The_empty_room__roomName__will_be_removed_automatically": "La sala buida __roomName__ s'eliminarà automàticament.", @@ -4107,7 +4135,7 @@ "The_user_s_will_be_removed_from_role_s": "L'usuari% s serà eliminat de el rol% s", "The_user_will_be_removed_from_s": "L'usuari s'eliminarà de %s", "The_user_wont_be_able_to_type_in_s": "L'usuari no podrà escriure a %s", - "Theme": "Aparença", + "Theme": "Tema", "theme-color-attention-color": "Color d'atenció", "theme-color-component-color": "Color de component", "theme-color-content-background-color": "Color del fons del contingut", @@ -4154,7 +4182,7 @@ "theme-color-status-busy": "Color de l'estat ocupat", "theme-color-status-offline": "Color de l'estat desconnectat", "theme-color-status-online": "Color de l'estat connectat", - "theme-color-success-color": "Color de 'Correcte'", + "theme-color-success-color": "Color d'èxit", "theme-color-tertiary-font-color": "Color terciari del text", "theme-color-transparent-dark": "Transparent fosc", "theme-color-transparent-darker": "Transparent més fosc", @@ -4192,7 +4220,7 @@ "thread": "fil", "Thread_message": "Comentat al missatge de *__username__'s* missatge: _ __msg__ _", "Threads": "Fils", - "Thursday": "dijous", + "Thursday": "Dijous", "Time_in_minutes": "Temps en minuts", "Time_in_seconds": "Temps en segons", "Timeout": "Temps d'espera", @@ -4208,11 +4236,11 @@ "to_see_more_details_on_how_to_integrate": "per a veure més detalls sobre com fer la integració.", "To_users": "Per als usuaris", "Today": "Avui", - "Toggle_original_translated": "Canvia original/traducció", + "Toggle_original_translated": "Alternar original / traduït", "toggle-room-e2e-encryption": "Alternar xifrat Room E2E", "toggle-room-e2e-encryption_description": "Permís per alternar la sala de xifrat e2e", "Token": "Token", - "Token_Access": "Accés de token", + "Token_Access": "Token d'accés", "Token_Controlled_Access": "Accés controlat per tokens", "Token_required": "El token és obligatori", "Tokenpass_Channel_Label": "Canal Tokenpass", @@ -4228,7 +4256,7 @@ "Total": "Total", "Total_abandoned_chats": "Total de xats abandonats", "Total_conversations": "Total devconverses", - "Total_Discussions": "Total dicussions", + "Total_Discussions": "Discussions totals", "Total_messages": "Total de missatges", "Total_Threads": "Totals de Fils", "Total_visitors": "Total de visitants", @@ -4240,7 +4268,7 @@ "totp-required": "Es requereix TOTP", "Transcript": "Transcripció", "Transcript_Enabled": "Pregunti a l'visitant si els agradaria una transcripció després de xat tancat", - "Transcript_message": "Missatge per mostrar a l'preguntar sobre la transcripció", + "Transcript_message": "Missatge per mostrar en preguntar sobre la transcripció", "Transcript_of_your_livechat_conversation": "Transcripció de la seva conversa de LiveChat.", "Transcript_Request": "Sol·licitud de transcripció", "transfer-livechat-guest": "Transferir convidats de Livechat", @@ -4258,16 +4286,16 @@ "Troubleshoot_Disable_Data_Exporter_Processor": "Desactiva el processador de l'exportador de dades", "Troubleshoot_Disable_Data_Exporter_Processor_Alert": "Aquesta configuració deté el processament de totes les sol·licituds d'exportació dels usuaris, de manera que no rebran l'enllaç per descarregar les seves dades.", "Troubleshoot_Disable_Instance_Broadcast": "Desactiva la retransmissió d'instàncies", - "Troubleshoot_Disable_Instance_Broadcast_Alert": "Aquesta configuració evita que les instàncies de Rocket.Chat enviïn esdeveniments a les altres instàncies, 'pot causar problemes de sincronització i mal comportament!", + "Troubleshoot_Disable_Instance_Broadcast_Alert": "Aquesta configuració evita que les instàncies de Rocket.Chat enviïn esdeveniments a les altres instàncies, pot causar problemes de sincronització i mal comportament!", "Troubleshoot_Disable_Livechat_Activity_Monitor": "Desactiva el monitor d'activitats de Livechat", "Troubleshoot_Disable_Livechat_Activity_Monitor_Alert": "Aquesta configuració deté el processament de les sessions de visita de l'LiveChat causant que les estadístiques deixin de funcionar!", "Troubleshoot_Disable_Notifications": "Desactiva notificacions", "Troubleshoot_Disable_Notifications_Alert": "Aquesta configuració desactiva per complet el sistema de notificacions; 'Els sons, les notificacions d'escriptori, les notificacions mòbils i els correus electrònics s'aturaran!", "Troubleshoot_Disable_Presence_Broadcast": "Desactiva la transmissió de presència", - "Troubleshoot_Disable_Presence_Broadcast_Alert": "Aquesta configuració evita que totes les instàncies enviïn els canvis d'estat dels usuaris als seus clients, mantenint a tots els usuaris amb el seu estat de presència des de la primera càrrega!", + "Troubleshoot_Disable_Presence_Broadcast_Alert": "Aquesta configuració evita que totes les instàncies enviïn els canvis d'estat dels usuaris als clients, mantenint tots els usuaris amb el seu estat de presència des de la primera càrrega!", "Troubleshoot_Disable_Sessions_Monitor": "Desactiva el monitor de sessions", "Troubleshoot_Disable_Sessions_Monitor_Alert": "Aquesta configuració deté el processament de les sessions de visita de l'LiveChat causant que les estadístiques deixin de funcionar!", - "Troubleshoot_Disable_Statistics_Generator": "Desactiva el generador d'estadístiques", + "Troubleshoot_Disable_Statistics_Generator": "Desactivar el generador d'estadístiques", "Troubleshoot_Disable_Statistics_Generator_Alert": "Aquest ajust deté el processament de totes les estadístiques fent que la pàgina d'informació quedi desactualitzada fins que algú faci clic al botó d'actualització i pot causar que falti altra informació en el sistema!", "Troubleshoot_Disable_Workspace_Sync": "Desactiva la sincronització de l'espai de treball", "Troubleshoot_Disable_Workspace_Sync_Alert": "¡Este ajuste detiene la sincronización de este servidor con la nube de Rocket.Chat y puede causar problemas con el mercado y las licencias de las empresas!", @@ -4275,8 +4303,10 @@ "Tuesday": "dimarts", "Turn_OFF": "Apagar", "Turn_ON": "ACTIVA", + "Turn_on_video": "Activar el vídeo", + "Turn_off_video": "Desactivar el vídeo", "Two Factor Authentication": "Autenticació de dos factors", - "Two-factor_authentication": "Autenticació de dos factors mitjançant TOTP", + "Two-factor_authentication": "Autenticació de dos factors a través de TOTP", "Two-factor_authentication_disabled": "Autenticació de dos factors desactivada", "Two-factor_authentication_email": "Autenticació de dos factors via correu electrònic", "Two-factor_authentication_email_is_currently_disabled": "L'autenticació en 2 passos via correu electrònic està inhabilitada", @@ -4287,21 +4317,21 @@ "typing": "escrivint", "Types": "Tipus", "Types_and_Distribution": "Tipus i distribució", - "Type_your_email": "Escrigui el seu correu electrònic", - "Type_your_job_title": "Escriu el títol del lloc de treball", + "Type_your_email": "Escriviu el vostre correu electrònic", + "Type_your_job_title": "Escriviu el vostre títol de treball", "Type_your_message": "Introduïu el missatge", "Type_your_name": "Escriu el teu nom", - "Type_your_new_password": "Escriu la nova contrasenya", + "Type_your_new_password": "Escriviu la nova contrasenya", "Type_your_password": "Escriviu la vostra contrasenya", "Type_your_username": "Escriviu el vostre nom d'usuari", "UI_Allow_room_names_with_special_chars": "Permetre caràcters especials en noms de sales", - "UI_Click_Direct_Message": "Clica per crear un missatge directe", + "UI_Click_Direct_Message": "Feu clic per crear un missatge directe", "UI_Click_Direct_Message_Description": "Evita obrir la pestanya del perfil, vés directe a la conversa", "UI_DisplayRoles": "Mostra rols", "UI_Group_Channels_By_Type": "Agrupar canals per tipus", - "UI_Merge_Channels_Groups": "Uneix grups privats amb canals", + "UI_Merge_Channels_Groups": "Uneix grups privats amb Channels", "UI_Show_top_navbar_embedded_layout": "Mostra la barra de navegació superior al disseny incrustat", - "UI_Unread_Counter_Style": "Estil de comptador de no-llegits", + "UI_Unread_Counter_Style": "Estil de comptador no llegit", "UI_Use_Name_Avatar": "Utilitzeu les inicials del nom complet per generar un avatar predeterminat", "UI_Use_Real_Name": "Utilitza el nom real", "unable-to-get-file": "No es pot obtenir l'arxiu", @@ -4309,7 +4339,7 @@ "unarchive-room": "Desarxivar sala", "unarchive-room_description": "Permís per desarxivar canals", "Unavailable": "No disponible", - "Unblock_User": "Desbloqueja usuari", + "Unblock_User": "Desbloquejar usuari", "Uncheck_All": "Desmarcar tot", "Uncollapse": "Desplegar", "Undefined": "No definit", @@ -4327,15 +4357,16 @@ "Unpin": "Treure els fixats", "Unpin_Message": "Desfixa el missatge", "unpinning-not-allowed": "No permet treure els fixats", - "Unread": "No llegits", - "Unread_Count": "Comptador de no llegits", - "Unread_Count_DM": "Comptador de no-llegits per als missatges directes", + "Unread": "No llegit", + "Unread_Count": "Recompte de no llegits", + "Unread_Count_DM": "Recompte de missatges no llegits per a missatges directes", "Unread_Messages": "Missatges no llegits", "Unread_on_top": "No s'ha llegit a la part superior", "Unread_Rooms": "Sales no llegides", "Unread_Rooms_Mode": "Mode de sales no llegides", "Unread_Tray_Icon_Alert": "Icona d'alerta de no llegits a la safata", "Unstar_Message": "Esborra el destacat", + "Unmute_microphone": "Activar so del micròfon", "Update": "Actualització", "Update_EnableChecker": "Habilitar el Update Checker", "Update_EnableChecker_Description": "Comprova automàticament si hi ha noves actualitzacions / missatges importants dels desenvolupadors de Rocket.Chat i rep notificacions quan estan disponibles. La notificació apareix una vegada per nova versió com un banner en què es pot fer clic i com un missatge de el bot Rocket.Cat, tots dos visibles només per als administradors.", @@ -4343,7 +4374,7 @@ "Update_LatestAvailableVersion": "Actualitza la darrera versió disponible", "Update_to_version": "Actualitzar a __version__", "Update_your_RocketChat": "Actualitza el teu Rocket.Chat", - "Updated_at": "Actualitzat el", + "Updated_at": "Actualitzat a", "Upload": "Pujar", "Uploads": "Càrregues", "Upload_app": "Pujar l'Aplicació", @@ -4370,11 +4401,11 @@ "Use_Room_configuration": "Sobreescriu la configuració del servidor i utilitza la configuració de sala", "Use_Server_configuration": "Utilitzeu la configuració del servidor", "Use_service_avatar": "Utilitza l'avatar de %s", - "Use_this_response": "Utilitzeu aquesta resposta", + "Use_this_response": "Fes servir aquesta resposta", "Use_response": "Utilitzeu la resposta", "Use_this_username": "Utilitza aquest nom d'usuari", "Use_uploaded_avatar": "Utilitza l'avatar pujat", - "Use_url_for_avatar": "Utilitzeu l'URL per a l'avatar", + "Use_url_for_avatar": "Usar URL per a avatar", "Use_User_Preferences_or_Global_Settings": "Usa les preferències d'usuari o la configuració global", "User": "Usuari", "User Search": "Cerca d'usuaris", @@ -4383,7 +4414,7 @@ "User__username__is_now_a_moderator_of__room_name_": "L'usuari __username__ ara és moderador de la sala __room_name__", "User__username__is_now_a_owner_of__room_name_": "L'usuari __username__ ara és un propietari de __room_name__", "User__username__muted_in_room__roomName__": "Usuari __username__ silenciat a la sala __roomName__", - "User__username__removed_from__room_name__leaders": "L'usuari __username__ ja no és líder de __room_name__", + "User__username__removed_from__room_name__leaders": "L'usuari __username__ va ser remogut dels líders de __room_name__", "User__username__removed_from__room_name__moderators": "L'usuari __username__ ja no és moderador de la sala __room_name__", "User__username__removed_from__room_name__owners": "L'usuari __username__ ja no és propietari de __room_name__", "User__username__unmuted_in_room__roomName__": "Usuari __username__ sense silenciar a la sala __roomName__", @@ -4446,7 +4477,7 @@ "User_uploaded_a_file_to_you": "__username__ us ha enviat un fitxer", "User_uploaded_file": "Ha pujat un arxiu", "User_uploaded_image": "Ha pujat una imatge", - "user-generate-access-token": "Usuaris generen Access Tokens", + "user-generate-access-token": "Token d'accés generat per l'usuari", "user-generate-access-token_description": "Permís perquè els usuaris puguin generar access tokens", "UserData_EnableDownload": "Habilitar la descàrrega de dades d'usuari", "UserData_FileSystemPath": "Ruta del sistema (archivos exportados)", @@ -4479,9 +4510,9 @@ "Username_title": "Tria un nom d'usuari", "Username_wants_to_start_otr_Do_you_want_to_accept": "L'usuari __username__ vol iniciar una conversa OTR. L'acceptes?", "Users": "Usuaris", - "Users must use Two Factor Authentication": "Els usuaris han d’utilitzar l’autenticació de dos factors", + "Users must use Two Factor Authentication": "Els usuaris han de fer servir l'autenticació de dos factors", "Users_added": "Els usuaris s'han afegit", - "Users_and_rooms": "Usuaris i Room s", + "Users_and_rooms": "Usuaris i Rooms", "Users_by_time_of_day": "Usuaris per hora del dia", "Users_in_role": "Usuaris al rol", "Users_key_has_been_reset": "Es va restablir la clau de l'usuari", @@ -4496,6 +4527,7 @@ "UTF8_User_Names_Validation_Description": "RegExp que s'utilitzarà per validar noms d'usuari", "UTF8_Channel_Names_Validation": "Validació de noms de channel UTF8", "UTF8_Channel_Names_Validation_Description": "RegExp que s'utilitzarà per validar els noms dels canals", + "Videocall_enabled": "Vídeo trucada activa", "Validate_email_address": "Validar l'adreça de correu electrònic", "Validation": "Validació", "Value_messages": "__value__ messages", @@ -4517,12 +4549,14 @@ "Video_Conference": "Videoconferència", "Video_message": "Missatge de vídeo", "Videocall_declined": "Vídeo trucada rebutjada.", - "Videocall_enabled": "Vídeo trucada activa", + "Video_and_Audio_Call": "Trucada d'àudio i vídeo", "Videos": "Vídeos", "View_All": "Veure tots els membres", "View_channels": "Veure Channel s", + "view-omnichannel-contact-center": "Veure centre de contacte Livechat", + "view-omnichannel-contact-center_description": "Permís per veure i interactuar amb el centre de contacte Livechat", "View_Logs": "Veure registre log", - "View_mode": "Mode de visualització", + "View_mode": "Mode de vista", "View_original": "Veure original", "View_the_Logs_for": "Veure els registres de: \"__name__\"", "view-broadcast-member-list": "Veure llista de membres a la sala de transmissió", @@ -4539,9 +4573,9 @@ "view-history_description": "Permís per veure l'historial del canal", "view-join-code": "Veure el codi per unir-se", "view-join-code_description": "Permís per veure el codi per unir-se al canal", - "view-joined-room": "Veure sales on unit", + "view-joined-room": "Veure Room unida", "view-joined-room_description": "Permís per veure els canals on actualment s'està unit", - "view-l-room": "Veure sales de LiveChat", + "view-l-room": "Veure Rooms de LiveChat", "view-l-room_description": "Permís per veure els canals de LiveChat", "view-livechat-analytics": "Veure analítiques de LiveChat", "view-livechat-analytics_description": "Permís per veure anàlisi de livechat", @@ -4567,18 +4601,18 @@ "view-livechat-triggers_description": "Permís per veure els activadors de livechat", "view-livechat-webhooks": "Veure webhooks Livechat", "view-livechat-webhooks_description": "Permís per veure webhooks Livechat", - "view-livechat-unit": "Veure les unitats de livechat", + "view-livechat-unit": "Veure les unitats de LiveChat", "view-logs": "Veure registres", "view-logs_description": "Permís per veure els registres del servidor", - "view-other-user-channels": "Veure canals d'altres usuaris", + "view-other-user-channels": "Veure d'altres usuaris Channels ", "view-other-user-channels_description": "Permís per veure canals que pertanyen a altres usuaris", "view-outside-room": "Vista exterior de Room", "view-outside-room_description": "Permís per veure usuaris fora de la sala actual", "view-p-room": "Veure sala privada", "view-p-room_description": "Permís per veure canals privats", - "view-privileged-setting": "Veure opcions privilegiades", + "view-privileged-setting": "Veure configuració privilegiada", "view-privileged-setting_description": "Permís per a veure la configuració", - "view-room-administration": "Veure administració de sala", + "view-room-administration": "Veure administració de Room", "view-room-administration_description": "Permís per veure estadístiques de missatges públics, privats i directes. No inclou veure converses o arxius", "view-statistics": "Veure estadístiques", "view-statistics_description": "Permís per veure estadístiques de el sistema, com el nombre d'usuaris connectats, el nombre d'habitacions, la informació de sistema operatiu", @@ -4594,6 +4628,7 @@ "Visitor_message": "Missatges dels visitants", "Visitor_Name": "Nom del visitant", "Visitor_Name_Placeholder": "Si us plau, introduïu el nom de l'visitant ...", + "Visitor_does_not_exist": "El visitant no existeix!", "Visitor_Navigation": "Navegació del visitant", "Visitor_page_URL": "URL de la pàgina del visitant", "Visitor_time_on_site": "Temps de visita", @@ -4606,7 +4641,7 @@ "Warnings": "Avisos", "WAU_value": "WAU __value__", "We_appreciate_your_feedback": "Agraïm els seus comentaris", - "We_are_offline_Sorry_for_the_inconvenience": "Estem fora de línia. Disculpi les molèsties.", + "We_are_offline_Sorry_for_the_inconvenience": "Estem fora de línia. Disculpeu les molèsties.", "We_have_sent_password_email": "T'hem enviat un missatge de correu electrònic amb les instruccions per reinicialitzar la contrasenya. Si no reps el missatge en breu, si us plau mira al correu brossa i/o torna i reintenta-ho.", "We_have_sent_registration_email": "T'hem enviat un missatge de correu electrònic per confirmar el registre. Si no reps el missatge en breu, si us plau mira al correu brossa i/o torna i reintenta-ho.", "Webdav Integration": "Integració de Webdav", @@ -4619,8 +4654,9 @@ "webdav-account-saved": "Compte WebDAV guardada", "webdav-account-updated": "Compte WebDAV actualitzada", "Webhook_Details": "Detalls de WebHook", - "Webhook_URL": "Adreça URL WebHook", + "Webhook_URL": "URL del webhook", "Webhooks": "Webhooks", + "WebRTC_Call": "Trucada WebRTC", "WebRTC_direct_audio_call_from_%s": "Trucada d'àudio directa de %s", "WebRTC_direct_video_call_from_%s": "Videotrucada directa de %s", "WebRTC_Enable_Channel": "Activa per a canals públics", @@ -4631,6 +4667,8 @@ "WebRTC_monitor_call_from_%s": "Superviseu la trucada de %s", "WebRTC_Servers": "Servidors STUN/TURN", "WebRTC_Servers_Description": "Llista de servidors STUN i TURN separats per comes.
      Noms d'usuari, contrasenya i port són permesos en el format `username:password@stun:host:port` o bé `username:password@turn:host:port`.", + "WebRTC_call_ended_message": " La trucada va finalitzar a les __endTime__ - Va durar __callDuration__", + "WebRTC_call_declined_message": "Trucada rebutjada per contacte.", "Website": "lloc web", "Wednesday": "dimecres", "Weekly_Active_Users": "Usuaris actius setmanals", @@ -4653,7 +4691,7 @@ "Would_you_like_to_place_chat_on_hold": "Li agradaria posar aquest xat en espera?", "Yes": "Sí", "Yes_archive_it": "Sí, arxiva'l!", - "Yes_clear_all": "Sí, esborra!", + "Yes_clear_all": "Sí, esborrar-ho tot.", "Yes_deactivate_it": "Sí, desactiveu-lo.", "Yes_delete_it": "Sí, elimina!", "Yes_hide_it": "Sí, oculta!", @@ -4663,14 +4701,14 @@ "Yes_remove_user": "Sí, elimina l'usuari!", "Yes_unarchive_it": "Sí, desarxiva'l!", "yesterday": "ahir", - "Yesterday": "ahir", + "Yesterday": "Ahir", "You": "Vostè", "You_are_converting_team_to_channel": "Ets convertint aquest equip en un canal.", "you_are_in_preview_mode_of": "Estàs en mode vista prèvia del canal #__room_name__", "you_are_in_preview_mode_of_incoming_livechat": "Esteu en mode de previsualització d'aquest xat", "You_are_logged_in_as": "Sessió iniciada com", "You_are_not_authorized_to_view_this_page": "No està autoritzat a veure aquesta pàgina.", - "You_can_change_a_different_avatar_too": "Es pot ignorar l'avatar d'aquesta integració.", + "You_can_change_a_different_avatar_too": "Podeu anul·lar l'avatar utilitzat per publicar des d'aquesta integració.", "You_can_close_this_window_now": "Ja pots tancar aquesta finestra", "You_can_search_using_RegExp_eg": "Podeu fer cerques mitjançant Expressió regular . per exemple. / ^ text $ / i ", "You_can_use_an_emoji_as_avatar": "També es pot utilitzar un emoji com a avatar.", @@ -4679,7 +4717,7 @@ "You_followed_this_message": "Vas seguir aquest missatge.", "You_have_a_new_message": "Teniu un missatge nou", "You_have_been_muted": "Has estat silenciat i no podràs dir res en aquesta sala", - "You_have_n_codes_remaining": "Encara et queden __number__ codis.", + "You_have_n_codes_remaining": "Et queden __number__ codis.", "You_have_not_verified_your_email": "Encara no has verificat la teva adreça de correu electrònic.", "You_have_successfully_unsubscribed": "T'has donat de baixa correctament de la nostra llista de distribució de correu.", "You_have_to_set_an_API_token_first_in_order_to_use_the_integration": "Primer ha de configurar un símbol d'API per utilitzar la integració.", @@ -4688,35 +4726,35 @@ "You_need_install_an_extension_to_allow_screen_sharing": "Necessita instal·lar una extensió per poder compartir la pantalla", "You_need_to_change_your_password": "Cal canvïs la contrasenya", "You_need_to_type_in_your_password_in_order_to_do_this": "Cal que escriguis la contrasenya per fer això.", - "You_need_to_type_in_your_username_in_order_to_do_this": "Cal que escriguis el nom d'usuari per fer això.", + "You_need_to_type_in_your_username_in_order_to_do_this": "Necessiteu escriure el vostre nom d'usuari per fer-ho!", "You_need_to_verifiy_your_email_address_to_get_notications": "Cal tenir verificada l'adreça de correu electrònic per poder rebre notificacions", "You_need_to_write_something": "Cal escriure alguna cosa!", "You_reached_the_maximum_number_of_guest_users_allowed_by_your_license": "Heu assolit el nombre màxim d’usuaris convidats que permet la vostra llicència.", "You_should_inform_one_url_at_least": "Heu de definir almenys una URL.", - "You_should_name_it_to_easily_manage_your_integrations": "Caldria posar-li un nom per poder administrar fàcilment les integracions.", + "You_should_name_it_to_easily_manage_your_integrations": "Ho hauria de nomenar per administrar fàcilment les seves integracions.", "You_unfollowed_this_message": "Vas deixar de seguir aquest missatge.", - "You_will_be_asked_for_permissions": "Se li demanaran permisos", + "You_will_be_asked_for_permissions": "Se us demanaran permisos", "You_will_not_be_able_to_recover": "No podràs recuperar aquest missatge!", "You_will_not_be_able_to_recover_email_inbox": "No podrà recuperar aquesta safata d'entrada de correu electrònic", - "You_will_not_be_able_to_recover_file": "No podràs recuperar aquest arxiu!", - "You_wont_receive_email_notifications_because_you_have_not_verified_your_email": "No rebràs notificacions per correu electrònic, ja que no s'ha verificat l'adreça.", + "You_will_not_be_able_to_recover_file": "No podreu recuperar aquest fitxer!", + "You_wont_receive_email_notifications_because_you_have_not_verified_your_email": "No rebreu notificacions per correu electrònic perquè no heu verificat el vostre correu electrònic.", "Your_e2e_key_has_been_reset": "La vostra clau e2e ha estat restablerta.", "Your_email_address_has_changed": "La seva adreça de correu electrònic ha estat modificada.", - "Your_email_has_been_queued_for_sending": "El teu correu electrònic s'ha posat a la cua d'enviament", + "Your_email_has_been_queued_for_sending": "El vostre correu electrònic s'ha posat en cua per enviar-lo.", "Your_entry_has_been_deleted": "L'entrada s'ha eliminat.", "Your_file_has_been_deleted": "L'arxiu s'ha eliminat.", "Your_invite_link_will_expire_after__usesLeft__uses": "El seu enllaç d'invitació expirarà després d'__usesLeft__ usos.", "Your_invite_link_will_expire_on__date__": "El seu enllaç d'invitació expirarà el dia __date__.", "Your_invite_link_will_expire_on__date__or_after__usesLeft__uses": "El seu enllaç d'invitació expirarà en __date__ o després de __usesLeft__ usos.", - "Your_invite_link_will_never_expire": "L'enllaç d'invitació no caducarà mai.", + "Your_invite_link_will_never_expire": "El vostre enllaç d'invitació mai no caducarà.", "Your_mail_was_sent_to_s": "S'ha enviat el missatge a %s", - "your_message": "el teu missatge", - "your_message_optional": "el teu missatge (opcional)", + "your_message": "El seu missatge", + "your_message_optional": "el seu missatge (opcional)", "Your_new_email_is_email": "La vostra nova adreça electrònica és [email] .", "Your_password_is_wrong": "La contrasenya és incorrecta!", "Your_password_was_changed_by_an_admin": "Un administrador ha canviat la vostra contrasenya.", "Your_push_was_sent_to_s_devices": "La notificació push s'ha enviat a %s dispositius", - "Your_question": "La teva pregunta", + "Your_question": "La seva pregunta", "Your_server_link": "El vostre enllaç del servidor", "Your_temporary_password_is_password": "La vostra contrasenya temporal és [contrasenya] .", "Your_TOTP_has_been_reset": "El vostre TOTP de dos factors s'ha restablert.", diff --git a/packages/rocketchat-i18n/i18n/cs.i18n.json b/packages/rocketchat-i18n/i18n/cs.i18n.json index c20fd884c89a3..b8e39f7a2af1f 100644 --- a/packages/rocketchat-i18n/i18n/cs.i18n.json +++ b/packages/rocketchat-i18n/i18n/cs.i18n.json @@ -372,6 +372,7 @@ "API_Upper_Count_Limit": "Maximální počet", "API_Upper_Count_Limit_Description": "Kolik nejvíce záznamů smí REST API vrátit (pokud není limitovaná)", "API_Use_REST_For_DDP_Calls": "Použít místo websocketů REST", + "API_User_Limit": "Maximální počet uživatelů přidaných do místnosti", "API_Wordpress_URL": "WordPress URL", "api-bypass-rate-limit": "Obejít rychlostní limit pro REST API", "Apiai_Key": "Api.ai Klíč", @@ -2842,7 +2843,7 @@ "Placeholder_for_password_login_field": "Zástupný text pro pole hesla v přihlášení", "Please_add_a_comment": "Prosím, přidejte komentář", "Please_add_a_comment_to_close_the_room": "Pro uzavření místnosti prosím přidejte komentář", - "Please_answer_survey": "Věnujte prosím chvilku času ohodnocení chatu.", + "Please_answer_survey": "Věnujte nám prosím chvilku svého času na ohodnocení chatu.", "Please_enter_usernames": "Zadejte uživatelská jména...", "please_enter_valid_domain": "Prosím zadejte platnou doménu", "Please_enter_value_for_url": "Prosím, zadejte URL Vašeho avataru.", @@ -3333,6 +3334,7 @@ "Show_Setup_Wizard": "Zobrazit průvodce nastavením", "Show_the_keyboard_shortcut_list": "Zobrazit klávesové zkratky", "Showing_archived_results": "

      Zobrazeno %s archivovaných výsledků

      ", + "Showing_online_users": null, "Showing_results": "

      Zobrazeno %s výsledků

      ", "Sidebar": "Postranní panel", "Sign_in_to_start_talking": "Pro konverzaci se přihlašte", @@ -3480,7 +3482,7 @@ "Sync_success": "Synchronizace úspěšná", "Sync_Users": "Synchronizace uživatelů", "System_messages": "Systémové zprávy", - "Tag": "Štítek", + "Tag": "Tag", "Tag_removed": "Štítek odstraněn", "Take_it": "Převzít", "Target user not allowed to receive messages": "Cílový uživatel nemá povoleno přijímat zprávy", @@ -3863,6 +3865,7 @@ "Uses": "Použití", "Uses_left": "Zbývající počet použití", "UTF8_Names_Slugify": "Url podoba UTF8 jmen", + "Videocall_enabled": "Videohovor povolen", "Validate_email_address": "Validovat email", "Validation": "Validace", "Value_messages": "__value__ zpráv", @@ -3884,7 +3887,6 @@ "Video_Conference": "Video konference", "Video_message": "Video zpráva", "Videocall_declined": "Videohovor odmítnut", - "Videocall_enabled": "Videohovor povolen", "Videos": "Videa", "View_All": "Zobrazit všechny členy", "View_Logs": "Zobrazit logy", @@ -4058,4 +4060,4 @@ "Your_server_link": "Odkaz na Váš server", "Your_temporary_password_is_password": "Vaše dočasné heslo je [password].", "Your_workspace_is_ready": "Váš prostředí je připraveno k použití 🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/cy.i18n.json b/packages/rocketchat-i18n/i18n/cy.i18n.json index 328c1a06530d4..15af907b0976c 100644 --- a/packages/rocketchat-i18n/i18n/cy.i18n.json +++ b/packages/rocketchat-i18n/i18n/cy.i18n.json @@ -2678,6 +2678,7 @@ "Users_added": "Mae'r defnyddwyr wedi cael eu hychwanegu", "Users_in_role": "Defnyddwyr mewn rôl", "UTF8_Names_Slugify": "Enwau UTF8 Slugify", + "Videocall_enabled": "Galwad Fideo Galluogi", "Validate_email_address": "Dilyswch yr E-bost", "Verification": "Gwirio", "Verification_Description": "Fe allech chi ddefnyddio'r llefydd canlynol:
      • [Verification_Url] ar gyfer yr URL dilysu.
      • [name], [fname], [lname] ar gyfer enw llawn, enw cyntaf neu enw olaf y defnyddiwr, yn y drefn honno.
      • [e-bost] ar gyfer e-bost y defnyddiwr.
      • [Site_Name] a [Site_URL] ar gyfer yr Enw Cais a'r URL yn y drefn honno.
      ", @@ -2692,7 +2693,6 @@ "Video_Conference": "Cynhadledd Fideo", "Video_message": "Neges fideo", "Videocall_declined": "Gwrthodwyd Galwad Fideo.", - "Videocall_enabled": "Galwad Fideo Galluogi", "View_All": "Gweld yr holl Aelodau", "View_Logs": "Gweld Logiau", "View_mode": "Modd Gweld", diff --git a/packages/rocketchat-i18n/i18n/da.i18n.json b/packages/rocketchat-i18n/i18n/da.i18n.json index 95768715eb56b..92937eda8db31 100644 --- a/packages/rocketchat-i18n/i18n/da.i18n.json +++ b/packages/rocketchat-i18n/i18n/da.i18n.json @@ -2857,7 +2857,7 @@ "Placeholder_for_password_login_field": "Stedholder for Password Login Field", "Please_add_a_comment": "Tilføj venligst en kommentar", "Please_add_a_comment_to_close_the_room": "Vær venlig at tilføje en kommentar for at lukke værelset", - "Please_answer_survey": "Tag et øjeblik til at besvare en hurtig undersøgelse om denne chat", + "Please_answer_survey": "Brug et øjeblik på at besvare et spørgeskema om denne chat", "Please_enter_usernames": "Indtast venligst brugernavne ...", "please_enter_valid_domain": "Angiv et gyldigt domænenavn", "Please_enter_value_for_url": "Indtast venligst en værdi for din avatarens url.", @@ -2869,7 +2869,7 @@ "Please_fill_a_username": "Venligst udfyld et brugernavn", "Please_fill_all_the_information": "Udfyld venligst alle oplysninger", "Please_fill_an_email": "Udlfyld med e-mail", - "Please_fill_name_and_email": "Venligst udfyld navn og email", + "Please_fill_name_and_email": "Udfyld venligst navn og e-mail", "Please_go_to_the_Administration_page_then_Livechat_Facebook": "Gå til Administrationssiden -> Omnikanal -> Facebook", "Please_select_an_user": "Vælg venligst en bruger", "Please_select_enabled_yes_or_no": "Vælg venligst en indstilling for Aktiveret", @@ -3363,7 +3363,7 @@ "Site_Url": "Webstedets webadresse", "Site_Url_Description": "Eksempel: https://chat.domain.com/", "Size": "Størrelse", - "Skip": "Springe", + "Skip": "Spring over", "Slack_Users": "Slack's Users CSV", "SlackBridge_APIToken": "API Tokens", "SlackBridge_APIToken_Description": "Du kan konfigurere flere slack-servere ved at tilføje en API-token pr. linje.", @@ -3492,8 +3492,8 @@ "Suggestion_from_recent_messages": "Forslag fra nylige meddelelser", "Sunday": "Søndag", "Support": "Support", - "Survey": "Undersøgelse", - "Survey_instructions": "Vurder hvert spørgsmål efter din tilfredshed, 1 hvilket betyder at du er helt utilfreds og 5 betyder, at du er helt tilfreds.", + "Survey": "Spørgeskema", + "Survey_instructions": "Vurder hvert spørgsmål efter din tilfredshed: 1 betyder, at du er helt utilfreds, og 5 betyder, at du er helt tilfreds.", "Symbols": "Symboler", "Sync": "Synkronisér", "Sync / Import": "Synkronisér / Importér", @@ -3886,6 +3886,7 @@ "Uses": "Brugere", "Uses_left": "Tilbageværende brugere", "UTF8_Names_Slugify": "UTF8 Navne Slugify", + "Videocall_enabled": "Videoopkald aktiveret", "Validate_email_address": "Valider e-mail-adresse", "Validation": "Validering", "Value_messages": "__value__ meddelelser", @@ -3907,7 +3908,6 @@ "Video_Conference": "Video konference", "Video_message": "Video besked", "Videocall_declined": "Videoopkald nægtet.", - "Videocall_enabled": "Videoopkald aktiveret", "Videos": "Videoer", "View_All": "Se alle medlemmer", "View_Logs": "Se logfiler", diff --git a/packages/rocketchat-i18n/i18n/de-AT.i18n.json b/packages/rocketchat-i18n/i18n/de-AT.i18n.json index 1c636fa1ec7ca..39085f874fec9 100644 --- a/packages/rocketchat-i18n/i18n/de-AT.i18n.json +++ b/packages/rocketchat-i18n/i18n/de-AT.i18n.json @@ -556,6 +556,7 @@ "Continuous_sound_notifications_for_new_livechat_room": "Kontinuierliche Soundbenachrichtigungen für den neuen Livechat-Raum", "Conversation": "Chat", "Conversation_closed": "Gespräch geschlossen: __comment__.", + "Conversation_finished": "Gespräch beendet", "Conversation_finished_message": "Konversation beendete Nachricht", "conversation_with_s": "die Konversation mit %s", "Convert_Ascii_Emojis": "ASCII zu Emoji konvertieren", @@ -1275,7 +1276,7 @@ "hours": "Stunden", "Hours": "Std", "How_friendly_was_the_chat_agent": "Wie freundlich war der Chat-Agent?", - "How_knowledgeable_was_the_chat_agent": "Wie sachkundig war der Chat-Agent?", + "How_knowledgeable_was_the_chat_agent": "Wie sachkundig war der Chat-Berater?", "How_long_to_wait_after_agent_goes_offline": "Wie lange wird gewartet, bis der Agent offline geht?", "How_responsive_was_the_chat_agent": "Wie reaktionsschnell war der Chat-Agent?", "How_satisfied_were_you_with_this_chat": "Wie zufrieden waren Sie mit diesem Chat?", @@ -2304,6 +2305,7 @@ "Show_Setup_Wizard": "Setup-Assistent anzeigen", "Show_the_keyboard_shortcut_list": "Zeigen Sie die Tastenkombination an", "Showing_archived_results": "

      Anzeigen von %s archivierten Ergebnissen

      ", + "Showing_online_users": null, "Showing_results": "

      %s Ergebnisse

      ", "Sidebar": "Seitenleiste", "Sidebar_list_mode": "Sidebar-Kanallistenmodus", @@ -2684,6 +2686,7 @@ "Users_added": "Die Benutzer wurden hinzugefügt", "Users_in_role": "Zugeordnete Nutzer", "UTF8_Names_Slugify": "UTF8-Namen-Slugify", + "Videocall_enabled": "Videoanruf aktiviert", "Validate_email_address": "E-mail Adresse bestätigen", "Verification": "Überprüfung", "Verification_Description": "Sie können die folgenden Platzhalter verwenden:
      • [Verification_Url] für die Bestätigungs-URL.
      • [Name], [Name], [Name] für den vollständigen Namen, den Vornamen bzw. den Nachnamen des Benutzers.
      • [E-Mail] für die E-Mail des Nutzers.
      • [Site_Name] und [Site_URL] für den Anwendungsnamen bzw. die URL.
      ", @@ -2698,7 +2701,6 @@ "Video_Conference": "Videokonferenz", "Video_message": "Videonachricht", "Videocall_declined": "Videoanruf abgelehnt.", - "Videocall_enabled": "Videoanruf aktiviert", "View_All": "Alle ansehen", "View_Logs": "Logs anzeigen", "View_mode": "Ansichts-Modus", @@ -2820,4 +2822,4 @@ "Your_push_was_sent_to_s_devices": "Die Push-Nachricht wurde an %s Geräte gesendet.", "Your_server_link": "Ihre Serververbindung", "Your_workspace_is_ready": "Ihr Arbeitsbereich ist einsatzbereit 🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/de.i18n.json b/packages/rocketchat-i18n/i18n/de.i18n.json index 764e5befd9f5b..1712ba826a809 100644 --- a/packages/rocketchat-i18n/i18n/de.i18n.json +++ b/packages/rocketchat-i18n/i18n/de.i18n.json @@ -1968,7 +1968,7 @@ "hours": "Stunden", "Hours": "Stunden", "How_friendly_was_the_chat_agent": "Wie freundlich war der Chat-Agent?", - "How_knowledgeable_was_the_chat_agent": "Wie sachkundig war der Chat-Agent?", + "How_knowledgeable_was_the_chat_agent": "Wie sachkundig war der Chat-Berater?", "How_long_to_wait_after_agent_goes_offline": "Wartedauer, bevor ein Agent in den Offline-Modus übergeht", "How_long_to_wait_to_consider_visitor_abandonment": "Wie lange warten, um die Abwesenheit von Besuchern aufzugeben?", "How_long_to_wait_to_consider_visitor_abandonment_in_seconds": "Wie lange warten, um die Abwesenheit von Besuchern aufzugeben?", @@ -3449,6 +3449,7 @@ "Show_Setup_Wizard": "Setup-Assistent anzeigen", "Show_the_keyboard_shortcut_list": "Zeige die Liste der Keyboard-Shortcuts", "Showing_archived_results": "

      Aneigen von %s archivierte Räume

      ", + "Showing_online_users": null, "Showing_results": "

      %s Ergebnisse

      ", "Sidebar": "Seitenleiste", "Sidebar_list_mode": "Seitenleiste Kanallisten-Modus", @@ -3973,6 +3974,7 @@ "Uses": "Verwendet", "Uses_left": "Verbleibende Verwendungen", "UTF8_Names_Slugify": "UTF8-Namen-Slugify", + "Videocall_enabled": "Videoanruf aktiviert", "Validate_email_address": "E-Mail-Adresse bestätigen", "Verification": "Überprüfung ", "Verification_Description": "Sie können die folgenden Platzhalter verwenden:
      • [Verification_Url] für die Verifikations-URL
      • [name], [fname], [lname] für den vollständigen Namen, Vornamen oder Nachnamen des Benutzers
      • [email] für die E-Mail-Adresse des Benutzers.
      • [Site_Name] und [Site_URL] für den Anwendungsnamen und die URL der Anwendung
      ", @@ -3991,7 +3993,6 @@ "Video_Conference": "Video-Konferenz", "Video_message": "Videonachricht", "Videocall_declined": "Videoanruf abgelehnt", - "Videocall_enabled": "Videoanruf aktiviert", "Videos": "Videos", "View_All": "Alle ansehen", "View_Logs": "Logs anzeigen", @@ -4163,4 +4164,4 @@ "Your_temporary_password_is_password": "Ihr temporäres Passwort lautet [password].", "Your_TOTP_has_been_reset": "Dein Zwei-Faktor-TOTP wurde zurückgesetzt.", "Your_workspace_is_ready": "Ihr Arbeitsbereich ist einsatzbereit 🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/el.i18n.json b/packages/rocketchat-i18n/i18n/el.i18n.json index 368800e9cea5f..6181f923c0b94 100644 --- a/packages/rocketchat-i18n/i18n/el.i18n.json +++ b/packages/rocketchat-i18n/i18n/el.i18n.json @@ -561,6 +561,7 @@ "Continuous_sound_notifications_for_new_livechat_room": "Συνεχείς ειδοποιήσεις ήχου για νέα αίθουσα livechat", "Conversation": "Συνομιλία", "Conversation_closed": "Η συνομιλία έκλεισε: __comment__.", + "Conversation_finished": "Η συνομιλία τελείωσε", "Conversation_finished_message": "Συνομιλία Ολοκληρώθηκε μήνυμα", "conversation_with_s": "τη συνομιλία με το %s", "Convert_Ascii_Emojis": "Μετατροπή ASCII σε Emoji", @@ -1693,6 +1694,7 @@ "Max_length_is": "Το μέγιστο μήκος είναι%s", "Media": "Μεσο ΜΑΖΙΚΗΣ ΕΝΗΜΕΡΩΣΗΣ", "Medium": "Μεσαίο", + "Members": "Μέλη", "Members_List": "Λίστα μελών", "mention-all": "Αναφέρετε όλα", "mention-all_description": "Άδεια χρήσης της παραπομπής @all", @@ -2308,6 +2310,7 @@ "Show_Setup_Wizard": "Εμφάνιση του οδηγού εγκατάστασης", "Show_the_keyboard_shortcut_list": "Εμφάνιση της λίστας συντομεύσεων πληκτρολογίου", "Showing_archived_results": "

      Εμφάνιση αρχειοθετημένα αποτελέσματα %s

      ", + "Showing_online_users": null, "Showing_results": "

      Εμφανιζονται %s αποτελεσματα

      ", "Sidebar": "Πλευρική γραμμή", "Sidebar_list_mode": "Λειτουργία λίστας καναλιών πλευρικής γραμμής", @@ -2414,7 +2417,7 @@ "Sunday": "Κυριακή", "Support": "Υποστήριξη", "Survey": "Έρευνα", - "Survey_instructions": "Βαθμολογήστε κάθε ερώτηση, σύμφωνα με την ικανοποίησή σας, 1 που σημαίνει ότι θα είναι εντελώς ανικανοποίητοι και 5 σημαίνει ότι είστε απόλυτα ικανοποιημένοι.", + "Survey_instructions": "Βαθμολογήστε κάθε ερώτηση, σύμφωνα με την ικανοποίησή σας, 1 που σημαίνει ότι μείνατε δυσαρεστημένοι και 5 σημαίνει ότι είστε απόλυτα ικανοποιημένοι.", "Symbols": "σύμβολα", "Sync_in_progress": "Ο συγχρονισμός βρίσκεται σε εξέλιξη", "Sync_success": "Συγχρονισμός επιτυχία", @@ -2435,7 +2438,7 @@ "Test_Connection": "δοκιμή σύνδεσης", "Test_Desktop_Notifications": "Δοκιμή Desktop Ειδοποιήσεις", "Thank_you_exclamation_mark": "Ευχαριστώ!", - "Thank_you_for_your_feedback": "Ευχαριστούμε για την ανταπόκρισή σας", + "Thank_you_for_your_feedback": "Ευχαριστούμε για τα σχόλιά σας", "The_application_name_is_required": "Το όνομα της εφαρμογής απαιτείται", "The_channel_name_is_required": "Το όνομα του καναλιού απαιτείται", "The_emails_are_being_sent": "Τα μηνύματα ηλεκτρονικού ταχυδρομείου που αποστέλλονται.", @@ -2687,6 +2690,7 @@ "Users_added": "Οι χρήστες έχουν προστεθεί", "Users_in_role": "Χρήστες σε ρόλο", "UTF8_Names_Slugify": "UTF8 Ονόματα Slugify", + "Videocall_enabled": "Κλήση βίντεο ενεργοποιημένη", "Validate_email_address": "Επαλήθευση διεύθυνσης ηλεκτρονικού ταχυδρομείου", "Verification": "Επαλήθευση", "Verification_Description": "Μπορείτε να χρησιμοποιήσετε τις ακόλουθες αντικαταστάσεις:
      • [Verification_Url] για τη διεύθυνση URL επαλήθευσης.
      • [name], [fname], [lname] για το πλήρες όνομα, το όνομα ή το επώνυμο του χρήστη, αντίστοιχα.
      • [email] για τη διεύθυνση ηλεκτρονικής αλληλογραφίας του χρήστη.
      • [Site_Name] και [Site_URL] για το Όνομα της Εφαρμογής και τη διεύθυνση URL αντίστοιχα.
      ", @@ -2701,7 +2705,6 @@ "Video_Conference": "Τηλεδιάσκεψη", "Video_message": "Βίντεο μηνύματος", "Videocall_declined": "Η κλήση βίντεο απορρίφθηκε.", - "Videocall_enabled": "Κλήση βίντεο ενεργοποιημένη", "View_All": "Εμφάνιση όλων", "View_Logs": "Δείτε τα αρχεία καταγραφών", "View_mode": "λειτουργία προβολής", @@ -2823,4 +2826,4 @@ "Your_push_was_sent_to_s_devices": "ώθηση σας στάλθηκε σε συσκευές %s", "Your_server_link": "Σύνδεσμος διακομιστή σας", "Your_workspace_is_ready": "Ο χώρος εργασίας σας είναι έτοιμος για χρήση 🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 7415bdc285c73..ce54f0dcc6cad 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -4528,6 +4528,7 @@ "UTF8_User_Names_Validation_Description": "RegExp that will be used to validate usernames", "UTF8_Channel_Names_Validation": "UTF8 Channel Names Validation", "UTF8_Channel_Names_Validation_Description": "RegExp that will be used to validate channel names", + "Videocall_enabled": "Video Call Enabled", "Validate_email_address": "Validate Email Address", "Validation": "Validation", "Value_messages": "__value__ messages", @@ -4759,4 +4760,4 @@ "Your_temporary_password_is_password": "Your temporary password is [password].", "Your_TOTP_has_been_reset": "Your Two Factor TOTP has been reset.", "Your_workspace_is_ready": "Your workspace is ready to use 🎉" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/eo.i18n.json b/packages/rocketchat-i18n/i18n/eo.i18n.json index 54fff096f16ae..866905fb6fc2a 100644 --- a/packages/rocketchat-i18n/i18n/eo.i18n.json +++ b/packages/rocketchat-i18n/i18n/eo.i18n.json @@ -555,6 +555,7 @@ "Continuous_sound_notifications_for_new_livechat_room": "Kontinua sono-sciigoj por nova viva ĉambro", "Conversation": "Konversacio", "Conversation_closed": "Konversacio fermita: __comment__.", + "Conversation_finished": "Konversacio finis", "Conversation_finished_message": "Konversacio Finita Mesaĝo", "conversation_with_s": "la konversacio kun %s", "Convert_Ascii_Emojis": "Konvertu ASCII al Emoji", @@ -2682,6 +2683,7 @@ "Users_added": "La uzantoj estis aldonitaj", "Users_in_role": "Uzantoj en rolo", "UTF8_Names_Slugify": "UTF8 Nomoj Slugify", + "Videocall_enabled": "Video Vokis Enabled", "Validate_email_address": "Validigi retpoŝtadreson", "Verification": "Verkcio", "Verification_Description": "Vi povas uzi la jenajn anstataŭilojn:
      • [Verification_Url] por la verificación URL.
      • [nomo], [fname], [lname] por la plena nomo, unua nomo aŭ familinomo de la uzanto, respektive.
      • [retpoŝto] por la retpoŝto de la uzanto.
      • [Site_Name] kaj [Site_URL] por la Aplika nomo kaj URL respektive.
      ", @@ -2696,7 +2698,6 @@ "Video_Conference": "Video Konferenco", "Video_message": "Video-mesaĝo", "Videocall_declined": "Video Vokis Malakceptita.", - "Videocall_enabled": "Video Vokis Enabled", "View_All": "Rigardi ĉiujn membrojn", "View_Logs": "Vidi Registrojn", "View_mode": "Rigardi Modo", diff --git a/packages/rocketchat-i18n/i18n/es.i18n.json b/packages/rocketchat-i18n/i18n/es.i18n.json index 4a02bc75ded72..9627c0b0d6e26 100644 --- a/packages/rocketchat-i18n/i18n/es.i18n.json +++ b/packages/rocketchat-i18n/i18n/es.i18n.json @@ -4,7 +4,7 @@ "__count__empty_rooms_will_be_removed_automatically": "__count__ salas vacías serán eliminadas automáticamente.", "__count__empty_rooms_will_be_removed_automatically__rooms__": "__count__ salas vacías serán eliminadas automáticamente.
      __rooms__.", "__username__is_no_longer__role__defined_by__user_by_": "__username__ ya no es __role__ (por __user_by__)", - "__username__was_set__role__by__user_by_": "__username__ fue establecido __role__ (por __user_by__)", + "__username__was_set__role__by__user_by_": "__username__ fue establecido __role__ por __user_by__", "This_room_encryption_has_been_enabled_by__username_": "El cifrado de esta sala ha sido habilitado por __username__", "This_room_encryption_has_been_disabled_by__username_": "El cifrado de esta sala ha sido deshabilitado por __username__", "@username": "@usuario", @@ -21,7 +21,7 @@ "A_new_owner_will_be_assigned_automatically_to_those__count__rooms__rooms__": "Un nuevo propietario será asignado automáticamente a estas __count__ salas.
      __rooms__.", "Accept": "Aceptar", "Accept_incoming_livechat_requests_even_if_there_are_no_online_agents": "Aceptar solicitudes entrantes de Omnichannel aunque no haya agentes en línea", - "Accept_new_livechats_when_agent_is_idle": "Aceptar nuevas solicitudes de Omnichannel cuando el agente esté inactivo", + "Accept_new_livechats_when_agent_is_idle": "Aceptar nuevas solicitudes de omnichannel cuando el agente esté inactivo", "Accept_with_no_online_agents": "Aceptar sin agentes en línea", "Access_not_authorized": "Acceso no autorizado", "Access_Token_URL": "URL de Token de Acceso", @@ -83,7 +83,7 @@ "Accounts_EmailVerification_Description": "Asegúrese de que tiene la configuración SMTP correcta para usar esta característica", "Accounts_Enrollment_Email": "Correo Electrónico de Inscripción ", "Accounts_Enrollment_Email_Default": "

      Bienvenido a [Site_Name]

      Ve a [Site_URL] y pruebe la mejor solución de chat de código abierto disponible en la actualidad!

      ", - "Accounts_Enrollment_Email_Description": "Puedes utilizar los siguientes marcadores:
      • [name], [fname], [lname] para el nombre completo, nombre o apellidos, respectivamente.
      • [email] para el correo electrónico del usuario.
      • [Site_Name] y [Site_URL] para el nombre del sitio web y la URL, respectivamente.
      ", + "Accounts_Enrollment_Email_Description": "Puedes utilizar los siguientes marcadores:
      • [name], [fname], [lname] para el nombre completo de usuario, nombre o apellidos, respectivamente.
      • [email] para el correo electrónico del usuario.
      • [Site_Name] y [Site_URL] para el nombre del sitio web y la URL, respectivamente.
      ", "Accounts_Enrollment_Email_Subject_Default": "Bienvenido a [Site_Name]", "Accounts_ForgetUserSessionOnWindowClose": "Olvidar la sesión de usuario al cerrar la ventana", "Accounts_Iframe_api_method": "Método API", @@ -114,6 +114,8 @@ "Accounts_OAuth_Custom_Merge_Users": "Fusionar usuarios", "Accounts_OAuth_Custom_Name_Field": "Campo de nombre", "Accounts_OAuth_Custom_Roles_Claim": "Nombre del campo roles/grupos", + "Accounts_OAuth_Custom_Roles_To_Sync": "Roles para sincronizar", + "Accounts_OAuth_Custom_Roles_To_Sync_Description": "Roles de OAuth para sincronizar en el inicio de sesión y la creación del usuario (separados por comas).", "Accounts_OAuth_Custom_Scope": "Ámbito (scope)", "Accounts_OAuth_Custom_Secret": "Secreto", "Accounts_OAuth_Custom_Show_Button_On_Login_Page": "Mostrar botón en la página de inicio de sesión", @@ -128,7 +130,7 @@ "Accounts_OAuth_Facebook_callback_url": "URL de retorno (callback) de Facebook", "Accounts_OAuth_Facebook_id": "App Id de Facebook", "Accounts_OAuth_Facebook_secret": "Secreto de Facebook", - "Accounts_OAuth_Github": "Habilitar OAuth", + "Accounts_OAuth_Github": "OAuth habilitado", "Accounts_OAuth_Github_callback_url": "URL de retorno (callback) de Github", "Accounts_OAuth_GitHub_Enterprise": "OAuth Habilitado", "Accounts_OAuth_GitHub_Enterprise_callback_url": "URL de retorno (callback) de GitHub Enterprise", @@ -217,17 +219,17 @@ "Accounts_RegistrationForm_SecretURL": "URL Secreto del Fomulario de Registro", "Accounts_RegistrationForm_SecretURL_Description": "Debe proporcionar una cadena de texto aleatorio que se añadirá a la URL de registro. Ejemplo: https://open.rocket.chat/register/[secret_hash]", "Accounts_RequireNameForSignUp": "Requerir un Nombre para el Registro", - "Accounts_RequirePasswordConfirmation": "Solicitar Confirmación de Contraseña", + "Accounts_RequirePasswordConfirmation": "Requiere Confirmación de Contraseña", "Accounts_RoomAvatarExternalProviderUrl": "Room URL del proveedor externo de Avatar", "Accounts_RoomAvatarExternalProviderUrl_Description": "Ejemplo: `https://acme.com/api/v1/{roomId}`", "Accounts_SearchFields": "Campos a Considerar en la Búsqueda", - "Accounts_Send_Email_When_Activating": "Enviar un correo electrónico al usuario cuando esté activado", + "Accounts_Send_Email_When_Activating": "Enviar correo electrónico al usuario cuando el usuario está activado", "Accounts_Send_Email_When_Deactivating": "Enviar un correo electrónico al usuario cuando esté desactivado", "Accounts_Set_Email_Of_External_Accounts_as_Verified": "Establecer el correo electrónico de las cuentas externas como verificado", "Accounts_Set_Email_Of_External_Accounts_as_Verified_Description": "Los correos electrónicos de cuentas creadas desde servicios externos, como LDAP, OAuth, etc., se marcarán como verificados automáticamente. ", "Accounts_SetDefaultAvatar": "Establecer avatar predeterminado", "Accounts_SetDefaultAvatar_Description": "Tratar de determinar el avatar predeterminado basado en la cuenta OAuth o Gravatar", - "Accounts_ShowFormLogin": "Mostrar el formulario de inicio de sesión predeterminado", + "Accounts_ShowFormLogin": "Mostrar formulario de inicio de sesión predeterminado", "Accounts_TwoFactorAuthentication_By_TOTP_Enabled": "Habilitar la autenticación de dos factores a través de TOTP", "Accounts_TwoFactorAuthentication_By_TOTP_Enabled_Description": "Los usuarios pueden configurar su autenticación de dos factores utilizando cualquier aplicación TOTP, como Google Authenticator o Authy.", "Accounts_TwoFactorAuthentication_By_Email_Auto_Opt_In": "Establecer segundo factor de autenticación vía email por defecto para nuevos usuarios ", @@ -270,7 +272,7 @@ "Add_User": "Añadir usuario", "Add_users": "Añadir usuarios", "Add_members": "Añadir miembros", - "add-livechat-department-agents": "Añadir agentes de Omnichannel a departamentos", + "add-livechat-department-agents": "Añadir agentes de Omnichannel a los departamentos", "add-livechat-department-agents_description": "Permiso para agregar agentes Omnichannel a los departamentos", "add-oauth-service": "Agregar Servicio Oauth", "add-oauth-service_description": "Permiso para agregar un nuevo servicio OAuth", @@ -287,14 +289,14 @@ "Adding_user": "Añadiendo usuario", "Additional_emails": "Correos electrónicos adicionales", "Additional_Feedback": "Retroalimentación adicional", - "additional_integrations_Bots": "Si está buscando cómo integrar su propio bot, entonces no busque más, nuestro adaptador Hubot. https://github.com/RocketChat/hubot-rocketchat", + "additional_integrations_Bots": "Si está buscando cómo integrar su propio bot, no busque más que nuestro adaptador Hubot. https://github.com/RocketChat/hubot-rocketchat", "additional_integrations_Zapier": "¿Está buscando integrar otro software y aplicaciones con Rocket.Chat pero no tiene tiempo para hacerlo manualmente? Entonces, sugerimos usar Zapier, que es totalmente compatible. Lea más sobre esto en nuestra documentación. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", "Admin_disabled_encryption": "Su administrador no habilitó encriptación E2E", "Admin_Info": "Información de administración", "Administration": "Administración", "Adult_images_are_not_allowed": "No se permiten imágenes para adultos", "Aerospace_and_Defense": "Aeroespacial y Defensa", - "After_OAuth2_authentication_users_will_be_redirected_to_this_URL": "Después de la autenticación OAuth2, los usuarios serán redirigidos a esta URL. Puedes añadir una URL por línea.", + "After_OAuth2_authentication_users_will_be_redirected_to_this_URL": "Después de la autenticación OAuth2, los usuarios serán redirigidos a una URL en esta lista. Puede agregar una URL por línea.", "Agent": "Agente", "Agent_added": "Agente agregado", "Agent_Info": "Información del agente", @@ -352,7 +354,7 @@ "API_Drupal_URL": "URL del servidor Drupal", "API_Drupal_URL_Description": "Ejemplo: https://domain.com (sin incluir la barra diagonal)", "API_Embed": "Incrustar (embed)", - "API_Embed_Description": "Ya sea que las previsualizaciones de enlace integrados estén activas o no cuando un usuario publica un enlace a un sitio web.", + "API_Embed_Description": "Si las vistas previas de enlaces incrustados están habilitadas o no cuando un usuario publica un enlace a un sitio web.", "API_Embed_UserAgent": "Incrusta agente de usuario en la solicitud", "API_EmbedCacheExpirationDays": "Incrusta días de vencimiento de caché", "API_EmbedDisabledFor": "Deshabilitar el insertar vinculos para los Usuarios", @@ -370,8 +372,10 @@ "API_Enable_Rate_Limiter_Dev": "Habilitar límite de frecuencia en desarrollo", "API_Enable_Rate_Limiter_Dev_Description": "¿Debería limitar la cantidad de llamadas a los puntos finales en el entorno de desarrollo?", "API_Enable_Rate_Limiter_Limit_Calls_Default": "Número predeterminado de llamadas al límite de velocidad", + "Rate_Limiter_Limit_RegisterUser": "Llamadas de números predeterminados al limitador de velocidad para registrar un usuario", + "Rate_Limiter_Limit_RegisterUser_Description": "Número de llamadas predeterminadas para usuarios que registran puntos finales (REST y API en tiempo real), permitidas dentro del rango de tiempo definido en la sección API Rate Limiter.", "API_Enable_Rate_Limiter_Limit_Calls_Default_Description": "Número de llamadas predeterminadas para cada punto final de la API REST, permitidas dentro del rango de tiempo definido a continuación", - "API_Enable_Rate_Limiter_Limit_Time_Default": "Límite de tiempo predeterminado para el límite de frecuencia (en ms)", + "API_Enable_Rate_Limiter_Limit_Time_Default": "Límite de tiempo predeterminado para el limitador de frecuencia (en ms)", "API_Enable_Rate_Limiter_Limit_Time_Default_Description": "Tiempo de espera predeterminado para limitar el número de llamadas en cada punto final de la API REST (en ms)", "API_Enable_Shields": "Activar escudos", "API_Enable_Shields_Description": "Activar los escudos disponibles en `/api/v1/shields. svg`", @@ -385,6 +389,7 @@ "API_Personal_Access_Tokens_Regenerate_Modal": "Si perdiste tu token, puedes volver a generarlo, pero recuerda que las aplicaciones que lo usen tendrán que actualizarlo", "API_Personal_Access_Tokens_Remove_Modal": "¿Estás seguro que quieres eliminar este token de acceso personal?", "API_Personal_Access_Tokens_To_REST_API": "Token de acceso personal a API REST", + "API_Rate_Limiter": "Limitador de tasa API", "API_Shield_Types": "Tipos de escudo", "API_Shield_Types_Description": "Tipos de escudos para activar como una lista separada por comas, elija entre `online`,` channel` o `*` para todos", "API_Shield_user_require_auth": "Requerir autenticación para escudos de los usuarios", @@ -394,7 +399,7 @@ "API_Upper_Count_Limit": "Cantidad máxima de registros", "API_Upper_Count_Limit_Description": "¿Cuál es el número máximo de registros que debe devolver la API REST (cuando no es ilimitado)?", "API_Use_REST_For_DDP_Calls": "Usar REST en lugar de WebSocket para las llamadas de Meteor", - "API_User_Limit": "Límite de usuarios para añadir todos los usuarios a un canal", + "API_User_Limit": "Límite de usuario para agregar todos los usuarios a un canal", "API_Wordpress_URL": "URL de Wordpress", "api-bypass-rate-limit": "Límite de velocidad de omisión para la API REST", "api-bypass-rate-limit_description": "Permiso para llamar a la API sin limitación de tarifas", @@ -452,16 +457,16 @@ "Apps_Interface_IPostRoomDeleted": "Evento que ocurre después de que una sala es eliminada", "Apps_Interface_IPostRoomUserJoined": "Evento que ocurre después de que un usuario se una a una sala (pública, privada)", "Apps_Interface_IPreMessageDeletePrevent": "Evento que ocurre después de que un mensaje es eliminado", - "Apps_Interface_IPreMessageSentExtend": "Evento que ocurre antes de que un mensaje es enviado", + "Apps_Interface_IPreMessageSentExtend": "Evento que ocurre antes de que se envíe un mensaje", "Apps_Interface_IPreMessageSentModify": "Evento que ocurre antes de que un mensaje es enviado", - "Apps_Interface_IPreMessageSentPrevent": "Evento que ocurre antes de que un mensaje es enviado", + "Apps_Interface_IPreMessageSentPrevent": "Evento que ocurre antes de que se envíe un mensaje", "Apps_Interface_IPreMessageUpdatedExtend": "Evento que ocurre antes de que un mensaje es actualizado", - "Apps_Interface_IPreMessageUpdatedModify": "Evento que ocurre antes de que un mensaje es actualizado", + "Apps_Interface_IPreMessageUpdatedModify": "Evento que ocurre antes de que se actualice un mensaje", "Apps_Interface_IPreMessageUpdatedPrevent": "Evento que ocurre antes de que un mensaje es actualizado", "Apps_Interface_IPreRoomCreateExtend": "Evento que ocurre antes de que una sala es creada", "Apps_Interface_IPreRoomCreateModify": "Evento que ocurre antes de que una sala es creada", - "Apps_Interface_IPreRoomCreatePrevent": "Evento que ocurre antes de que una sala es creada", - "Apps_Interface_IPreRoomDeletePrevent": "Evento que ocurre antes de que una sala es eliminada", + "Apps_Interface_IPreRoomCreatePrevent": "Evento que ocurre antes de que se cree una sala", + "Apps_Interface_IPreRoomDeletePrevent": "Evento que ocurre antes de que se elimine una sala", "Apps_Interface_IPreRoomUserJoined": "Evento que ocurre antes de que un usuario se una a una sala (pública, privada)", "Apps_License_Message_appId": "No se ha emitido la licencia para esta aplicación.", "Apps_License_Message_bundle": "Licencia emitida para un paquete que no contiene la aplicación", @@ -476,8 +481,8 @@ "Apps_Logs_TTL_30days": "30 díes", "Apps_Logs_TTL_Alert": "Dependiendo del tamaño de la colección de registros, cambiar esta configuración puede causar lentitud por algunos momentos", "Apps_Marketplace_Deactivate_App_Prompt": "¿Realmente quieres deshabilitar esta aplicación?", - "Apps_Marketplace_Login_Required_Description": "Comprar aplicaciones del Marketplace Rocket.Chat requiere registrar tu entorno de trabajo y logarse.", - "Apps_Marketplace_Login_Required_Title": "Login requerido para Marketplace", + "Apps_Marketplace_Login_Required_Description": "Comprar aplicaciones de Rocket.Chat Marketplace requiere registrar su espacio de trabajo e iniciar sesión.", + "Apps_Marketplace_Login_Required_Title": "Se requiere inicio de sesión en Marketplace", "Apps_Marketplace_Modify_App_Subscription": "Modificar suscripción", "Apps_Marketplace_pricingPlan_monthly": "__price__ / mes", "Apps_Marketplace_pricingPlan_monthly_perUser": "__price__ / mes por usuario", @@ -550,10 +555,10 @@ "assign-roles": "Asignar roles", "assign-roles_description": "Permiso para asignar roles a otros usuarios", "at": "en", - "At_least_one_added_token_is_required_by_the_user": "Al menos uno de los tokens añadidos es requerido por el usuario", + "At_least_one_added_token_is_required_by_the_user": "El usuario requiere al menos un token agregado", "AtlassianCrowd": "Atlassian Crowd", "Attachment_File_Uploaded": "Archivo subido", - "Attribute_handling": "Manejo de atributos", + "Attribute_handling": "Tratamiento de atributos", "Audio": "Audio", "Audio_message": "Mensaje de audio", "Audio_Notification_Value_Description": "Puede ser cualquier sonido personalizado o los predeterminados: bip, chelle, ding, droplet, highbell, seasons", @@ -585,7 +590,7 @@ "Automatic_Translation": "Traducción automática", "AutoTranslate": " Traducción Automática", "AutoTranslate_APIKey": "Clave API", - "AutoTranslate_Change_Language_Description": "Cambiar el idioma de la traducción automática no traduce los mensajes anteriores.", + "AutoTranslate_Change_Language_Description": "Cambiar el idioma de traducción automática no traduce los mensajes anteriores.", "AutoTranslate_DeepL": "DeepL", "AutoTranslate_Enabled": "Habilitar Traducción Automática", "AutoTranslate_Enabled_Description": "Habilitar la traducción automática, permite a las personas con la licencia de auto-traducción que todos los mensajes se traduzcan automáticamente a su idioma seleccionado. Se pueden aplicar cargos.", @@ -603,7 +608,7 @@ "Avg_chat_duration": "Promedio de duración del chat", "Avg_first_response_time": "Promedio del tiempo de la primera respuesta", "Avg_of_abandoned_chats": "Promedio de conversaciones abandonadas", - "Avg_of_available_service_time": "Promedio del tiempo de servicio disponible", + "Avg_of_available_service_time": "Promedio del tiempo disponible del servicio", "Avg_of_chat_duration_time": "Promedio del tiempo de duración del chat", "Avg_of_service_time": "Promedio del tiempo de servicio", "Avg_of_waiting_time": "Promedio del tiempo de espera", @@ -646,10 +651,10 @@ "Block_Multiple_Failed_Logins_By_Ip": "Bloquear los intentos fallidos de acceso por IP", "Block_Multiple_Failed_Logins_By_User": "Bloquear los intentos fallidos de acceso por nombre de usuario", "Block_Multiple_Failed_Logins_Enable_Collect_Login_data_Description": "Almacenar la IP y el nombre de usuario de los intentos de acceso en una colección en la base de datos", - "Block_Multiple_Failed_Logins_Enabled": "Habilitar la recopilación de datos de inicio de sesión", + "Block_Multiple_Failed_Logins_Enabled": "Habilitar recopilar datos de inicio de sesión", "Block_Multiple_Failed_Logins_Ip_Whitelist": "Lista blanca de IP", "Block_Multiple_Failed_Logins_Ip_Whitelist_Description": "Lista separada por comas de IP permitidas", - "Block_Multiple_Failed_Logins_Time_To_Unblock_By_Ip_In_Minutes": "Tiempo para desbloquear la IP (En minutos)", + "Block_Multiple_Failed_Logins_Time_To_Unblock_By_Ip_In_Minutes": "Tiempo para desbloquear IP (en minutos)", "Block_Multiple_Failed_Logins_Time_To_Unblock_By_User_In_Minutes": "Tiempo para desbloquear al usuario (en minutos)", "Block_Multiple_Failed_Logins_Notify_Failed": "Notificación de intentos fallidos de inicio de sesión", "Block_Multiple_Failed_Logins_Notify_Failed_Channel": "Channel para enviar las notificaciones", @@ -676,8 +681,8 @@ "Broadcasting_enabled": "Habilitar transmisión", "Broadcasting_media_server_url": "URL del servidor de medios de transmisión", "Browse_Files": "Búsqueda de archivos", - "Browser_does_not_support_audio_element": "Su navegador no soporta el elemento de audio.", - "Browser_does_not_support_video_element": "Su navegador no soporta el elemento de vídeo.", + "Browser_does_not_support_audio_element": "Su navegador no es compatible con el elemento de audio.", + "Browser_does_not_support_video_element": "Su navegador no es compatible con el elemento de vídeo.", "Bugsnag_api_key": "Clave API de Bugsnag", "Build_Environment": "Entorno de construcción", "bulk-register-user": "Creación masiva de usuarios", @@ -701,6 +706,9 @@ "By_author": "Por __author__", "cache_cleared": "Caché borrada", "Call": "Llamada", + "Call_declined": "¡Llamada rechazada!", + "Call_provider": "Proveedor de llamadas", + "Call_Already_Ended": "Llamada ya finalizada", "call-management": "Gestión de llamadas", "call-management_description": "Permiso para inciar una reunión", "Caller": "Emisor", @@ -712,7 +720,7 @@ "Canned_Response_Delete_Warning": "La eliminación de una respuesta automática no se puede deshacer.", "Canned_Response_Removed": "Respuesta predefinida eliminada", "Canned_Response_Sharing_Department_Description": "Cualquier persona del departamento seleccionado puede acceder a esta respuesta predefinida", - "Canned_Response_Sharing_Private_Description": "Solo usted y los administradores de LiveChat pueden acceder a esta respuesta predefinida", + "Canned_Response_Sharing_Private_Description": "Solo usted y los administradores de Omnichannel pueden acceder a esta respuesta predefinida", "Canned_Response_Sharing_Public_Description": "Cualquiera puede acceder a esta respuesta predefinida", "Canned_Responses": "Respuestas predefinidas", "Canned_Responses_Enable": "Habilitar respuesta predefinida", @@ -734,12 +742,12 @@ "CAS_login_url_Description": "La URL de inicio de sesión de su servicio de SSO externo, por ejemplo: https: //sso.example.undef/sso/login", "CAS_popup_height": "Altura de la ventana emergente de inicio de sesión", "CAS_popup_width": "Ancho de la ventana emergente de inicio de sesión", - "CAS_Sync_User_Data_Enabled": "Sincronizar siempre los datos de usuario", + "CAS_Sync_User_Data_Enabled": "Sincronizar siempre los datos del usuario", "CAS_Sync_User_Data_Enabled_Description": "Sincronizar siempre los datos de usuario de CAS externos en los atributos disponibles al iniciar sesión. Nota: los atributos siempre se sincronizan al crear la cuenta de todos modos.", "CAS_Sync_User_Data_FieldMap": "Mapa de atributos", "CAS_Sync_User_Data_FieldMap_Description": "Utilice esta entrada JSON para construir atributos internos (clave) a partir de atributos externos (valor). Los nombres de los atributos externos encerrados con '%' se interpolarán en cadenas de valores.
      Ejemplo, `{\"email\":\"%email%\", \"name\":\"%firstname%, %lastname%\"}`

      El mapa de atributos siempre está interpolado. En CAS 1.0 sólo está disponible el atributo \"nombre de usuario\". Los atributos internos disponibles son: nombre de usuario, nombre, correo electrónico, habitaciones; habitaciones es una lista separada por comas de habitaciones a las que unirse al crear un usuario, por ejemplo: {\"rooms\": \"%team%,%department%\"} se uniría a los usuarios de CAS en la creación a su equipo y canal de departamento.", "CAS_trust_username": "Confiar en el nombre de usuario de CAS", - "CAS_trust_username_description": "Cuando está habilitado, Rocket.Chat confiará en que cualquier nombre de usuario de CAS pertenece al mismo usuario en Rocket.Chat.
      Esto puede ser necesario si se cambia el nombre de un usuario en CAS, pero también puede permitir que las personas tomen el control de Rocket. Chatee las cuentas cambiando el nombre de sus propios usuarios CAS", + "CAS_trust_username_description": "Cuando está habilitado, Rocket.Chat confiará en que cualquier nombre de usuario de CAS pertenece al mismo usuario en Rocket.Chat.
      Esto puede ser necesario si se cambia el nombre de un usuario en CAS, pero también puede permitir que las personas tomen el control de Rocket. Chatee las cuentas cambiando el nombre de sus propios usuarios de CAS.", "CAS_version": "Versión CAS", "CAS_version_Description": "Use sólo una versión compatible con CAS admitida por su servicio CAS SSO.", "Categories": "Categorías", @@ -747,7 +755,7 @@ "CDN_PREFIX": "Prefijo de CDN", "CDN_PREFIX_ALL": "Utilizar prefijo CDN para todos los activos", "Certificates_and_Keys": "Certificados y Claves", - "change-livechat-room-visitor": "Cambiar visitante de sala Omnichannel", + "change-livechat-room-visitor": "Cambiar visitante de sala Omnichannel Room", "change-livechat-room-visitor_description": "Permiso para agregar información adicional al visitante de Omnichannel", "Change_Room_Type": "Cambiar el Tipo de Sala", "Changing_email": "Cambiando correo electrónico", @@ -755,7 +763,7 @@ "Channel": "Canal", "Channel_already_exist": "El canal '#%s' ya existe.", "Channel_already_exist_static": "El canal ya existe.", - "Channel_already_Unarchived": "El canal con nombre `#%s` ya está en estado Desarchivado", + "Channel_already_Unarchived": "El Channel con nombre `#%s` ya está en estado Desarchivado", "Channel_Archived": "El canal con nombre `#%s` ha sido archivado con éxito", "Channel_created": "Canal `#%s` creado.", "Channel_doesnt_exist": "El canal `#%s` no existe", @@ -844,7 +852,7 @@ "Chatpal_Window_Size_Description": "El tamaño de las ventanas de índice en horas (en arranque)", "Chats_removed": "Chats eliminados", "Check_All": "Seleccionar todo", - "Check_Progress": "Comprobar progres", + "Check_Progress": "Comprobar progreso", "Choose_a_room": "Elija una sala", "Choose_messages": "Elija mensajes", "Choose_the_alias_that_will_appear_before_the_username_in_messages": "Elige el alias que aparecerá antes del nombre de usuario en los mensajes.", @@ -856,15 +864,15 @@ "clear": "Borrar", "Clear_all_unreads_question": "Borrar todos los mensajes no leídos?", "clear_cache_now": "Borrar caché ahora", - "Clear_filters": "Limpiar filtros", + "Clear_filters": "Borrar los filtros", "clear_history": "Borrar historial", "Click_here": "Haga clic aquí", - "Click_here_for_more_details_or_contact_sales_for_a_new_license": "Click aquí para más detalles o contacta con __email__ para una nueva licencia.", + "Click_here_for_more_details_or_contact_sales_for_a_new_license": "Haga clic aquí para obtener más detalles o comuníquese con __email__ para obtener una nueva licencia.", "Click_here_for_more_info": "Haga click aquí para más información", "Click_here_to_enter_your_encryption_password": "Haga clic aquí para ingresar su contraseña de cifrado", "Click_here_to_view_and_copy_your_password": "Haga click aquí para ver y copiar su contraseña.", "Click_the_messages_you_would_like_to_send_by_email": "Haga click en los mensajes que desee enviar por correo electrónico", - "Click_to_join": "¡Click para unirse!", + "Click_to_join": "¡Haga clic para unirse!", "Click_to_load": "Haga click para cargar", "Client_ID": "Cliente ID", "Client_Secret": "Cliente Secreto", @@ -873,12 +881,12 @@ "Close": "Cerrar", "Close_chat": "Cerrar chat", "Close_room_description": "Estás a punto de cerrar este chat. ¿Estás seguro de que quieres continuar?", - "Close_to_seat_limit_banner_warning": "* Te quedan [__seats__] asientos * \nEste espacio de trabajo se acerca a su límite de asientos. Una vez que se alcanza el límite, no se pueden agregar nuevos miembros. * [Solicitar más asientos] (__url__) *", + "Close_to_seat_limit_banner_warning": "* Te quedan [__seats__] sitiios * \nEste espacio de trabajo se acerca a su límite de sitios. Una vez que se alcanza el límite, no se pueden agregar nuevos miembros. * [Solicitar más sitios] (__url__) *", "Close_to_seat_limit_warning": "No se pueden crear nuevos miembros una vez que se alcanza el límite de asientos.", "close-livechat-room": "Cerrar la Room de Omnichannel", "close-livechat-room_description": "Permiso para cerrar la sala Omnichannel actual", "Close_menu": "Cerrar menú", - "close-others-livechat-room": "Cerrar otras salas de Omnichannel", + "close-others-livechat-room": "Cerrar otras Room de Omnichannel", "close-others-livechat-room_description": "Permiso para cerrar otras salas de Omnichannel", "Closed": "Cerrado", "Closed_At": "Cerrado en", @@ -923,10 +931,10 @@ "Cloud_update_email": "Actualizar correo electrónico", "Cloud_what_is_it": "¿Que es esto?", "Cloud_what_is_it_additional": "Además, podrá administrar licencias, facturación y soporte desde la consola de la nube Rocket.Chat.", - "Cloud_what_is_it_description": "Rocket.Chat Cloud Connect le permite conectar su espacio de trabajo Rocket.Chat con los servicios que ofrecemos en nuestra nube.", + "Cloud_what_is_it_description": "Rocket.Chat Cloud Connect le permite conectar su espacio de trabajo Rocket.Chat autohospedado a los servicios que brindamos en nuestra nube.", "Cloud_what_is_it_services_like": "Servicios como:", "Cloud_workspace_connected": "Su espacio de trabajo está conectado a Rocket.Chat Cloud. Iniciar sesión en su cuenta de Rocket.Chat Cloud aquí le permitirá interactuar con algunos servicios como el marketplace.", - "Cloud_workspace_connected_plus_account": "Su área de trabajo ahora está conectada a Rocket.Chat Cloud y una cuenta está asociada.", + "Cloud_workspace_connected_plus_account": "Su espacio de trabajo ahora está conectado a Rocket.Chat Cloud y una cuenta está asociada.", "Cloud_workspace_connected_without_account": "Su espacio de trabajo ahora está conectado a Rocket.Chat Cloud. Si lo desea, puede iniciar sesión en Rocket.Chat Cloud y asociar su espacio de trabajo con su cuenta en la nube.", "Cloud_workspace_disconnect": "Si ya no desea utilizar los servicios en la nube, puede desconectar su espacio de trabajo de Rocket.Chat Cloud.", "Cloud_workspace_support": "Si tiene problemas con un servicio en la nube, intente sincronizar primero. Si el problema persiste, abra un ticket de soporte en Cloud Console.", @@ -937,7 +945,7 @@ "Color": "Color", "Colors": "Colores", "Commands": "Comandos", - "Comment_to_leave_on_closing_session": "Comentario para salir en la sesión de clausura", + "Comment_to_leave_on_closing_session": "Comentario para dejar en la sesión de cierre", "Comment": "Comentario", "Common_Access": "Acceso común", "Community": "Comunidad", @@ -979,7 +987,7 @@ "Contact_Info": "Información de contacto", "Content": "Contenido", "Continue": "Continuar", - "Continuous_sound_notifications_for_new_livechat_room": "Notificaciones continuas de sonido para nuevas salas", + "Continuous_sound_notifications_for_new_livechat_room": "Notificaciones de sonido continuas para una nueva sala omnichannel", "Conversation": "Conversación", "Conversation_closed": "Conversación cerrada: __comment__.", "Conversation_closing_tags": "Etiquetas de cierre de la conversación", @@ -1114,7 +1122,7 @@ "Country_Kenya": "Kenia", "Country_Kiribati": "Kiribati", "Country_Korea_Democratic_Peoples_Republic_of": "República Popular Democrática de Corea", - "Country_Korea_Republic_of": "Corea, República de", + "Country_Korea_Republic_of": "República de Corea", "Country_Kuwait": "Kuwait", "Country_Kyrgyzstan": "Kirguistán", "Country_Lao_Peoples_Democratic_Republic": "República Democrática Popular Lao", @@ -1340,9 +1348,10 @@ "Days": "Días", "DB_Migration": "Migración de base de datos", "DB_Migration_Date": "Fecha de migración de base de datos", + "DDP_Rate_Limit": "Límite de tasa de DDP", "DDP_Rate_Limit_Connection_By_Method_Enabled": "Límite de Conexión por Método: habilitado", - "DDP_Rate_Limit_Connection_By_Method_Interval_Time": "Límite de Conexión por Método: intervalo de tiempo", - "DDP_Rate_Limit_Connection_By_Method_Requests_Allowed": "Límite de Conexión por Método: peticiones permitidas", + "DDP_Rate_Limit_Connection_By_Method_Interval_Time": "Límite por conexión por método: tiempo de intervalo", + "DDP_Rate_Limit_Connection_By_Method_Requests_Allowed": "Límite por conexión por método: solicitudes permitidas", "DDP_Rate_Limit_Connection_Enabled": "Límite por Conexión: habilitado", "DDP_Rate_Limit_Connection_Interval_Time": "Límite por Conexión: intervalo de tiempo", "DDP_Rate_Limit_Connection_Requests_Allowed": "Límite por conexión: solicitudes permitidas", @@ -1399,7 +1408,7 @@ "Desktop_Notifications_Default_Alert": "Alerta predeterminada de notificaciones de escritorio", "Desktop_Notifications_Disabled": "Las Notificaciones de Escritorio has sido Deshabilitadas. Cambia las preferencias en tu navegador si necesitas habilitar las Notificaciones.", "Desktop_Notifications_Duration": "Duración de las notificaciones del escritorio", - "Desktop_Notifications_Duration_Description": "Segundos para mostrar notificación de escritorio. Esto puede afectar el centro de notificaciones de OS X. Introduzca 0 para utilizar la configuración del navegador por defecto y no afectar Centro de Notificación X OS.", + "Desktop_Notifications_Duration_Description": "Segundos para mostrar la notificación de escritorio. Esto puede afectar al Centro de notificaciones de OS X. Ingrese 0 para usar la configuración predeterminada del navegador y no afectar al Centro de notificaciones de OS X.", "Desktop_Notifications_Enabled": "Las Notificaciones de Escritorio están Habilitadas", "Desktop_Notifications_Not_Enabled": "Las notificaciones de escritorio no están habilitadas", "Details": "Detalles", @@ -1414,9 +1423,9 @@ "Direct_Reply_Debug": "Respuesta directa a la depuración", "Direct_Reply_Debug_Description": "[Cuidado] Habilitar el modo de depuración mostraría su 'Contraseña de texto sin formato' en la consola de administración.", "Direct_Reply_Delete": "Eliminar correos electrónicos", - "Direct_Reply_Delete_Description": "[¡Atención!] Si se activa esta opción, todos los mensajes no leídos serán eliminados irrevocablemente, incluso los que no son respuestas directas. El buzón de correo electrónico configurado está entonces siempre vacío y no puede ser procesado en \"paralelo\" por humanos.", + "Direct_Reply_Delete_Description": "[¡Atención!] Si esta opción está activada, todos los mensajes no leídos se eliminan irrevocablemente, incluso aquellos que no son respuestas directas. El buzón de correo electrónico configurado está siempre vacío y no puede ser procesado en \"paralelo\" por humanos.", "Direct_Reply_Enable": "Habilitar respuesta directa", - "Direct_Reply_Enable_Description": "[¡Atención!] Si \"Respuesta Directa\" está habilitado, Rocket.Chat controlará el buzón de correo electrónico configurado. Todos los correos electrónicos no leídos son recuperados, marcados como leídos y procesados. \"Respuesta directa\" sólo debe ser activada si el buzón utilizado está destinado exclusivamente para el acceso de Rocket.Chat y no es leído/procesado \"en paralelo\" por humanos.", + "Direct_Reply_Enable_Description": "[¡Atención!] Si la \"Respuesta directa\" está habilitada, Rocket.Chat controlará el buzón de correo electrónico configurado. Todos los correos electrónicos no leídos se recuperan, se marcan como leídos y se procesan. La \"Respuesta directa\" solo debe activarse si el buzón de correo utilizado está destinado exclusivamente para el acceso de Rocket.Chat y no es leído / procesado \"en paralelo\" por humanos.", "Direct_Reply_Frequency": "Frecuencia de verificación de correo electrónico", "Direct_Reply_Frequency_Description": "(en minutos, por defecto / mínimo 2)", "Direct_Reply_Host": "Host de respuesta directa", @@ -1471,7 +1480,7 @@ "Domains": "Dominios", "Domains_allowed_to_embed_the_livechat_widget": "Lista de dominios separados por comas que permite incrustar el widget LiveChat. Déjelo en blanco para permitir todos los dominios.", "Dont_ask_me_again": "¡No me preguntes otra vez!", - "Dont_ask_me_again_list": "No me preguntes de nuevo", + "Dont_ask_me_again_list": "Lista de 'No me preguntes de nuevo'", "Download": "Descargar", "Download_Info": "Descargar información", "Download_My_Data": "Descargar mis datos (HTML)", @@ -1484,7 +1493,7 @@ "Dry_run_description": "Se enviara únicamente un correo electrónico, a la misma dirección establecida en el campo De. El correo electrónico debe pertenecer a un usuario valido.", "Duplicate_archived_channel_name": "Ya existe un canal archivado con el nombre ' %s' ", "Duplicate_archived_private_group_name": "Ya existe un grupo privado archivado con el nombre ' %s' ", - "Duplicate_channel_name": "Ya existe un canal con el nombre '%s' ", + "Duplicate_channel_name": "Ya existe un Channel con el nombre '%s' ", "Duplicate_file_name_found": "Se encontró un nombre de archivo duplicado.", "Duplicate_private_group_name": "Ya existe un Grupo Privado con el nombre '%s' ", "Duplicated_Email_address_will_be_ignored": "Se ignorará la dirección de correo electrónico duplicada.", @@ -1498,12 +1507,12 @@ "E2E_Enabled_Default_DirectRooms": "Habilitar la encriptación para las Rooms directas por defecto", "E2E_Enabled_Default_PrivateRooms": "Habilitar la encriptación para las Rooms privadas por defecto", "E2E_Encryption_Password_Change": "Cambiar la contraseña de cifrado", - "E2E_Encryption_Password_Explanation": "Ahora puede crear grupos privados codificados y mensajes directos. También puede cambiar los grupos privados o mensajes directos existentes a cifrados.

      Esto es cifrado de extremo a extremo, así que la clave para cifrar/descifrar tus mensajes no se guardará en el servidor. Por esa razón necesitas guardar tu contraseña en un lugar seguro. Se te pedirá que la introduzcas en otros dispositivos en los que desees utilizar la encriptación de E2E.", + "E2E_Encryption_Password_Explanation": "Ahora puede crear grupos privados cifrados y mensajes directos. También puede cambiar los grupos privados o DM existentes a cifrados.

      Este es un cifrado de extremo a extremo, por lo que la clave para codificar / decodificar sus mensajes no se guardará en el servidor. Por esa razón, debe guardar su contraseña en un lugar seguro. Se le pedirá que lo ingrese en otros dispositivos en los que desee usar el cifrado e2e.", "E2E_key_reset_email": "Notificación de reseteo de clave E2E", "E2E_password_request_text": "Para acceder a sus grupos privados cifrados y a los mensajes directos, introduzca su contraseña de cifrado.
      Necesitas introducir esta contraseña para cifrar/descifrar tus mensajes en cada cliente que utilices, ya que la clave no se almacena en el servidor.", - "E2E_password_reveal_text": "Ahora puede crear grupos privados codificados y mensajes directos. También puedes cambiar los grupos privados o mensajes directos existentes a cifrados.

      Esto es cifrado de extremo a extremo, así que la clave para cifrar/descifrar tus mensajes no se guardará en el servidor. Por esa razón necesitas almacenar esta contraseña en un lugar seguro. Se te pedirá que la introduzcas en otros dispositivos en los que desees utilizar cifrado E2E. ¡Aprende más aquí!

      Tu contraseña es: %s

      Esta es una contraseña autogenerada, puedes configurar una nueva contraseña para tu clave de cifrado en cualquier momento desde cualquier navegador en el que hayas introducido la contraseña existente.
      Esta contraseña sólo se almacena en este navegador hasta que guardes la contraseña y desestimes este mensaje.", + "E2E_password_reveal_text": "Ahora puede crear grupos privados cifrados y mensajes directos. También puede cambiar los grupos privados o DM existentes a cifrados.

      Este es un cifrado de extremo a extremo, por lo que la clave para codificar / decodificar sus mensajes no se guardará en el servidor. Por esa razón, debe guardar esta contraseña en un lugar seguro. Se le pedirá que lo ingrese en otros dispositivos en los que desee usar el cifrado e2e. ¡Obtenga más información aquí!

      Su contraseña es: % s

      Esta es una contraseña generada automáticamente, puede configurar una nueva contraseña para su clave de cifrado en cualquier momento desde cualquier navegador que haya ingresado la contraseña existente.
      Esta contraseña solo se almacena en este navegador hasta que la guarde y descarte este mensaje.", "E2E_Reset_Email_Content": "Se ha desconectado automáticamente. Cuando vuelva a iniciar sesión, Rocket.Chat generará una nueva clave y restaurará su acceso a cualquier sala cifrada que tenga uno o más miembros en línea. Debido a la naturaleza del cifrado E2E, Rocket.Chat no podrá restaurar el acceso a ninguna sala cifrada que no tenga miembros en línea.", - "E2E_Reset_Key_Explanation": "Esta opción eliminará su clave E2E actual y cerrará la sesión.
      Cuando vuelva a iniciar sesión, Rocket.Chat le generará una nueva clave y restablecerá su acceso a cualquier sala cifrada que tenga uno o más miembros en línea.
      Debido a la naturaleza del cifrado E2E, Rocket.Chat no podrá restaurar el acceso a ninguna sala cifrada que no tenga miembros en línea", + "E2E_Reset_Key_Explanation": "Esta opción eliminará su clave E2E actual y cerrará la sesión.
      Cuando vuelva a iniciar sesión, Rocket.Chat le generará una nueva clave y restaurará su acceso a cualquier sala cifrada que tenga uno o más miembros en línea.
      Debido a la naturaleza del cifrado E2E, Rocket.Chat no podrá restaurar el acceso a ninguna sala cifrada que no tenga miembros en línea", "E2E_Reset_Other_Key_Warning": "Restablecer la clave E2E actual cerrará la sesión del usuario. Cuando el usuario vuelve a iniciar sesión, Rocket.Chat generará una nueva clave y restaurará el acceso del usuario a cualquier sala cifrada que tenga uno o más miembros en línea. Debido a la naturaleza del cifrado E2E, Rocket.Chat no podrá restaurar el acceso a ninguna sala cifrada que no tenga miembros en línea.", "ECDH_Enabled": "Habilite el cifrado de segunda capa para el transporte de datos", "Edit": "Editar", @@ -1517,10 +1526,10 @@ "Edit_Priority": "Editar prioridad", "Edit_Status": "Editar estado", "Edit_Tag": "Editar etiqueta", - "Edit_Trigger": "Editar disparador", + "Edit_Trigger": "Editar activador", "Edit_Unit": "Editar unidad", "Edit_User": "Editar usuario", - "edit-livechat-room-customfields": "Editar campos personalizados de sala Omnichannel", + "edit-livechat-room-customfields": "Editar campos personalizados de Omnichannel Room", "edit-livechat-room-customfields_description": "Permiso para editar los campos personalizados de la sala Omnichannel", "edit-message": "Editar Mensaje", "edit-message_description": "Permiso para editar un mensaje dentro de una sala", @@ -1531,7 +1540,7 @@ "edit-other-user-e2ee": "Editar el cifrado E2E de otro usuario", "edit-other-user-e2ee_description": "Permiso para editar el cifrado E2E de otro usuario", "edit-other-user-info": "Editar la información de otro usuario", - "edit-other-user-info_description": "Permiso para cambiar el nombre, nombre de usuario o dirección de correo electrónico de otro usuario.", + "edit-other-user-info_description": "Permiso para cambiar el nombre, el nombre de usuario o la dirección de correo electrónico de otro usuario.", "edit-other-user-password": "Editar la contraseña de otro usuario", "edit-other-user-password_description": "Permiso para modificar las contraseñas de otros usuarios. Requiere permiso edit-other-user-info.", "edit-other-user-totp": "Editar el doble factor TOTP de otro usuario", @@ -1556,11 +1565,11 @@ "Email_address_to_send_offline_messages": "Dirección de correo electrónico para enviar mensajes fuera de línea", "Email_already_exists": "El correo electrónico ya existe", "Email_body": "Cuerpo del Correo electrónico", - "Email_Change_Disabled": "Su administrador ha deshabilitado el cambio de correo electrónico", - "Email_Changed_Description": "Puede utilizar los siguientes marcadores de posición:
      • [email] para el correo electrónico del usuario.
      • [Site_Name] y [Site_URL] para el nombre de la aplicación y la URL respectivamente.
      ", + "Email_Change_Disabled": "Su administrador de Rocket.Chat ha desactivado el cambio de correo electrónico", + "Email_Changed_Description": "Puede utilizar los siguientes marcadores de posición:
      • [email] para el correo electrónico del usuario.
      • [Site_Name] y [Site_URL] para el nombre de la aplicación y la URL respectivamente.
      ", "Email_Changed_Email_Subject": "[Site_Name] - La dirección de correo electrónico ha sido modificada", "Email_changed_section": "Dirección de correo electrónico modificada", - "Email_Footer_Description": "Es posible utilizar los siguientes marcadores:
      • [Site_Name] y [Site_URL] para el nombre de la aplicación y la URL, respectivamente.
      ", + "Email_Footer_Description": "Puede utilizar los siguientes marcadores de posición:
      • [Site_Name] y [Site_URL] para el nombre de la aplicación y la URL respectivamente.
      ", "Email_from": "De", "Email_Header_Description": "Es posible utilizar los siguientes marcadores:
      • [Site_Name] y [Site_URL] para el nombre de la aplicación y la URL, respectivamente.
      ", "Email_Inbox": "Bandeja de entrada de correo electrónico", @@ -1603,11 +1612,13 @@ "Encrypted": "Cifrado", "Encrypted_channel_Description": "Canal cifrado de extremo a extremo. La búsqueda no funcionará con canales cifrados y es posible que las notificaciones no muestren el contenido de los mensajes.", "Encrypted_message": "Mensaje cifrado", - "Encrypted_setting_changed_successfully": "La configuración encriptada se modificó correctamente", + "Encrypted_setting_changed_successfully": "La configuración encriptada se cambió correctamente", "Encrypted_not_available": "No disponible para Channels Públicos", - "Encryption_key_saved_successfully": "Su clave de encriptación se guardó correctamente", + "Encryption_key_saved_successfully": "su clave de cifrado se guardó correctamente.", "EncryptionKey_Change_Disabled": "No puede establecer una contraseña para su clave de cifrado porque su clave privada no está presente en este cliente. Para establecer una nueva contraseña, necesita cargar su clave privada usando su contraseña existente o usar un cliente donde la clave ya esté cargada.", "End": "Fin", + "End_call": "Finalizar llamada", + "Expand_view": "Expandir vista", "End_OTR": "Finalizar OTR", "Engagement_Dashboard": "Panel de participación", "Enter": "Entrar", @@ -1629,7 +1640,7 @@ "Enter_your_E2E_password": "Introduzca su contraseña E2E", "Enterprise": "Empresa", "Enterprise_License": "Licencia de empresa", - "Enterprise_License_Description": "Si tu espacio de trabajo está registrado y dispone de una licencia proporcionada por Rocket.Chat Cloud no es necesario actualizar esta licencia.", + "Enterprise_License_Description": "Si su espacio de trabajo está registrado y la licencia la proporciona Rocket.Chat Cloud, no es necesario que actualice manualmente la licencia aquí.", "Entertainment": "Entretenimiento", "Error": "Error", "Error_404": "Error 404", @@ -1671,13 +1682,13 @@ "error-field-unavailable": "__field__ ya está en uso :(", "error-file-too-large": "El archivo es demasiado grande", "error-forwarding-chat": "Se produjo un error al reenviar el chat. Vuelve a intentarlo más tarde.", - "error-forwarding-chat-same-department": "El departamento seleccionado y el actual departamento de la sala son los mismos", + "error-forwarding-chat-same-department": "El departamento seleccionado y el departamento de sala actual son el mismo", "error-forwarding-department-target-not-allowed": "No se permite el reenvío al departamento de destino.", "error-guests-cant-have-other-roles": "Los usuarios invitados no pueden tener ningún otro rol.", "error-import-file-extract-error": "No se pudo extraer el archivo de importación.", "error-import-file-is-empty": "El archivo importado parece estar vacío.", "error-import-file-missing": "No se encontró el archivo a importar en la ruta especificada.", - "error-importer-not-defined": "El importador no se definió correctamente, no se encuentra la Clase de Importación.", + "error-importer-not-defined": "El importador no se definió correctamente, falta la clase Import.", "error-input-is-not-a-valid-field": "__input__ no es un __field__ válido", "error-invalid-account": "Cuenta no válida", "error-invalid-actionlink": "Enlace de acción inválido", @@ -1709,7 +1720,7 @@ "error-invalid-permission": "Permiso no válido", "error-invalid-port-number": "Número de puerto no válido", "error-invalid-priority": "Prioridad inválida", - "error-invalid-redirectUri": "redirectUri no válida", + "error-invalid-redirectUri": "RedirectUri no válido", "error-invalid-role": "Rol no válido", "error-invalid-room": "Sala no válida", "error-invalid-room-name": "__room_name__ no es un nombre válido de sala, utilice sólo letras, números, guiones y guiones bajos", @@ -1745,7 +1756,7 @@ "error-password-policy-not-met-oneNumber": "La contraseña no cumple con la política del servidor de al menos un carácter numérico", "error-password-policy-not-met-oneSpecial": "La contraseña no cumple con la política del servidor de al menos un carácter especial", "error-password-policy-not-met-oneUppercase": "La contraseña no cumple con la política del servidor de al menos un carácter en mayúscula", - "error-password-policy-not-met-repeatingCharacters": "La contraseña no cumple con la política del servidor de caracteres repetitivos prohibidos (tiene demasiados caracteres iguales uno al lado del otro)", + "error-password-policy-not-met-repeatingCharacters": "La contraseña no cumple con la política del servidor de caracteres repetidos prohibidos (tiene demasiados caracteres iguales uno al lado del otro)", "error-password-same-as-current": "Ingresó la misma contraseña que la contraseña actual", "error-personal-access-tokens-are-current-disabled": "Las claves de acceso personales están actualmente desactivadas", "error-pinning-message": "No se pudo fijar el mensaje", @@ -1753,18 +1764,18 @@ "error-remove-last-owner": "Este es el último propietario. Por favor, establece un nuevo propietario antes de eliminarlo.", "error-returning-inquiry": "Error al devolver la consulta a la cola", "error-role-in-use": "No puede eliminar el rol porque esta en uso", - "error-role-name-required": "El nombre de rol es requerido", + "error-role-name-required": "Se requiere nombre de Rol", "error-role-already-present": "Ya existe un rol con este nombre", "error-room-is-not-closed": "La sala no está cerrada", "error-room-onHold": "¡Error! Room está en espera", "error-selected-agent-room-agent-are-same": "El agente seleccionado y el agente de la sala son los mismos", "error-starring-message": "No se pudo mirar el mensaje", "error-tags-must-be-assigned-before-closing-chat": "Se deben asignar etiquetas antes de cerrar el chat", - "error-the-field-is-required": " E campo __field__. es requerido", + "error-the-field-is-required": "El campo __field__ es obligatorio.", "error-this-is-not-a-livechat-room": "Esta no es una sala de Omnichannel", "error-token-already-exists": "Ya existe un token con este nombre", "error-token-does-not-exists": "El token no existe", - "error-too-many-requests": "Error, demasiadas peticiones. Por favor más despacio. Debe esperar __seconds__ segundos antes de volver a intentarlo.", + "error-too-many-requests": "Error, demasiadas solicitudes. Por favor más despacio. Debe esperar __seconds__ segundos antes de volver a intentarlo.", "error-transcript-already-requested": "Transcripción ya solicitada", "error-unpinning-message": "No se pudo desanclar el mensaje", "error-user-has-no-roles": "El usuario no tiene roles", @@ -1779,7 +1790,7 @@ "error-validating-department-chat-closing-tags": "Se requiere al menos una etiqueta de cierre cuando el departamento requiere etiqueta(s) en las conversaciones de cierre.", "error-no-permission-team-channel": "No tienes permiso para agregar este canal al equipo.", "error-no-owner-channel": "Solo los propietarios pueden agregar este canal al equipo", - "error-you-are-last-owner": "Usted es el último propietario. Por favor, establezca un nuevo propietario antes de salir de la Sala.", + "error-you-are-last-owner": "Eres el último dueño. Establezca un nuevo propietario antes de salir de la Sala.", "Errors_and_Warnings": "Errores y advertencias", "Esc_to": "Esc a", "Estimated_due_time": "Tiempo estimado de espera (tiempo en minutos)", @@ -1800,14 +1811,14 @@ "Example_s": "Ejemplo: %s", "except_pinned": "(excepto aquellos que están fijados)", "Exclude_Botnames": "Excluir Bots", - "Exclude_Botnames_Description": "No propagar los mensajes de bots cuyos nombres coincidan con la expresión regular. Se se deja en blanco, todos los mensajes de los bots se propagarán.", + "Exclude_Botnames_Description": "No propague mensajes de bots cuyo nombre coincida con la expresión regular anterior. Si se deja en blanco, se propagarán todos los mensajes de los bots.", "Exclude_pinned": "Excluir mensajes fijados", "Execute_Synchronization_Now": "Ejecutar sincronización ahora", "Exit_Full_Screen": "Salir de pantalla completa", "Expand": "Expandir", "Experimental_Feature_Alert": "¡Esta es una función experimental! Tenga en cuenta que puede cambiar, romperse o incluso eliminarse en el futuro sin previo aviso.", "Expiration": "Expiración", - "Expiration_(Days)": "Expiración (días)", + "Expiration_(Days)": "Caducidad (días)", "Export_as_file": "Exportar como archivo", "Export_Messages": "Exportar mensajes", "Export_My_Data": "Exportar mis datos (jSON)", @@ -1823,19 +1834,21 @@ "Facebook_Page": "Pagina de Facebook", "Failed": "Error", "Failed_to_activate_invite_token": "Error al activar el token de invitación", - "Failed_to_add_monitor": "Error al añadir monitor", + "Failed_to_add_monitor": "No se pudo agregar el monitor", "Failed_To_Download_Files": "Error al descargar ficheros", "Failed_to_generate_invite_link": "Error al generar enlace de invitación", "Failed_To_Load_Import_Data": "Error al cargar importación de datos", "Failed_To_Load_Import_History": "Error al cargar importación de histórico", "Failed_To_Load_Import_Operation": "Error al cargar operación de importación", "Failed_To_Start_Import": "Error al iniciar operación de importación", - "Failed_to_validate_invite_token": "Error al validar token de invitación", + "Failed_to_validate_invite_token": "Error al validar el token de invitación", "False": "Falso", "Favorite": "Favorito", "Favorite_Rooms": "Habilitar salas favoritas", "Favorites": "Favoritos", + "Feature_depends_on_selected_call_provider_to_be_enabled_from_administration_settings": "Esta función depende del proveedor de llamadas seleccionado anteriormente que se habilitará desde la configuración de administración.", "Feature_Depends_on_Livechat_Visitor_navigation_as_a_message_to_be_enabled": "Esta función depende de \"Enviar el historial de navegación de visitantes como un mensaje\" para que se habilite.", + "Feature_Limiting": "Limitación de funciones", "Features": "Características", "Features_Enabled": "Funcionalidades habilitadas", "Feature_Disabled": "Característica deshabilitada", @@ -1880,10 +1893,10 @@ "FEDERATION_Discovery_Method_Description": "Puede usar el hub o un SRV y una entrada TXT en sus registros DNS.", "FEDERATION_Domain": "Dominio", "FEDERATION_Domain_Alert": "No cambie esto después de habilitar la función, todavía no podemos manejar los cambios de dominio.", - "FEDERATION_Domain_Description": "Añada el dominio al que este servidor debería estar vinculado, por ejemplo: @rocket.chat.", - "FEDERATION_Enabled": "Intentar integrar la federación de soporte.", + "FEDERATION_Domain_Description": "Agregue el dominio al que debe estar vinculado este servidor, por ejemplo: @ rocket.chat", + "FEDERATION_Enabled": "Intente integrar el soporte de la federación.", "FEDERATION_Enabled_Alert": "La federación de soporte está en progreso. Su uso en un entorno de producción no se recomienda de momento.", - "FEDERATION_Error_user_is_federated_on_rooms": "No se puede eliminar a los usuarios federados que pertenecen a las salas", + "FEDERATION_Error_user_is_federated_on_rooms": "No puede eliminar usuarios federados que pertenecen a salas", "FEDERATION_Hub_URL": "URL del Hub", "FEDERATION_Hub_URL_Description": "Configure la URL del concentrador, por ejemplo: https://hub.rocket.chat. También se aceptan puertos.", "FEDERATION_Public_Key": "Clave pública", @@ -1897,7 +1910,7 @@ "FEDERATION_Unique_Id_Description": "Este es el ID único de su federación, que se utiliza para identificar a su par en la red.", "Field": "Campo", "Field_removed": "Campo eliminado", - "Field_required": "Campo requerido", + "Field_required": "Campo obligatorio", "File": "Archivo", "File_Downloads_Started": "Descargas de archivos iniciadas", "File_exceeds_allowed_size_of_bytes": "El archivo supera el tamaño permitido de __size__ ", @@ -1931,10 +1944,10 @@ "FileUpload_FileSystemPath": "Ruta del sistema", "FileUpload_GoogleStorage_AccessId": "ID de acceso de almacenamiento de Google", "FileUpload_GoogleStorage_AccessId_Description": "El identificador de acceso generalmente está en un formato de correo electrónico, por ejemplo: \"example-test@example.iam.gserviceaccount.com\"", - "FileUpload_GoogleStorage_Bucket": "Nombre del bucket de Google", + "FileUpload_GoogleStorage_Bucket": "Nombre del Bucket Google Storage", "FileUpload_GoogleStorage_Bucket_Description": "Nombre del bucket en el que deben cargarse los archivos.", "FileUpload_GoogleStorage_Proxy_Avatars": "Avatares Proxy", - "FileUpload_GoogleStorage_Proxy_Avatars_Description": "Transmisiones de archivos proxy avatar a través de su servidor en lugar de acceso directo a la URL del activo", + "FileUpload_GoogleStorage_Proxy_Avatars_Description": "Transmisiones de archivos de avatar de proxy a través de su servidor en lugar de acceso directo a la URL del activo", "FileUpload_GoogleStorage_Proxy_Uploads": "Subidas de Proxy", "FileUpload_GoogleStorage_Proxy_Uploads_Description": "Proxy carga transmisiones de archivos a través de su servidor en lugar de acceso directo a la URL del activo", "FileUpload_GoogleStorage_Secret": "Secreto de almacenamiento de Google", @@ -1955,10 +1968,10 @@ "FileUpload_RotateImages_Description": "Habilitar esta configuración puede causar pérdida de calidad de imagen", "FileUpload_S3_Acl": "Amazon S3 acl", "FileUpload_S3_AWSAccessKeyId": "Access Key", - "FileUpload_S3_AWSSecretAccessKey": "Secret Key", + "FileUpload_S3_AWSSecretAccessKey": "Clave Secreta", "FileUpload_S3_Bucket": "Nombre de Bucket", "FileUpload_S3_BucketURL": "Bucket URL", - "FileUpload_S3_CDN": "Dominio CDN para Descargas", + "FileUpload_S3_CDN": "Dominio CDN para descargas", "FileUpload_S3_ForcePathStyle": "Estilo de ruta de fuerza", "FileUpload_S3_Proxy_Avatars": "Avatares Proxy", "FileUpload_S3_Proxy_Avatars_Description": "Proxy transmisiones de archivos de avatar a través de su servidor en lugar de acceso directo a la URL del activo", @@ -1967,14 +1980,14 @@ "FileUpload_S3_Region": "Región", "FileUpload_S3_SignatureVersion": "Versión de firma", "FileUpload_S3_URLExpiryTimeSpan": "Tiempo de caducidad de las URLs", - "FileUpload_S3_URLExpiryTimeSpan_Description": "Tiempo después el cual las direcciones de Amazon S3 generadas dejarán de ser válidas (en segundos). Si se establece a menos de 5 segundos, este campo será ignorado.", + "FileUpload_S3_URLExpiryTimeSpan_Description": "Tiempo después del cual las URL generadas por Amazon S3 ya no serán válidas (en segundos). Si se establece en menos de 5 segundos, este campo se ignorará.", "FileUpload_Storage_Type": "Tipo de Almacenamiento", "FileUpload_Webdav_Password": "Contraseña de WebDAV", "FileUpload_Webdav_Proxy_Avatars": "Avatares Proxy", - "FileUpload_Webdav_Proxy_Avatars_Description": "Transmisiones de archivos de avatar a través de su servidor en lugar de acceso directo a la URL del activo", + "FileUpload_Webdav_Proxy_Avatars_Description": "Transmisiones de archivos de avatar de proxy a través de su servidor en lugar de acceso directo a la URL del activoivo", "FileUpload_Webdav_Proxy_Uploads": "Subidas de Proxy", "FileUpload_Webdav_Proxy_Uploads_Description": "Transmisiones de archivos de carga proxy a través de su servidor en lugar de acceso directo a la URL del activo", - "FileUpload_Webdav_Server_URL": "URL de acceso al servidor WebDAV", + "FileUpload_Webdav_Server_URL": "UURL de acceso al servidor WebDAV", "FileUpload_Webdav_Upload_Folder_Path": "Cargar ruta de carpeta", "FileUpload_Webdav_Upload_Folder_Path_Description": "Ruta de la carpeta WebDAV en la que se deben cargar los archivos", "FileUpload_Webdav_Username": "Nombre de usuario WebDAV", @@ -1997,20 +2010,20 @@ "For_more_details_please_check_our_docs": "Para obtener más detalles, consulte nuestros documentos.", "For_your_security_you_must_enter_your_current_password_to_continue": "Por su seguridad, debe introducir su contraseña actual para continuar", "Force_Disable_OpLog_For_Cache": "Forzar la desactivación de OpLog para caché", - "Force_Disable_OpLog_For_Cache_Description": "No usará OpLog para sincronizar el caché, incluso cuando esté disponible", + "Force_Disable_OpLog_For_Cache_Description": "No usará OpLog para sincronizar la caché incluso cuando esté disponible", "Force_Screen_Lock": "Forzar bloqueo de pantalla", "Force_Screen_Lock_After": "Forzar bloqueo de pantalla después de", "Force_Screen_Lock_After_description": "El tiempo para solicitar la contraseña nuevamente después de finalizar la última sesión, en segundos.", "Force_Screen_Lock_description": "Cuando esté habilitado, obligará a sus usuarios a usar un PIN / BIOMETRÍA / FACEID para desbloquear la aplicación.", "Force_SSL": "Forzar SSL", - "Force_SSL_Description": "* Precaución! * _Force SSL_ nunca debe ser usado con proxy inverso. Si usted tiene un proxy inverso, debería hacer la redirección AHÍ. Esta opción existe para los despliegues como Heroku, que no permite la configuración de redirección en el proxy inverso.", + "Force_SSL_Description": "* ¡Precaución! * _Force SSL_ nunca debe usarse con proxy inverso. Si tiene un proxy inverso, debe realizar la redirección ALLÍ. Esta opción existe para implementaciones como Heroku, que no permite la configuración de redireccionamiento en el proxy inverso.", "Force_visitor_to_accept_data_processing_consent": "Obligar al visitante a aceptar el consentimiento del procesamiento de datos", "Force_visitor_to_accept_data_processing_consent_description": "Los visitantes no pueden empezar a chatear sin consentimiento.", "Force_visitor_to_accept_data_processing_consent_enabled_alert": "El acuerdo con el procesamiento de datos debe basarse en una comprensión transparente del motivo del procesamiento. Debido a esto, debe completar la configuración a continuación que se mostrará a los usuarios para proporcionar las razones para recopilar y procesar su información personal.", "force-delete-message": "Forzar borrar mensaje", "force-delete-message_description": "Permiso para eliminar un mensaje que pasa por alto todas las restricciones", "Forgot_password": "¿Olvidó su contraseña?", - "Forgot_Password_Description": "Puede usar los siguientes marcadores de posición:
      • [Forgot_Password_Url] para la URL de recuperación de contraseña.
      • [nombre], [fname], [lname] para el nombre completo, el nombre o el apellido del usuario, respectivamente.
      • [email] para el correo electrónico del usuario.
      • [Site_Name] y [Site_URL] para el nombre de la aplicación y la URL, respectivamente.
      ", + "Forgot_Password_Description": "Puede utilizar los siguientes marcadores de posición:
      • [Forgot_Password_Url] para la URL de recuperación de contraseña.
      • [name], [fname] , [lname] para el nombre completo, nombre o apellido del usuario, respectivamente.
      • [email] para el correo electrónico del usuario.
      • [Site_Name ] y [Site_URL] para el nombre de la aplicación y la URL respectivamente.
      ", "Forgot_Password_Email": "Haga clic en aquí para restablecer su contraseña.", "Forgot_Password_Email_Subject": "[Site_Name] - Recuperación de contraseña", "Forgot_password_section": "Olvidó su contraseña", @@ -2050,7 +2063,7 @@ "Global_purge_override_warning": "Existe una política de retención global. Si deja desactivada la opción \"Anular la política de retención global\", solo puede aplicar una política que sea más estricta que la política global.", "Global_Search": "Búsqueda global", "Go_to_your_workspace": "Ve a tu espacio de trabajo", - "GoogleCloudStorage": "Google Cloud Storage", + "GoogleCloudStorage": "Almacenamiento en la nube de Google", "GoogleNaturalLanguage_ServiceAccount_Description": "Clave de la cuenta de servicio archivo JSON. Puede encontrar más información [aquí] (https://cloud.google.com/natural-language/docs/common/auth#set_up_a_service_account)", "GoogleTagManager_id": "Id del Administrador de etiquetas de Google", "Government": "Gobierno", @@ -2088,6 +2101,7 @@ "Hide_System_Messages": "Ocultar mensajes de sistema", "Hide_Unread_Room_Status": "Ocultar el estado de una sala no leída", "Hide_usernames": "Ocultar nombres de usuario", + "Hide_video": "Ocultar video", "Highlights": "Destacados", "Highlights_How_To": "Para ser notificado cuando alguien menciona una palabra o frase, añadir aquí. Puede separar las palabras o frases con comas. Resaltar palabras no distingue entre mayúsculas y minúsculas.", "Highlights_List": "Resaltar palabras", @@ -2098,11 +2112,11 @@ "hours": "horas", "Hours": "Horas", "How_friendly_was_the_chat_agent": "¿Ha sido amable el agente de chat?", - "How_knowledgeable_was_the_chat_agent": "¿Cuánto sabía el agente de chat?", + "How_knowledgeable_was_the_chat_agent": "¿Qué tan informado estaba el agente de chat?", "How_long_to_wait_after_agent_goes_offline": "Cuánto tiempo esperar después de que el agente se desconecte", "How_long_to_wait_to_consider_visitor_abandonment": "¿Cuánto tiempo esperar para considerar el abandono de visitantes?", "How_long_to_wait_to_consider_visitor_abandonment_in_seconds": "¿Cuánto tiempo hay que esperar para considerar el abandono de visitantes?", - "How_responsive_was_the_chat_agent": "¿Cómo de rápido ha respondido nuestro agente de chat?", + "How_responsive_was_the_chat_agent": "¿Qué tan receptivo fue el agente de chat?", "How_satisfied_were_you_with_this_chat": "¿Cómo de satisfecho está con esta conversación?", "How_to_handle_open_sessions_when_agent_goes_offline": "Cómo manejar sesiones abiertas cuando el agente se desconecta", "I_Saved_My_Password": "He guardado mi contraseña", @@ -2114,16 +2128,16 @@ "If_you_are_sure_type_in_your_username": "Si está seguro ingrese su nombre de usuario:", "If_you_didnt_ask_for_reset_ignore_this_email": "Si no solicitó el restablecimiento de su contraseña, puede ignorar este correo electrónico.", "If_you_didnt_try_to_login_in_your_account_please_ignore_this_email": "Si no intentó iniciar sesión en su cuenta, ignore este correo electrónico.", - "If_you_dont_have_one_send_an_email_to_omni_rocketchat_to_get_yours": "Si no tiene uno, envíe un correo electrónico a [omni@rocket.chat](mailto: omni@rocket.chat) para obtener el suyo.", + "If_you_dont_have_one_send_an_email_to_omni_rocketchat_to_get_yours": "Si no tiene uno, envíe un correo electrónico a [omni@rocket.chat] (mailto: omni@rocket.chat) para obtener el suyo.", "Iframe_Integration": "Integración iframe", - "Iframe_Integration_receive_enable": "Habilitar Recibir", + "Iframe_Integration_receive_enable": "Habilitar recibir", "Iframe_Integration_receive_enable_Description": "Permitir que la ventana principal envíe comandos a Rocket.Chat.", "Iframe_Integration_receive_origin": "Recibir orígenes", - "Iframe_Integration_receive_origin_Description": "Orígenes con prefijo de protocolo, separados por comas, que pueden recibir comandos, p. 'https://localhost, http://localhost', o * para permitir la recepción desde cualquier lugar.", + "Iframe_Integration_receive_origin_Description": "Orígenes con prefijo de protocolo, separados por comas, que pueden recibir comandos, p. Ej. 'https: // localhost, http: // localhost', o * para permitir recibir desde cualquier lugar.", "Iframe_Integration_send_enable": "Habilitar envío", "Iframe_Integration_send_enable_Description": "Enviar eventos a la ventana principal", "Iframe_Integration_send_target_origin": "Enviar origen de destino", - "Iframe_Integration_send_target_origin_Description": "Origen con prefijo de protocolo, cuyos comandos se envían a, p. 'https://localhost', o * para permitir el envío a cualquier parte.", + "Iframe_Integration_send_target_origin_Description": "Origen con prefijo de protocolo, al que se envían los comandos, p. Ej. 'https: // localhost', o * para permitir el envío a cualquier lugar.", "Iframe_Restrict_Access": "Restringir el acceso dentro de cualquier iframe", "Iframe_Restrict_Access_Description": "Esta configuración habilita / deshabilita las restricciones para cargar el RC dentro de cualquier iframe", "Iframe_X_Frame_Options": "Opciones de X-Frame-Options", @@ -2144,7 +2158,7 @@ "Importer_CSV_Information": "El importador de CSV requiere un formato específico; lea la documentación sobre cómo estructurar su archivo zip:", "Importer_done": "¡Importación terminada!", "Importer_ExternalUrl_Description": "También puede utilizar una URL para un archivo de acceso público:", - "Importer_finishing": "Finalizando la importación.", + "Importer_finishing": "Terminando la importación.", "Importer_From_Description": "Las importaciones __from__ datos's en Rocket.Chat.", "Importer_HipChatEnterprise_BetaWarning": "Tenga en cuenta que esta importación sigue siendo un trabajo en progreso. Informe cualquier error que ocurra en GitHub:", "Importer_HipChatEnterprise_Information": "El archivo cargado debe ser un tar.gz descifrado, lea la documentación para obtener más información:", @@ -2159,7 +2173,7 @@ "Importer_not_setup": "El importador no está configurado correctamente, ya que no devolvió ningún dato.", "Importer_Prepare_Restart_Import": "Reiniciar importación", "Importer_Prepare_Start_Import": "Iniciar importación", - "Importer_Prepare_Uncheck_Archived_Channels": "Descarmar salas archivadas", + "Importer_Prepare_Uncheck_Archived_Channels": "Desmarcar Channel archivadas", "Importer_Prepare_Uncheck_Deleted_Users": "Desmarcar usuarios eliminados", "Importer_progress_error": "No se pudo obtener el progreso de la importación.", "Importer_setup_error": "Se produjo un error al configurar el importador.", @@ -2184,7 +2198,7 @@ "importer_status_uploading": "Subiendo archivo", "importer_status_user_selection": "Preparado para seleccionar qué importar", "Importer_Upload_FileSize_Message": "La configuración de su servidor permite subir archivos de tamaño hasta __maxFileSize__.", - "Importer_Upload_Unlimited_FileSize": "La configuración de su servidor permite la subida de archivos de cualquier tamaño.", + "Importer_Upload_Unlimited_FileSize": "La configuración de su servidor permite la carga de archivos de cualquier tamaño.", "Importing_channels": "Importando canales", "Importing_Data": "Importando datos", "Importing_messages": "Importando mensajes", @@ -2203,19 +2217,19 @@ "Install": "Instalar", "Install_Extension": "Instalar Extensión", "Install_FxOs": "Instalar Rocket.Chat en su Firefox", - "Install_FxOs_done": "¡Genial! Ya puede comenzar a usarRocket.Chat mediante el ícono en su Escritorio. ¡Diviértase usando Rocket.Chat!", + "Install_FxOs_done": "¡Excelente! Ahora puede usar Rocket.Chat a través del ícono en su pantalla de inicio. Diviértete con Rocket.Chat!", "Install_FxOs_error": "Lo sentimos, ¡eso no funcionó como se esperaba! El siguiente error apareció:", "Install_FxOs_follow_instructions": "Por favor confirma la instalación de la aplicación en tu dispositivo (presione \"Instalar\" cuando se le solicite).", "Install_package": "Paquete de instalación", "Installation": "Instalación ", "Installed": "Instalado", - "Installed_at": "Instalación", + "Installed_at": "Instalación en", "Instance": "Instancia", "Instances": "Instancias", "Instances_health": "Estado de la Instancias", "Instance_Record": "Registro de instancia", "Instructions": "Instrucciones", - "Instructions_to_your_visitor_fill_the_form_to_send_a_message": "Instrucciones para sus visitantes completen el formulario para enviar un mensaje", + "Instructions_to_your_visitor_fill_the_form_to_send_a_message": "Instrucciones para su visitante complete el formulario para enviar un mensaje", "Insert_Contact_Name": "Inserte el nombre del contacto", "Insert_Placeholder": "Insertar marcador de posición", "Insurance": "Seguro", @@ -2235,23 +2249,23 @@ "Integration_Outgoing_WebHook_History_Http_Response_Error": "Error de respuesta HTTP", "Integration_Outgoing_WebHook_History_Messages_Sent_From_Prepare_Script": "Mensajes enviados desde Prepare Step Script", "Integration_Outgoing_WebHook_History_Messages_Sent_From_Process_Script": "Mensajes enviados desde el paso de respuesta del proceso", - "Integration_Outgoing_WebHook_History_Time_Ended_Or_Error": "Tiempo que terminó o error", + "Integration_Outgoing_WebHook_History_Time_Ended_Or_Error": "Hora de finalización o error", "Integration_Outgoing_WebHook_History_Time_Triggered": "Integración de tiempo activada", "Integration_Outgoing_WebHook_History_Trigger_Step": "Último paso de activación", - "Integration_Outgoing_WebHook_No_History": "Esta integración saliente de webhook aún no tiene ningún historial registrado.", + "Integration_Outgoing_WebHook_No_History": "Esta integración de webhook saliente aún no tiene ningún historial registrado.", "Integration_Retry_Count": "Reintentar recuento", - "Integration_Retry_Count_Description": "¿Cuántas veces debería intentarse la integración si falla la llamada a la url?", + "Integration_Retry_Count_Description": "¿Cuántas veces se debe intentar la integración si falla la llamada a la URL?", "Integration_Retry_Delay": "Delay del reintento", "Integration_Retry_Delay_Description": "¿Qué algoritmo de retraso debería usar el reintento? 10 ^ xo 2 ^ xo x * 2", "Integration_Retry_Failed_Url_Calls": "Reintentar llamadas de URL fallidas", - "Integration_Retry_Failed_Url_Calls_Description": "¿Debe la integración intentar un tiempo razonable si falla la llamada a la url?", + "Integration_Retry_Failed_Url_Calls_Description": "¿Debería intentar la integración una cantidad de tiempo razonable si falla la llamada a la URL?", "Integration_Run_When_Message_Is_Edited": "Ejecutar en ediciones", "Integration_Run_When_Message_Is_Edited_Description": "¿Debería ejecutarse la integración cuando se edite el mensaje? Al establecer esto en falso, la integración solo se ejecutará en nuevos mensajes.", "Integration_updated": "La Integración ha sido actualizada", "Integration_Word_Trigger_Placement": "Colocación de palabras en cualquier lugar", "Integration_Word_Trigger_Placement_Description": "¿Debería activarse la Palabra cuando se coloca en cualquier lugar de la oración que no sea el principio?", "Integrations": "Integraciones", - "Integrations_for_all_channels": "Introduzca all_public_channels para escuchar en todos los canales públicos, all_private_groups para escuchar en todos los grupos privados, y all_direct_messages para escuchar todos los mensajes directos.", + "Integrations_for_all_channels": "IIngrese all_public_channels para escuchar en todos los canales públicos, all_private_groups para escuchar en todos los grupos privados y all_direct_messages para escuchar todos los mensajes directos", "Integrations_Outgoing_Type_FileUploaded": "Archivo Subido", "Integrations_Outgoing_Type_RoomArchived": "Sala archivada", "Integrations_Outgoing_Type_RoomCreated": "Sala Creada (pública y privada)", @@ -2267,7 +2281,7 @@ "InternalHubot_reload": "Recargar los scripts", "InternalHubot_ScriptsToLoad": "Scripts a Cargar", "InternalHubot_ScriptsToLoad_Description": "Por favor introduzca una lista separada por comas de scripts a cargar desde su carpeta personalizada ", - "InternalHubot_Username_Description": "Este debe ser un nombre de usuario válido de un bot registrado en su servidor.", + "InternalHubot_Username_Description": "Debe ser un nombre de usuario válido de un bot registrado en su servidor.", "Invalid Canned Response": "Respuesta predefinida no válida", "Invalid_confirm_pass": "La confirmación de la contraseña no coincide con la contraseña", "Invalid_Department": "Departamento incorrecto", @@ -2290,7 +2304,7 @@ "Invitation": "Invitación", "Invitation_Email_Description": "Es posible utilizar los siguientes marcadores:
      • [email] para el correo electrónico del destinatario.
      • [Site_Name] y [Site_URL] para el nombre de la aplicación y la URL, respectivamente.
      ", "Invitation_HTML": "HTML de la Invitación", - "Invitation_HTML_Default": "

      Se le ha invitado a [Site_Name]

      Ir a [Site_URL] y probar la mejor solución de chat de código abierto disponibles en la actualidad!

      ", + "Invitation_HTML_Default": "

      Se le ha invitado a [Site_Name]

      Ir a [Site_URL]y probar la mejor solución de chat de código abierto disponibles en la actualidad!

      ", "Invitation_Subject": "Asunto de la Invitación", "Invitation_Subject_Default": "Se le ha invitado a [Site_Name]", "Invite": "Invitación", @@ -2311,7 +2325,7 @@ "IRC_Federation_Disabled": "La Federación IRC está desactivada.", "IRC_Hostname": "El servidor de host IRC para conectarse.", "IRC_Login_Fail": "Salida después de una conexión fallida al servidor IRC.", - "IRC_Login_Success": "Salida después de una conexión exitosa al servidor IRC.", + "IRC_Login_Success": "Salida tras una conexión exitosa al servidor IRC.", "IRC_Message_Cache_Size": "El límite de caché para el manejo de mensajes salientes.", "IRC_Port": "El puerto al que enlazar en el servidor host IRC.", "IRC_Private_Message": "Salida del comando PRIVMSG.", @@ -2337,12 +2351,14 @@ "Jitsi_Limit_Token_To_Room": "Limitar token a la Room Jitsi", "Job_Title": "Título Profesional", "join": "Unirse", + "Join_call": "Unirse a la llamada", "Join_audio_call": "Unirse a la llamada", "Join_Chat": "Unirse al chat", "Join_default_channels": "Unirse a los canales predeterminados", "Join_the_Community": "Únete a la Comunidad", "Join_the_given_channel": "Unirse al canal dado", "Join_video_call": "Unirse a la video llamada", + "Join_my_room_to_start_the_video_call": "Únete a mi sala para iniciar la videollamada", "join-without-join-code": "Únete sin el código de unión", "join-without-join-code_description": "Permiso para eludir el código de unión en canales con código de unión activado", "Joined": "Unido", @@ -2371,7 +2387,7 @@ "Keyboard_Shortcuts_Mark_all_as_read": "Marcar todos los mensajes (en todos los canales) como leídos", "Keyboard_Shortcuts_Move_To_Beginning_Of_Message": "Mover al comienzo del mensaje", "Keyboard_Shortcuts_Move_To_End_Of_Message": "Mover al final del mensaje", - "Keyboard_Shortcuts_New_Line_In_Message": "Nueva línea en el mensaje de redacción de mensaje", + "Keyboard_Shortcuts_New_Line_In_Message": "Nueva línea en la entrada de redacción del mensaje", "Keyboard_Shortcuts_Open_Channel_Slash_User_Search": "Abrir canal/búsqueda de usuario", "Keyboard_Shortcuts_Title": "Atajos de teclado", "Knowledge_Base": "Base de conocimiento", @@ -2457,7 +2473,7 @@ "LDAP_Authentication_UserDN": "DN de usuario", "LDAP_Authentication_UserDN_Description": "El usuario LDAP que realiza búsquedas de usuario para autenticar a otros usuarios cuando inician sesión en.
      Esta es normalmente una cuenta de servicio creado específicamente para integraciones de terceros. Utilizar un nombre completo, como `cn = Administrador, cn = Users, dc = ejemplo, dc = com`.", "LDAP_Avatar_Field": "Campo de avatar de usuario", - "LDAP_Avatar_Field_Description": "Qué campo se utilizará como *avatar* para los usuarios. Déjelo en blanco para usar `thumbnailPhoto` primero y` jpegPhoto` como respaldo.", + "LDAP_Avatar_Field_Description": "Qué campo se utilizará como * avatar * para los usuarios. Déjelo en blanco para usar `thumbnailPhoto` primero y` jpegPhoto` como respaldo.", "LDAP_Background_Sync": "Sincronización de fondo", "LDAP_Background_Sync_Avatars": "Sincronización de fondo de avatar", "LDAP_Background_Sync_Avatars_Description": "Habilite un proceso en segundo plano separado para sincronizar los avatares de los usuarios.", @@ -2469,12 +2485,12 @@ "LDAP_Background_Sync_Keep_Existant_Users_Updated": "Actualización de sincronización de fondo de usuarios existentes", "LDAP_Background_Sync_Keep_Existant_Users_Updated_Description": "Sincronizará el avatar, los campos, el nombre de usuario, etc. (según su configuración) de todos los usuarios ya importados de LDAP en cada ** Intervalo de sincronización **", "LDAP_BaseDN": "Base DN", - "LDAP_BaseDN_Description": "El nombre distinguido (DN) completamente calificado de un subárbol LDAP que desea buscar usuarios y grupos. Puede agregar tantos como desee; sin embargo, cada grupo debe estar definido en la misma base de dominio que los usuarios que le pertenecen. Ejemplo: `ou = Usuarios + ou = Proyectos, dc = Ejemplo, dc = com`. Si especifica grupos de usuarios restringidos, solo los usuarios que pertenecen a esos grupos estarán dentro del alcance. Le recomendamos que especifique el nivel superior de su árbol de directorios LDAP como base de su dominio y utilice el filtro de búsqueda para controlar el acceso.", - "LDAP_CA_Cert": "CA Cert", + "LDAP_BaseDN_Description": "El nombre distinguido (DN) completo de un subárbol LDAP en el que desea buscar usuarios y grupos. Puede agregar tantos como desee; sin embargo, cada grupo debe estar definido en la misma base de dominio que los usuarios que pertenecen a él. Ejemplo: `ou = Usuarios + ou = Proyectos, dc = Ejemplo, dc = com`. Si especifica grupos de usuarios restringidos, solo los usuarios que pertenecen a esos grupos estarán dentro del alcance. Le recomendamos que especifique el nivel superior de su árbol de directorios LDAP como base de su dominio y utilice el filtro de búsqueda para controlar el acceso.", + "LDAP_CA_Cert": "Certificado de CA", "LDAP_Connect_Timeout": "Tiempo de espera de conexión(ms)", "LDAP_DataSync_AutoLogout": "Usuarios desactivados de cierre de sesión automático", "LDAP_Default_Domain": "Dominio Predeterminado", - "LDAP_Default_Domain_Description": "Si se proporciona, el Dominio predeterminado se usará para crear un correo electrónico exclusivo para los usuarios donde el correo electrónico no se haya importado de LDAP. El correo electrónico se montará como `username @ default_domain` o` unique_id @ default_domain`.
      Ejemplo: `rocket.chat`", + "LDAP_Default_Domain_Description": "si se proporciona, el dominio predeterminado se utilizará para crear un correo electrónico único para los usuarios en los que el correo electrónico no se importó desde LDAP. El correo electrónico se montará como `username @ default_domain` o` unique_id @ default_domain`.
      Ejemplo: `rocket.chat`", "LDAP_Enable": "Habilitar", "LDAP_Enable_Description": "Intentar utilizar LDAP como método de autenticación ", "LDAP_Enable_LDAP_Groups_To_RC_Teams": "Habilite el mapeo del equipo de LDAP a Rocket.Chat", @@ -2482,7 +2498,7 @@ "LDAP_Encryption_Description": "Metodo de cifrado usado para la comunicación segura hacia el servidor LDAP. Algunos ejemplos 'sin cifrado', 'SSL/LDAPS (cifrado desde el inicio), y 'StartTLS' ( actualizar a comunicaciónes cifradas una vez conectado).", "LDAP_Find_User_After_Login": "Encontrar usuario después de iniciar sesión", "LDAP_Find_User_After_Login_Description": "Realizará una búsqueda del DN del usuario después de la vinculación para garantizar que la vinculación se realizó correctamente y evitará el inicio de sesión con contraseñas vacías cuando lo permita la configuración de AD.", - "LDAP_Group_Filter_Enable": "Habilitar filtro de grupo de usuarios LDAP", + "LDAP_Group_Filter_Enable": "Habilitar el filtro de grupo de usuarios LDAP", "LDAP_Group_Filter_Enable_Description": "Restringir el acceso a los usuarios en un grupo LDAP
      Útil para permitir que los servidores OpenLDAP sin un filtro * memberOf * restrinjan el acceso por grupos", "LDAP_Group_Filter_Group_Id_Attribute": "Atributo de ID de grupo", "LDAP_Group_Filter_Group_Id_Attribute_Description": "Por ejemplo. *OpenLDAP:* cn", @@ -2501,10 +2517,10 @@ "LDAP_Idle_Timeout_Description": "Cuántos milisegundos esperan después de la última operación LDAP hasta que se cierra la conexión. (Cada operación abrirá una nueva conexión)", "LDAP_Import_Users_Description": "Su verdadero proceso de sincronización importará a todos los usuarios de LDAP
      *¡Atención!* Especifique el filtro de búsqueda para no importar el exceso de usuarios.", "LDAP_Internal_Log_Level": "Nivel de registro interno", - "LDAP_Login_Fallback": "Login Fallback", + "LDAP_Login_Fallback": "Respaldo de inicio de sesión", "LDAP_Login_Fallback_Description": "Si el inicio de sesión en LDAP no es exitoso, intente iniciar sesión en el sistema predeterminado/cuenta local. Ayuda cuando el LDAP está inactivo por alguna razón.", "LDAP_Merge_Existing_Users": "Fusionar usuarios existentes", - "LDAP_Merge_Existing_Users_Description": "* ¡Precaución! * Al importar un usuario de LDAP y ya existe un usuario con el mismo nombre de usuario, la información y la contraseña de LDAP se establecerán en el usuario existente.", + "LDAP_Merge_Existing_Users_Description": "* ¡Precaución! * Cuando se importa un usuario de LDAP y ya existe un usuario con el mismo nombre de usuario, la información y la contraseña de LDAP se establecerán en el usuario existente.", "LDAP_Port": "Puerto", "LDAP_Port_Description": "Puerto para acceder a LDAP. Por ejemplo. `389` o `636` para LDAPS", "LDAP_Prevent_Username_Changes": "Impedir que los usuarios de LDAP cambien su nombre de usuario de Rocket.Chat", @@ -2516,13 +2532,13 @@ "LDAP_Search_Page_Size": "Tamaño de página de búsqueda", "LDAP_Search_Page_Size_Description": "El número máximo de entradas que cada página de resultados volverá a procesarse", "LDAP_Search_Size_Limit": "Límite de tamaño de búsqueda", - "LDAP_Search_Size_Limit_Description": "El número máximo de entradas para devolver.
      **Atención** Este número debe ser mayor que **Tamaño de página de búsqueda**", + "LDAP_Search_Size_Limit_Description": "El número máximo de entradas para devolver.
      ** Atención ** Este número debe ser mayor que ** Tamaño de la página de búsqueda **", "LDAP_Sync_Custom_Fields": "Sincronizar campos personalizados", "LDAP_CustomFieldMap": "Asignación de campos personalizados", "LDAP_Sync_AutoLogout_Enabled": "Habilitar cierre de sesión automático", "LDAP_Sync_AutoLogout_Interval": "Intervalo de cierre de sesión automático", "LDAP_Sync_Now": "Sincronizar ahora", - "LDAP_Sync_Now_Description": "Esto iniciará una operación ** Sincronización en segundo plano ** ahora, sin esperar a la próxima sincronización programada. \nEsta acción es asincrónica; consulte los registros para obtener más información.", + "LDAP_Sync_Now_Description": "Esto iniciará una operación ** Sincronización en segundo plano ** ahora, sin esperar a la próxima sincronización programada.\nEsta acción es asincrónica; consulte los registros para obtener más información.", "LDAP_Sync_User_Active_State": "Sincronizar el estado de actividad del usuario", "LDAP_Sync_User_Active_State_Both": "Habilitar y deshabilitar usuarios", "LDAP_Sync_User_Active_State_Description": "Determine si los usuarios deben estar habilitados o deshabilitados en Rocket.Chat según el estado de LDAP. El atributo 'pwdAccountLockedTime' se utilizará para determinar si el usuario está deshabilitado.", @@ -2550,15 +2566,19 @@ "LDAP_Sync_User_Data_Roles_Filter_Description": "El filtro de búsqueda LDAP que se usa para verificar si un usuario está en un grupo.", "LDAP_Sync_User_Data_RolesMap": "Mapa de grupo de datos de usuario", "LDAP_Sync_User_Data_RolesMap_Description": "Mapea los grupos LDAP a los roles de usuario de Rocket.Chat
      Como ejemplo, `{\"rocket-admin\":\"admin\", \"tech-support\":\"support\"}` mapeará el grupo LDAP de rocket-admin al rol de \"admin\" de Rocket.", + "LDAP_Teams_BaseDN": "Equipos LDAP BaseDN", + "LDAP_Teams_BaseDN_Description": "LDAP BaseDN utilizado para buscar equipos de usuarios.", + "LDAP_Teams_Name_Field": "Atributo de nombre de equipo LDAP", + "LDAP_Teams_Name_Field_Description": "El atributo LDAP que Rocket.Chat debe usar para cargar el nombre del equipo. Puede especificar más de un posible nombre de atributo si los separa con una coma.", "LDAP_Timeout": "Tiempo de espera (ms)", "LDAP_Timeout_Description": "Cuántos milisegundos esperan un resultado de búsqueda antes de devolver un error", "LDAP_Unique_Identifier_Field": "Campo Identificador Único ", - "LDAP_Unique_Identifier_Field_Description": "Qué campo se utilizará para vincular al usuario LDAP y el usuario Rocket.Chat. Puede informar a varios valores separados por una coma para tratar de obtener el valor del registro de LDAP.
      El valor por defecto es `objectGUID, IBM-entryUUID, GUID, dominoUNID, nsuniqueid, uidNumber`", + "LDAP_Unique_Identifier_Field_Description": "Qué campo se utilizará para vincular el usuario LDAP y el usuario de Rocket.Chat. Puede informar varios valores separados por comas para intentar obtener el valor del registro LDAP.
      El valor predeterminado es `objectGUID, ibm-entryUUID, GUID, dominoUNID, nsuniqueId, uidNumber`", "LDAP_User_Found": "Usuario LDAP encontrado", "LDAP_User_Search_AttributesToQuery": "Atributos para consulta", "LDAP_User_Search_AttributesToQuery_Description": "Especifique qué atributos deben devolverse en las consultas LDAP, separándolos con comas. Valores predeterminados para todo. `*` representa todos los atributos regulares y `+` representa todos los atributos operativos. Asegúrese de incluir todos los atributos que utilizan todas las opciones de sincronización de Rocket.Chat.", "LDAP_User_Search_Field": "Campo de búsqueda", - "LDAP_User_Search_Field_Description": "El atributo LDAP que identifica al usuario LDAP que intenta la autenticación. Este campo debe ser \"sAMAccountName\" para la mayoría de las instalaciones de Active Directory, pero puede ser \"uid\" para otras soluciones LDAP, como OpenLDAP. Puede usar `mail` para identificar a los usuarios por correo electrónico o cualquier atributo que desee.
      Puede usar varios valores separados por comas para permitir que los usuarios inicien sesión usando múltiples identificadores como nombre de usuario o correo electrónico.", + "LDAP_User_Search_Field_Description": "El atributo LDAP que identifica al usuario LDAP que intenta la autenticación. Este campo debe ser \"sAMAccountName\" para la mayoría de las instalaciones de Active Directory, pero puede ser \"uid\" para otras soluciones LDAP, como OpenLDAP. Puede usar `mail` para identificar a los usuarios por correo electrónico o cualquier atributo que desee.
      Puede usar varios valores separados por comas para permitir que los usuarios inicien sesión usando múltiples identificadores como nombre de usuario o correo electrónico.", "LDAP_User_Search_Filter": "Filtro", "LDAP_User_Search_Filter_Description": "Si se les permitirá especificados, sólo los usuarios que coincidan con este filtro para iniciar sesión. Si no se especifica ningún filtro, todos los usuarios dentro del alcance de la base de dominio especificado serán capaces de iniciar sesión.
      Por ejemplo, para Active Directory `memberOf = cn = ROCKET_CHAT, ou = Groups` general.
      Por ejemplo, para OpenLDAP (búsqueda de coincidencia de extensible) `ou: dn: = ROCKET_CHAT`.", "LDAP_User_Search_Scope": "Alcance", @@ -2571,10 +2591,10 @@ "Lead_capture_phone_regex": "Regex de teléfono de captura clientes potenciales", "Leave": "Salir", "Leave_a_comment": "Dejar un comentario", - "Leave_Group_Warning": "¿Seguro que quieres dejar el grupo \"%s\"?", + "Leave_Group_Warning": "¿Estás seguro de que quieres dejar el grupo \"% s\"?", "Leave_Livechat_Warning": "¿Seguro que quieres salir de la sala Omnichannel con \"%s\"?", "Leave_Private_Warning": "¿Seguro que quieres salir de la discusión con \"%s\"?", - "Leave_room": "Salir de la sala", + "Leave_room": "Salir ", "Leave_Room_Warning": "¿Seguro que quieres salir de la sala \"%s\"?", "Leave_the_current_channel": "Salir del canal actual", "Leave_the_description_field_blank_if_you_dont_want_to_show_the_role": "Deje el campo de descripción en blanco si no desea mostrar el rol", @@ -2616,7 +2636,7 @@ "Livechat_Facebook_API_Key": "Clave API de Facebook", "Livechat_Facebook_API_Secret": "API Secreto Facebook", "Livechat_Facebook_Enabled": "Integración de Facebook habilitada", - "Livechat_forward_open_chats": "Reenviar charlas abiertas", + "Livechat_forward_open_chats": "Reenviar chats abiertos", "Livechat_forward_open_chats_timeout": "Tiempo de espera (en segundos) para reenviar los chats", "Livechat_guest_count": "Contador de invitados", "Livechat_Inquiry_Already_Taken": "Solicitud de Omnichannel ya atendida", @@ -2627,10 +2647,11 @@ "Livechat_Managers": "Administradores", "Livechat_max_queue_wait_time_action": "Cómo manejar los chats en cola cuando se alcanza el tiempo máximo de espera", "Livechat_maximum_queue_wait_time": "Tiempo máximo de espera en cola", + "Livechat_maximum_queue_wait_time_description": "Tiempo máximo (en minutos) para mantener los chats en cola. -1 significa ilimitado", "Livechat_message_character_limit": "Límite de caracteres del mensaje de LiveChat", "Livechat_monitors": "Monitores de Omnichannel", "Livechat_Monitors": "Monitores", - "Livechat_offline": "LiveChat desconectado", + "Livechat_offline": "Omnichannel desconectado", "Livechat_offline_message_sent": "Mensaje de Livechat enviado sin conexión", "Livechat_OfflineMessageToChannel_enabled": "Enviar mensajes sin conexión de LiveChat a un canal", "Omnichannel_on_hold_chat_resumed": "Reanudación del chat en espera: __comment__", @@ -2639,12 +2660,12 @@ "Omnichannel_On_Hold_due_to_inactivity": "El chat se puso automáticamente en espera porque no hemos recibido ninguna respuesta de __guest__ en __timeout__ segundos", "Omnichannel_On_Hold_manually": "El chat fue puesto manualmente en espera por __user__", "Omnichannel_onHold_Chat": "Poner chat en espera", - "Livechat_online": "LiveChat en línea", + "Livechat_online": "Omnichannel en línea", "Omnichannel_placed_chat_on_hold": "Chat en espera: __comment__", "Livechat_Queue": "Cola de Omnichannel", "Livechat_registration_form": "Formulario de Registro", "Livechat_registration_form_message": "Mensaje del formulario de registro", - "Livechat_room_count": "Recuento de salas Omnichannel", + "Livechat_room_count": "Recuento de Room de Omnichannel ", "Livechat_Routing_Method": "Método de enrutamiento del Omnichannel", "Livechat_status": "Estado de LiveChat", "Livechat_Take_Confirm": "¿Quiere aceptar este cliente?", @@ -2653,7 +2674,7 @@ "Livechat_transcript_already_requested_warning": "La transcripción de este chat ya ha sido solicitada y será enviada tan pronto como la conversación termine.", "Livechat_transcript_has_been_requested": "Se ha solicitado la transcripción del chat.", "Livechat_transcript_request_has_been_canceled": "Se canceló la solicitud de transcripción del chat.", - "Livechat_transcript_sent": "Se ha enviado una transcripción de LiveChat", + "Livechat_transcript_sent": "Se ha enviado una transcripción de Omnichannel", "Livechat_transfer_return_to_the_queue": "__from__ devolvió el chat a la cola", "Livechat_transfer_to_agent": "__from__ transfirió el chat a __to__", "Livechat_transfer_to_agent_with_a_comment": "__from__ transfirió el chat a __to__ con un comentario: __comment__", @@ -2661,7 +2682,8 @@ "Livechat_transfer_to_department_with_a_comment": "__from__ transfirió el chat a el departamento __to__ con un comentario: __comment__", "Livechat_Triggers": "Activadores LiveChat", "Livechat_user_sent_chat_transcript_to_visitor": "__agent__ envió la transcripción del chat a __guest__", - "Livechat_Users": "Usuarios de LiveChat", + "Livechat_Users": "Usuarios de Omnichannel", + "Livechat_Calls": "Llamadas Livechat", "Livechat_visitor_email_and_transcript_email_do_not_match": "El correo electrónico del visitante y el de la transcripción no coinciden", "Livechat_visitor_transcript_request": "__guest__ pidió la transcripción del chat", "LiveStream & Broadcasting": "Transmisión en directo y transmisión", @@ -2688,7 +2710,7 @@ "Local_Time": "Hora local", "Local_Timezone": "Zona horaria local", "Local_Time_time": "Hora local: __time__", - "Localization": "Idioma", + "Localization": "Localización", "Location": "Ubicación", "Log_Exceptions_to_Channel": "Registrar excepciones al canal", "Log_Exceptions_to_Channel_Description": "Un canal que recibirá todas las excepciones capturadas. Déjalo vacío para ignorar las excepciones.", @@ -2720,17 +2742,17 @@ "Longest_reaction_time": "Tiempo de reacción más largo", "Longest_response_time": "Tiempo de respuesta más largo", "Looked_for": "Buscado", - "Mail_Message_Invalid_emails": "Ha proporcionado uno o mas correos electronicos invalidos %s", + "Mail_Message_Invalid_emails": "Ha proporcionado uno o más correos electrónicos inválidos %s", "Mail_Message_Missing_subject": "Debes proporcionar un asunto del correo electrónico.", "Mail_Message_Missing_to": "Debe seleccionar uno o más usuarios o proporcionar una o más direcciones de correo electrónico, separadas por comas.", "Mail_Message_No_messages_selected_select_all": "No ha seleccionado ningún mensaje", "Mail_Messages": "Mensajes de correo", "Mail_Messages_Instructions": "Seleccione los mensajes que desea enviar por correo electrónico haciendo clic en los mensajes", - "Mail_Messages_Subject": "Aquí hay una parte seleccionada de %s mensajes", + "Mail_Messages_Subject": "Aquí hay una parte seleccionada de% s mensajes", "mail-messages": "Mensajes de correo", "mail-messages_description": "Permiso para usar la opción de mensajes de correo", "Mailer": "Remitente", - "Mailer_body_tags": "Debe utilizar [unsubscribe] para el enlace de anulación de la suscripción.
      Es posible utilizar [name], [fname], [lname] para el nombre completo del usuario, nombre o apellido, respectivamente.
      Es posible utilizar [email] para el correo electrónico del usuario.", + "Mailer_body_tags": "Usted debe usar [unsubscribe] para el enlace de cancelación de suscripción.
      Puede usar [name], [fname], [lname] para el nombre completo, nombre o apellido del usuario, respectivamente.
      Puede usar [email] para el correo electrónico del usuario.", "Mailing": "Envío", "Make_Admin": "Hacer Administrador", "Make_sure_you_have_a_copy_of_your_codes_1": "Asegúrese de tener una copia de sus códigos:", @@ -2751,9 +2773,9 @@ "manage-integrations_description": "Permiso para administrar las integraciones del servidor", "manage-livechat-agents": "Administrar agentes de Omnichannel", "manage-livechat-agents_description": "Permiso para gestionar agentes Omnichannel", - "manage-livechat-departments": "Administrar departamentos de Omnichannel", + "manage-livechat-departments": "Administrar departamentos de Omnichannel", "manage-livechat-departments_description": "Permiso para gestionar departamentos Omnichannel", - "manage-livechat-managers": "Administrar administradores de Omnichannel", + "manage-livechat-managers": "Administrar administradores de Omnichannel", "manage-livechat-managers_description": "Permiso para gestionar gestores Omnichannel", "manage-oauth-apps": "Administrar aplicaciones Oauth", "manage-oauth-apps_description": "Permiso para administrar las aplicaciones Oauth del servidor", @@ -2776,7 +2798,7 @@ "Manager_removed": "Administrador eliminado", "Managers": "Administradores", "Managing_assets": "La gestión de administradores", - "Managing_integrations": "La gestión de integraciones", + "Managing_integrations": "Gestión de integraciones", "Manual_Selection": "Selección manual", "Manufacturing": "Fabricación", "MapView_Enabled": "Habilitar Mapview", @@ -2795,9 +2817,10 @@ "Markdown_Marked_SmartLists": "Habilitar listas inteligentes marcadas", "Markdown_Marked_Smartypants": "Habilitar Smartypants marcados", "Markdown_Marked_Tables": "Habilitar tablas marcadas", - "Markdown_Parser": "Markdown Parser", - "Markdown_SupportSchemesForLink": "Planes de apoyo de rebajas de Enlace", + "Markdown_Parser": "Analizador de Markdown", + "Markdown_SupportSchemesForLink": "Esquemas de soporte de Markdown para enlace", "Markdown_SupportSchemesForLink_Description": "Lista separada por comas de los esquemas permitidos", + "Marketplace": "Mercado", "Marketplace_view_marketplace": "Ver Marketplace", "MAU_value": "MAU __value__", "Max_length_is": "La longitud máxima es %s", @@ -2828,7 +2851,7 @@ "Message_AllowDeleting": "Permitir la eliminación de mensajes", "Message_AllowDeleting_BlockDeleteInMinutes": "Bloquear la Eliminación de Mensajes Despues de (n) Minutos", "Message_AllowDeleting_BlockDeleteInMinutes_Description": "Introduzca 0 para desactivar el bloqueo.", - "Message_AllowDirectMessagesToYourself": "Permitir mensajes directos del usuario a usted mismo", + "Message_AllowDirectMessagesToYourself": "Permitir que los usuarios se envíen mensajes directos a usted mismo", "Message_AllowEditing": "Permitir la edición de mensajes", "Message_AllowEditing_BlockEditInMinutes": "Bloquear la Edicion de Mensajes Despues de (n) Minutos", "Message_AllowEditing_BlockEditInMinutesDescription": "Ingrese 0 para deshabilitar el bloqueo.", @@ -2841,7 +2864,7 @@ "Message_AlwaysSearchRegExp": "Siempre buscar utilizando RegExp", "Message_AlwaysSearchRegExp_Description": "Recomendamos establecer `TRUE si el idioma no es compatible con la búsqueda de texto MongoDB .", "Message_Attachments": "Adjuntos de mensajes", - "Message_Attachments_GroupAttach": "Botones de archivos adjuntos", + "Message_Attachments_GroupAttach": "Grupo de botones de archivos adjuntos", "Message_Attachments_GroupAttachDescription": "Esto agrupa los iconos debajo de un menú expandible. Toma menos espacio de pantalla.", "Message_Attachments_Thumbnails_Enabled": "Habilite las miniaturas de imágenes para ahorrar ancho de banda", "Message_Attachments_Thumbnails_Width": "Ancho máximo de la miniatura (en píxeles)", @@ -2897,7 +2920,7 @@ "Message_HideType_room_unarchived": "Ocultar mensajes de \"Sala no archivada\"", "Message_HideType_ru": "Ocultar mensajes de \"Usuario borrado\"", "Message_HideType_subscription_role_added": "Ocultar mensajes de \"Rol establecido\"", - "Message_HideType_subscription_role_removed": "Ocultar mensajes de \"Rol ya no definido\"", + "Message_HideType_subscription_role_removed": "Ocultar mensajes de \"Rol no definido\"", "Message_HideType_uj": "Ocultar mensajes de \"Usuario unido\"", "Message_HideType_ul": "Ocultar mensajes de \"Salida de usuario\"", "Message_HideType_ut": "Ocultar mensajes de \"El usuario se unió a la conversación\"", @@ -2930,7 +2953,7 @@ "Message_too_long": "Mensaje demasiado largo", "Message_UserId": "ID del usuario", "Message_VideoRecorderEnabled": "Activar grabador de video", - "Message_VideoRecorderEnabledDescription": "Requer que los archivos de tipo 'video/webm' sean un tipo de medio aceptado en la configuracion 'Subida ficheros'.", + "Message_VideoRecorderEnabledDescription": "Requiere que los archivos 'video / webm' sean un tipo de medio aceptado dentro de la configuración de 'Carga de archivos'.", "Message_view_mode_info": "Esto cambia la cantidad de mensajes espacio ocupan en la pantalla.", "MessageBox_view_mode": "Modo de visualización del panel de mensajes", "messages": "mensajes", @@ -2949,7 +2972,7 @@ "meteor_status_connecting": "Conectando...", "meteor_status_failed": "La conexión con el servidor falló", "meteor_status_offline": "Modo fuera de línea.", - "meteor_status_reconnect_in": "reintentando automáticamente en un segundo...", + "meteor_status_reconnect_in": "intentando de nuevo en un segundo ...", "meteor_status_reconnect_in_plural": "reintentando automáticamente en __count__ segundos...", "meteor_status_try_now_offline": "Conectar de nuevo", "meteor_status_try_now_waiting": "Intentar ahora", @@ -2961,16 +2984,18 @@ "minute": "minuto", "minutes": "minutos", "Mobex_sms_gateway_address": "Dirección Mobex SMS Gateway", - "Mobex_sms_gateway_address_desc": "IP o Host de su servicio Mobex con un puerto específico. Por ejemplo, `http://192.168.1.1:1401` o `https://www.example.com:1401`", - "Mobex_sms_gateway_from_number": "Desde", - "Mobex_sms_gateway_from_number_desc": "Direccióm/número de teléfono de origen al enviar un nuevo SMS al cliente de Omnichannel ", + "Mobex_sms_gateway_address_desc": "IP o Host de su servicio Mobex con puerto especificado. P.ej. `http: //192.168.1.1: 1401` o` https: //www.example.com: 1401`", + "Mobex_sms_gateway_from_number": "De", + "Mobex_sms_gateway_from_number_desc": "Dirección /número de teléfono de origen al enviar un nuevo SMS al cliente de Omnichannel", "Mobex_sms_gateway_from_numbers_list": "Lista de números desde los que enviar SMS", "Mobex_sms_gateway_from_numbers_list_desc": "Lista de números separados por comas para usar en el envío de mensajes nuevos, por ejemplo 123456789, 123456788, 123456888", "Mobex_sms_gateway_password": "Contraseña", - "Mobex_sms_gateway_restful_address": "Dirección Mobex SMS REST API", + "Mobex_sms_gateway_restful_address": "Dirección de la API REST de SMS de Mobex", "Mobex_sms_gateway_restful_address_desc": "IP o Host de su Mobex REST API. Por ejemplo, `http://192.168.1.1:8080` o `https://www.example.com:8080`", "Mobex_sms_gateway_username": "Nombre de usuario", "Mobile": "Móvil", + "mobile-download-file": "Permitir la descarga de archivos en dispositivos móviles", + "mobile-upload-file": "Permitir la carga de archivos en dispositivos móviles", "Mobile_Push_Notifications_Default_Alert": "Alerta predeterminada de notificaciones móviles", "Monday": "Lunes", "Mongo_storageEngine": "Motor de almacenamiento Mongo", @@ -2989,17 +3014,19 @@ "More_groups": "Más grupos privados", "More_unreads": "Más no leídos", "Most_popular_channels_top_5": "Canales más populares (Top 5)", - "Move_beginning_message": "`%s` - Mover al comienzo del mensaje", + "Move_beginning_message": "`%s` - Ir al principio del mensaje", "Move_end_message": "`%s` - Mover al final del mensaje", "Move_queue": "Mover a la cola", "Msgs": "Mensajes", "multi": "multi", "multi_line": "línea múltiple", + "Mute": "Silenciar", "Mute_all_notifications": "Silenciar todas las notificaciones", "Mute_Focused_Conversations": "Silenciar conversaciones enfocadas", "Mute_Group_Mentions": "Silenciar @all i @here menciones", "Mute_someone_in_room": "Silenciar a alguien en la sala", "Mute_user": "Silenciar usuario", + "Mute_microphone": "Silenciar micrófono", "mute-user": "Usuario silenciado", "mute-user_description": "Permiso para silenciar a otros usuarios en el mismo canal", "Muted": "Silenciado", @@ -3041,7 +3068,7 @@ "New_Password_Placeholder": "Por favor ingrese nueva contraseña ...", "New_Priority": "Nueva prioridad", "New_role": "Nuevo rol", - "New_Room_Notification": "Notificación de Nueva Sala", + "New_Room_Notification": "Notificación de Nueva Room", "New_Tag": "Nueva etiqueta", "New_Trigger": "Nuevo disparador", "New_Unit": "Nueva unidad", @@ -3110,9 +3137,9 @@ "Notification_Desktop_Default_For": "Mostrar notificaciones de escritorio para", "Notification_Push_Default_For": "Notificaciones móviles push para", "Notification_RequireInteraction": "Requerir interacción para descartar la notificación de escritorio", - "Notification_RequireInteraction_Description": "Funciona solo con versiones de Google Chrome > 50. Utilice el parámetro Utilizes the requireInteraction para mostrar la notificación de escritorio de forma indefinida hasta que el usuario interactúe con ella.", + "Notification_RequireInteraction_Description": "Funciona solo con las versiones del navegador Chrome> 50. Utiliza el parámetro requireInteraction para mostrar la notificación de escritorio de forma indefinida hasta que el usuario interactúe con ella.", "Notifications": "Notificaciones", - "Notifications_Max_Room_Members": "Número máximo de miembros de la sala antes de deshabilitar todas las notificaciones de mensajes", + "Notifications_Max_Room_Members": "Número máximo de miembros de la Room antes de deshabilitar todas las notificaciones de mensajes", "Notifications_Max_Room_Members_Description": "Número máximo de miembros en la sala cuando se deshabilitan las notificaciones de todos los mensajes. Los usuarios aún pueden cambiar la configuración de cada habitación para recibir todas las notificaciones de forma individual. (0 para deshabilitar)", "Notifications_Muted_Description": "Si elige silenciar todo, no verá el resaltado de la sala en la lista cuando haya mensajes nuevos, a excepción de las menciones. Las notificaciones de silenciamiento anularán las configuraciones de notificaciones.", "Notifications_Preferences": "Preferencias de notificaciones", @@ -3134,7 +3161,7 @@ "Number_of_federated_users": "Número de usuarios federados", "Number_of_messages": "Número de mensajes", "Number_of_most_recent_chats_estimate_wait_time": "Número de chats recientes para calcular el tiempo de espera estimado", - "Number_of_most_recent_chats_estimate_wait_time_description": "Este valor define el número de las últimas salas reservadas que serán utilizadas para calcular los tiempos de espera en cola. ", + "Number_of_most_recent_chats_estimate_wait_time_description": "Este número define el número de las últimas salas atendidas que se utilizarán para calcular los tiempos de espera de la cola.", "Number_of_users_autocomplete_suggestions": "Número de sugerencias de autocompletar de los usuarios.", "OAuth Apps": "Aplicaciones OAuth", "OAuth_Application": "Aplicación de OAuth", @@ -3150,14 +3177,14 @@ "Offline_DM_Email": "Se le ha contactado directamente por __user__", "Offline_Email_Subject_Description": "Puede usar los siguientes marcadores de posición:
      • [Site_Name], [Site_URL], [User] y [Room] para el nombre de la aplicación, la URL, el nombre de usuario y el nombre de la habitación, respectivamente.
      ", "Offline_form": "Formulario fuera de línea", - "Offline_form_unavailable_message": "Mensaje a enviar mientras está fuera de línea", + "Offline_form_unavailable_message": "Mensaje de formulario sin conexión no disponible", "Offline_Link_Message": "IR AL MENSAJE", "Offline_Mention_All_Email": "Mencione todo el asunto del correo electrónico", - "Offline_Mention_Email": "Usted ha sido mencionado por __user__ en #__room__", + "Offline_Mention_Email": "Mencione el asunto del correo electrónico", "Offline_message": "Mensaje fuera de línea", "Offline_Message": "Mensaje fuera de línea", "Offline_Message_Use_DeepLink": "Usar formato de URL de enlace profundo", - "Offline_messages": "Mensajes fuera de línea", + "Offline_messages": "Mensajes sin conexión", "Offline_success_message": "Mensaje fuera de línea correcto", "Offline_unavailable": "No disponible sin conexión", "Ok": "De acuerdo", @@ -3166,7 +3193,9 @@ "Older_than": "Más antiguo que", "Omnichannel": "Omnichannel", "Omnichannel_Directory": "Directorio de Omnichannel", - "Omnichannel_appearance": "Apariencia de LiveChat", + "Omnichannel_appearance": "Apariencia de Omnichannel", + "Omnichannel_calculate_dispatch_service_queue_statistics": "Calcular y enviar estadísticas de colas de espera Omnichannel", + "Omnichannel_calculate_dispatch_service_queue_statistics_Description": "Procesar y enviar estadísticas de la cola de espera, como la posición y el tiempo de espera estimado. Si el * canal de chat en vivo * no está en uso, se recomienda deshabilitar esta configuración y evitar que el servidor realice procesos innecesarios.", "Omnichannel_Contact_Center": "Centro de contactos de Omnichannel", "Omnichannel_contact_manager_routing": "Asignar nuevas conversaciones al administrador de contactos", "Omnichannel_contact_manager_routing_Description": "Esta configuración asigna un chat al Administrador de contactos asignado, siempre que el Administrador de contactos esté en línea cuando se inicia el chat", @@ -3191,7 +3220,7 @@ "Oops_page_not_found": "Vaya, página no encontrada", "Oops!": "Uy!", "Open": "Abierto", - "Open_channel_user_search": "`%s` - Abrir canal / búsqueda de usuario", + "Open_channel_user_search": "`%s` - Abrir channel / búsqueda de usuario", "Open_conversations": "Conversaciones abiertas", "Open_Days": "Días abiertos", "Open_days_of_the_week": "Días abiertos de la semana", @@ -3213,7 +3242,7 @@ "Organization_Type": "Tipo de Organización", "Original": "Original", "OS_Arch": "Arquitectura de SO", - "OS_Cpus": "Recuento de CPUs", + "OS_Cpus": "Recuento de CPU del SO", "OS_Freemem": "Memoria Libre del SO", "OS_Loadavg": "Promedio de Carga del SO", "OS_Platform": "Plataforma del SO", @@ -3230,7 +3259,7 @@ "Outgoing_WebHook": "WebHook saliente", "Outgoing_WebHook_Description": "Obtenga datos de Rocket.Chat en tiempo real.", "Output_format": "Formato de salida", - "Override_URL_to_which_files_are_uploaded_This_url_also_used_for_downloads_unless_a_CDN_is_given": "URL de alteración a la que se cargan los archivos. Este sitio de Internet también se utiliza para las descargas a menos de un CDN es dado", + "Override_URL_to_which_files_are_uploaded_This_url_also_used_for_downloads_unless_a_CDN_is_given": "Reemplazar la URL a la que se cargan los archivos. Esta URL también se usa para descargas a menos que se proporcione un CDN", "Page_title": "Título de la página", "Page_URL": "URL de la página", "Pages": "Páginas", @@ -3277,7 +3306,7 @@ "PiwikAnalytics_domains": "Ocultar enlaces salientes", "PiwikAnalytics_domains_Description": "En el informe \"Enlaces externos\", oculte los clics a URL de alias conocidas. Inserte un dominio por línea y no use separadores.", "PiwikAnalytics_prependDomain": "Anteponer el dominio", - "PiwikAnalytics_prependDomain_Description": "Anteponga el dominio del sitio al título de la página cuando realiza el seguimiento", + "PiwikAnalytics_prependDomain_Description": "Anteponga el dominio del sitio al título de la página cuando realice el seguimiento", "PiwikAnalytics_siteId_Description": "La Identificación del sitio a utilizar para la identificación de este sitio. Ejemplo: 17", "PiwikAnalytics_url_Description": "La url donde reside el Piwik, asegúrese de incluir la barra probando. Ejemplo: //piwik.rocket.chat/", "Placeholder_for_email_or_username_login_field": "Marcador de posición para el campo de inicio de sesión de correo electrónico o nombre de usuario", @@ -3334,7 +3363,7 @@ "Privacy_Policy": "Política de Privacidad", "Private": "Privado", "Private_Channel": "Canal Privado", - "Private_Channels": "Canales privados", + "Private_Channels": "Channels privados", "Private_Chats": "Chats privados", "Private_Group": "Grupo Privado", "Private_Groups": "Grupos Privados", @@ -3369,7 +3398,7 @@ "Purchase_for_free": "Compra GRATIS", "Purchase_for_price": "Compra por $%s", "Purchased": "Comprado", - "Push": "Push", + "Push": "Pulsar", "Push_Notifications": "Notificaciones Push", "Push_apn_cert": "Certificado de APN", "Push_apn_dev_cert": "Certificado de APN de desarrollador", @@ -3394,6 +3423,7 @@ "Query_description": "Condiciones adicionales para determinar a que usuarios enviar el correo electrónico. Los usuarios que optaron por cancelar su suscripción serán eliminados de la consulta. Debe ser JSON valido. Ejemplo: \"{\"createdAt\":{\"$gt\":{\"$date\": \"2015-01-01T00:00:00.000Z\"}}}\"", "Query_is_not_valid_JSON": "La consulta no es JSON válido", "Queue": "Cola", + "Queue_delay_timeout": "Tiempo de espera de espera de procesamiento de cola", "Queue_Time": "Tiempo de cola", "Queue_management": "Gestión de colas", "quote": "cita", @@ -3408,7 +3438,7 @@ "Reactions": "Reacciones", "Read_by": "Leído por", "Read_only": "Sólo lectura", - "Read_only_changed_successfully": "Sólo lectura cambiado con éxito", + "Read_only_changed_successfully": "Solo lectura cambiado correctamente", "Read_only_channel": "Canal de sólo lectura", "Read_only_group": "Grupo de sólo lectura", "Real_Estate": "Bienes raíces", @@ -3461,8 +3491,8 @@ "Remove_from_team": "Elimina del equipo", "Remove_last_admin": "Eliminando el último administrador", "Remove_someone_from_room": "Eliminar a alguien de la sala", - "remove-closed-livechat-room": "Quitar sala cerrada de Omnichannel", - "remove-closed-livechat-rooms": "Eliminar salas cerradas de Omnichannel", + "remove-closed-livechat-room": "Quitar Room cerrada de Omnichannel", + "remove-closed-livechat-rooms": "Eliminar Rooms cerradas de Omnichannel", "remove-closed-livechat-rooms_description": "Permiso para eliminar salas Omnichannel cerradas", "remove-livechat-department": "Elimina los departamentos Omnichannel", "remove-user": "Eliminar usuario", @@ -3472,7 +3502,7 @@ "Replay": "Repetición", "Replied_on": "Respondió en", "Replies": "Respuestas", - "Reply": "Responder", + "Reply": "Respuesta", "reply_counter": "__counter__ respuesta", "reply_counter_plural": "__counter__ respuestas", "Reply_in_direct_message": "Responder en mensaje directo", @@ -3540,21 +3570,21 @@ "RetentionPolicy_Precision": "Precisión del temporizador", "RetentionPolicy_Precision_Description": "Con qué frecuencia debe funcionar el temporizador de poda. Establecer esto en un valor más preciso hace que los canales con temporizadores de retención rápidos funcionen mejor, pero podría costar potencia de procesamiento adicional en comunidades grandes.", "RetentionPolicy_RoomWarning": "Los mensajes anteriores a __time__ se eliminan automáticamente aquí", - "RetentionPolicy_RoomWarning_FilesOnly": "Los archivos anteriores a __time__ se eliminarán automáticamente aquí (los mensajes permanecen intactos)", + "RetentionPolicy_RoomWarning_FilesOnly": "Los archivos anteriores a __time__ se eliminan automáticamente aquí (los mensajes permanecen intactos)", "RetentionPolicy_RoomWarning_Unpinned": "Los mensajes no fijados anteriores a __time__ se eliminarán automáticamente aquí", - "RetentionPolicy_RoomWarning_UnpinnedFilesOnly": "Los archivos no fijados anteriores a __time__ se eliminarán automáticamente aquí (los mensajes permanecen intactos)", + "RetentionPolicy_RoomWarning_UnpinnedFilesOnly": "Los archivos no fijados anteriores a __time__ se eliminan automáticamente aquí (los mensajes permanecen intactos)", "RetentionPolicyRoom_Enabled": "Borrar mensajes antiguos automáticamente", "RetentionPolicyRoom_ExcludePinned": "Excluir mensajes fijados", "RetentionPolicyRoom_FilesOnly": "Borre solo archivos, mantenga mensajes", "RetentionPolicyRoom_MaxAge": "Antigüedad máxima del mensaje en días (predeterminado: __max__)", "RetentionPolicyRoom_OverrideGlobal": "Anular la política de retención global", - "RetentionPolicyRoom_ReadTheDocs": "¡Cuidado! Ajustar estas configuraciones sin el mayor cuidado puede destruir todo el historial de mensajes. Lea la documentación antes de activar la función aquí .", + "RetentionPolicyRoom_ReadTheDocs": "¡Cuidado! Ajustar estas configuraciones sin el mayor cuidado puede destruir todo el historial de mensajes. Lea la documentación antes de activar la función aquí .", "Retry": "procesar de nuevo", "Retry_Count": "Contador de reintentos", "Return_to_home": "Volver a inicio", "Return_to_previous_page": "Volver a la página anterior", "Return_to_the_queue": "Regresar a la cola", - "Robot_Instructions_File_Content": "Contenido del fichero Robots.txt", + "Robot_Instructions_File_Content": "Contenido del archivo Robots.txt", "Default_Referrer_Policy": "Política de referencia predeterminada", "Default_Referrer_Policy_Description": "Controla la cabecera 'referrer' que se envía al solicitar medios incrustados de otros servidores. Para más información, consulte este enlace de MDN. Recuerde que se requiere una actualización completa de la página para que esto surta efecto", "No_Referrer": "Sin remitente", @@ -3578,12 +3608,12 @@ "Room_archivation_state_true": "Archivado", "Room_archived": "Sala Archivada", "room_changed_announcement": "El anuncio de la sala cambió a: __room_announcement__ por __user_by__", - "room_changed_avatar": "Avatar de la sala cambiado por __user_by__", + "room_changed_avatar": "Avatar de Room cambiado por__user_by__ ", "room_changed_description": "Descripción de la sala cambiada a: __room_description__ por __user_by__.", "room_changed_privacy": "Tipo de sala cambiado a: __room_type__ por __user_by__", "room_changed_topic": "Tema de la sala cambiado a: __room_topic__ por __user_by__", "Room_default_change_to_private_will_be_default_no_more": "Este es un canal predeterminado y cambiarlo a un grupo privado hará que deje de ser un canal predeterminado. ¿Quieres proceder?", - "Room_description_changed_successfully": "Descripción de la sala cambiada con éxito", + "Room_description_changed_successfully": "Room Descripción cambiada con éxito", "room_disallowed_reacting": "Room no permitida reaccionando por __user_by__ ", "Room_Edit": "Editar Room ", "Room_has_been_archived": "La sala ha sido archivada", @@ -3602,10 +3632,10 @@ "room_removed_read_only": "Room agregó permiso de escritura por __user_by__ ", "room_set_read_only": "Room configurada como de solo lectura por __user_by__ ", "Room_tokenpass_config_changed_successfully": "La configuración tokenpass de la sala cambiada con éxito", - "Room_topic_changed_successfully": "Tema de la sala cambiado con éxito", + "Room_topic_changed_successfully": "Room Tema de la sala cambiado con éxito", "Room_type_changed_successfully": "Tipo de sala cambiado con éxito", "Room_type_of_default_rooms_cant_be_changed": "Esta es una sala predeterminada y no se puede cambiar el tipo. Consulte a su administrador.", - "Room_unarchived": "Sala no archivada", + "Room_unarchived": "Room no archivada", "Room_updated_successfully": "¡Sala actualizada correctamente!", "Room_uploaded_file_list": "Lista de Archivos", "Room_uploaded_file_list_empty": "Ningún archivo disponible.", @@ -3679,9 +3709,9 @@ "SAML_Metadata_Template_Description": "Las siguientes variables están disponibles:\n- **\\_\\_sloLocation\\_\\_**: La URL de cierre de sesión simple de Rocket.Chat\n- **\\_\\_issuer\\_\\_**: The value of the __Custom Issuer__ setting.\n- **\\_\\_identifierFormat\\_\\_**: El valor de la opción __Identifier Format__\n- **\\_\\_certificateTag\\_\\_**: Si un certificado privado es configurado, esto incluirá el __Metadata Certificate Template__, de lo contrario será ignorado.\n- **\\_\\_callbackUrl\\_\\_**: La URL de llamada de Rocket.Chat", "SAML_MetadataCertificate_Template": "Plantilla de certificado de metadatos", "SAML_NameIdPolicy_Template": "Plantilla de política de NameID", - "SAML_NameIdPolicy_Template_Description": "Puede utilizar cualquier variable de la plantilla de solicitud de autorización aquí.", + "SAML_NameIdPolicy_Template_Description": "Puede utilizar cualquier variable de la Plantilla de solicitud de autorización aquí.", "SAML_Role_Attribute_Name": "Nombre del atributo de rol", - "SAML_Role_Attribute_Name_Description": "Si este atributo se encuentra en la respuesta SAML, sus valores se usarán como nombres de rol para los nuevos usuarios.", + "SAML_Role_Attribute_Name_Description": "Si este atributo se encuentra en la respuesta SAML, sus valores se usarán como nombres de roles para nuevos usuarios.", "SAML_Role_Attribute_Sync": "Sincronizar los roles de usuario", "SAML_Role_Attribute_Sync_Description": "Sincronice los roles de usuario de SAML al iniciar sesión (sobrescribe los roles de usuario local).", "SAML_Section_1_User_Interface": "Interfaz de usuario", @@ -3716,7 +3746,7 @@ "Search_Apps": "Buscar aplicaciones", "Search_by_file_name": "Buscar por nombre de archivo", "Search_by_username": "Búsqueda por nombre de usuario", - "Search_Channels": "Canales de búsqueda", + "Search_Channels": "búsqueda Channel", "Search_Chat_History": "Buscar en el historial de chat", "Search_current_provider_not_active": "El proveedor de búsqueda actual no está activo", "Search_Integrations": "Integraciones de búsqueda", @@ -3734,7 +3764,7 @@ "Secret_token": "Token secreto", "Security": "Seguridad", "See_full_profile": "Ver perfil completo", - "Select_a_department": "Seleccionar un departamento", + "Select_a_department": "Seleccione un departamento", "Select_a_room": "Seleccione una sala", "Select_a_user": "Seleccione un usuario", "Select_an_avatar": "Selecciona un avatar", @@ -3765,6 +3795,7 @@ "Send_invitation_email_error": "No has proporcionado ninguna dirección de correo electrónico valida. ", "Send_invitation_email_info": "Puede enviar múltiples invitaciones por correo electrónico a la vez", "Send_invitation_email_success": "Has enviado con éxito una invitación por correo electrónico a las siguientes direcciones:", + "Send_it_as_attachment_instead_question": "¿Enviarlo como archivo adjunto?", "Send_me_the_code_again": "Envíame el código de nuevo", "Send_request_on": "Enviar solicitud en", "Send_request_on_agent_message": "Enviar solicitud en mensajes del agente", @@ -3801,7 +3832,6 @@ "Server_Type": "Tipo de servidor", "Service": "Servicio", "Service_account_key": "Clave de cuenta de servicio", - "Sessions": "Sesiones", "Set_as_favorite": "Establecer como favorito", "Set_as_leader": "Establecer como líder", "Set_as_moderator": "Establecer como moderador", @@ -3822,6 +3852,7 @@ "Setup_Wizard": "Asistente de configuración", "Setup_Wizard_Info": "Lo guiaremos para configurar su primer usuario administrador, configurar su organización y registrar su servidor para recibir notificaciones push gratuitas y más.", "Share_Location_Title": "¿Compartir ubicacion?", + "Share_screen": "Compartir pantalla", "New_CannedResponse": "Nueva respuesta preparada", "Edit_CannedResponse": "Editar respuesta predefinida", "Sharing": "Intercambio", @@ -3849,7 +3880,8 @@ "Show_room_counter_on_sidebar": "Mostrar contador de salas en la barra lateral", "Show_Setup_Wizard": "Mostrar el asistente de configuración", "Show_the_keyboard_shortcut_list": "Mostrar la lista de atajos de teclado", - "Showing_archived_results": "

      Mostrando resultados archivados %s

      ", + "Show_video": "Ver video", + "Showing_archived_results": "

      Mostrando %sarchivados resultados

      ", "Showing_online_users": "Mostrando: __total_showing__ En linea: __online__ Total:__total__ ", "Showing_results": "

      Mostrando %s resultados

      ", "Showing_results_of": "Mostrando resultados %s - %s de %s", @@ -3861,12 +3893,12 @@ "Site_Url": "URL del Sitio", "Site_Url_Description": "Ejemplo: https://chat.domain.com", "Size": "Tamaño", - "Skip": "Saltar", + "Skip": "Omitir", "Slack_Users": "CSV de los usuarios de Slack", "SlackBridge_APIToken": "API Tokens", - "SlackBridge_APIToken_Description": "Puede configurar varios servidores slack agregando un token API por línea.", + "SlackBridge_APIToken_Description": "Puede configurar varios servidores slack agregando un token de API por línea.", "Slackbridge_channel_links_removed_successfully": "Los enlaces del canal de Slackbridge se han eliminado correctamente.", - "SlackBridge_error": "SlackBridge obtuvo un error al importar sus mensajes en %s: %s", + "SlackBridge_error": "SlackBridge recibió un error al importar sus mensajes en% s:% s", "SlackBridge_finish": "SlackBridge ha terminado de importar los mensajes en%s. Por favor, vuelva a cargar para ver todos los mensajes.", "SlackBridge_Out_All": "SlackBridge Out All", "SlackBridge_Out_All_Description": "Envía mensajes de todos los canales que existen en Slack y el bot se ha unido", @@ -3888,16 +3920,16 @@ "Smarsh_Email": "Smarsh Email", "Smarsh_Email_Description": "Dirección de correo electrónico Smarsh para enviar el archivo .eml a.", "Smarsh_Enabled": "Smarsh habilitado", - "Smarsh_Enabled_Description": "Si el conector eml de Smarsh está habilitado o no (se debe completar 'De correo electrónico' en Correo electrónico -> SMTP).", + "Smarsh_Enabled_Description": "Si el conector eml de Smarsh está habilitado o no (es necesario completar 'Desde correo electrónico' en Correo electrónico -> SMTP).", "Smarsh_Interval": "Intervalo Smarsh", - "Smarsh_Interval_Description": "La cantidad de tiempo que se debe esperar antes de enviar los chats (se debe completar \"De correo electrónico\" en Correo electrónico -> SMTP).", + "Smarsh_Interval_Description": "La cantidad de tiempo de espera antes de enviar los chats (es necesario completar 'Desde correo electrónico' en Correo electrónico -> SMTP).", "Smarsh_MissingEmail_Email": "Falta el correo electrónico", - "Smarsh_MissingEmail_Email_Description": "El correo electrónico para mostrar una cuenta de usuario cuando falta su dirección de correo electrónico, generalmente ocurre con las cuentas bot.", + "Smarsh_MissingEmail_Email_Description": "El correo electrónico que se muestra para una cuenta de usuario cuando falta su dirección de correo electrónico, generalmente ocurre con las cuentas de bot.", "Smarsh_Timezone": "Zona horaria Smarsh", "Smileys_and_People": "Sonrisas y Personas", "SMS": "SMS", "SMS_Default_Omnichannel_Department": "Departamento de Omnichannel (por defecto)", - "SMS_Default_Omnichannel_Department_Description": "Si se establece, todos los nuevos chats entrantes iniciados por esta integración se enrutarán a este departamento.", + "SMS_Default_Omnichannel_Department_Description": "Si se establece, todos los nuevos chats entrantes iniciados por esta integración se enrutarán a este departamento. \nEsta configuración se puede sobrescribir pasando el parámetro de consulta del departamento en la solicitud. \nEj.https:///api/v1/livechat/sms-entrante/twilio?department=.\nNota: si está usando el nombre del departamento, entonces debería ser URL seguro.", "SMS_Enabled": "SMS Habilitado", "SMTP": "SMTP", "SMTP_Host": "Servidor SMTP", @@ -3933,7 +3965,7 @@ "start-discussion": "Iniciar discusión", "start-discussion_description": "Permiso para iniciar una discusión", "start-discussion-other-user": "Iniciar discusión (Otro usuario)", - "start-discussion-other-user_description": "Permiso para iniciar una discusión, que le da permiso al usuario para crear una discusión a partir de un mensaje enviado también por otro usuario.", + "start-discussion-other-user_description": "Permiso para iniciar una discusión, que le da permiso al usuario para crear una discusión a partir de un mensaje enviado por otro usuario también.", "Started": "Comenzó", "Started_a_video_call": "Comenzó una videollamada", "Started_At": "Comenzó a las", @@ -3979,6 +4011,7 @@ "StatusMessage_Placeholder": "¿Qué estás haciendo en este momento?", "StatusMessage_Too_Long": "El mensaje de estado debe tener menos de 120 caracteres.", "Step": "Paso", + "Stop_call": "Detener llamada", "Stop_Recording": "Detener grabación", "Store_Last_Message": "Almacenar el último mensaje", "Store_Last_Message_Sent_per_Room": "Almacenar el último mensaje enviado en cada sala.", @@ -4013,7 +4046,7 @@ "Taken_at": "Tomado en", "Target user not allowed to receive messages": "El usuario objetivo no puede recibir mensajes", "TargetRoom": "Sala objetiva", - "TargetRoom_Description": "La sala donde se enviarán los mensajes que son el resultado de este evento. Solo se permite una sala objetivo y debe existir.", + "TargetRoom_Description": "La sala a la que se enviarán los mensajes que son el resultado de la activación de este evento. Solo se permite una sala de destino y debe existir.", "Team_Add_existing_channels": "Agregar Channels existentes", "Team_Add_existing": "Agregar existentes", "Team_Auto-join": "Unirse automáticamente", @@ -4084,7 +4117,7 @@ "Texts": "Textos", "Thank_you_exclamation_mark": "¡Gracias!", "Thank_you_for_your_feedback": "Gracias por su comentario", - "The_application_name_is_required": "El nombre de la aplicación es requerido", + "The_application_name_is_required": "El nombre de la aplicación es obligatorio.", "The_channel_name_is_required": "El nombre del canal es requerido", "The_emails_are_being_sent": "Los correos electrónicos están siendo enviados.", "The_empty_room__roomName__will_be_removed_automatically": "La sala vacía __roomName__ se eliminará automáticamente.", @@ -4106,7 +4139,7 @@ "theme-color-attention-color": "Color de atención", "theme-color-component-color": "Color de componente", "theme-color-content-background-color": "Color de fondo del contenido", - "theme-color-custom-scrollbar-color": "Barra de desplazamiento de color personalizado", + "theme-color-custom-scrollbar-color": "Color de barra de desplazamiento personalizado", "theme-color-error-color": "Color de error", "theme-color-info-font-color": "Color del Texto de Información", "theme-color-link-font-color": "Color del Texto de los Enlaces", @@ -4158,7 +4191,7 @@ "theme-color-transparent-lightest": "Luz aún más transparente", "theme-color-unread-notification-color": "Color de Notificaciones No Leídas", "theme-custom-css": "CSS personalizado", - "theme-font-body-font-family": "Familia de fuentes Body", + "theme-font-body-font-family": "Familia de fuentes de cuerpo", "There_are_no_agents_added_to_this_department_yet": "Todavía no hay agentes agregados a este departamento.", "There_are_no_applications": "Aún no se han agregado aplicaciones OAuth.", "There_are_no_applications_installed": "Actualmente no hay aplicaciones Rocket.Chat instaladas.", @@ -4207,7 +4240,7 @@ "toggle-room-e2e-encryption": "Alternar cifrado Room E2E", "toggle-room-e2e-encryption_description": "Permiso para alternar la sala de cifrado e2e", "Token": "Token", - "Token_Access": "Acceso Token", + "Token_Access": "Token de Acceso", "Token_Controlled_Access": "Acceso controlado Token", "Token_required": "Token requerido", "Tokenpass_Channel_Label": "Canal Tokenpass", @@ -4236,7 +4269,7 @@ "Transcript": "Transcripción", "Transcript_Enabled": "Preguntar al visitante si le gustaría una transcripción después de que se cerró el chat", "Transcript_message": "Mensaje para mostrar al preguntar sobre la transcripción", - "Transcript_of_your_livechat_conversation": "Transcripción de su conversación de Omnichannel.", + "Transcript_of_your_livechat_conversation": "Transcripción de su conversación de omnichannel.", "Transcript_Request": "Solicitud de transcripción", "transfer-livechat-guest": "Transferir invitados de Omnichannel", "transfer-livechat-guest_description": "Permiso para transferir invitados al Omnichannel", @@ -4247,7 +4280,7 @@ "Travel_and_Places": "Viajes y Lugares", "Trigger_removed": "Disparador eliminado", "Trigger_Words": "Palabras de activación", - "Triggers": "Disparadores", + "Triggers": "Activadores", "Troubleshoot": "Solución de problemas", "Troubleshoot_Description": "Estas configuraciones están diseñadas para habilitarse solo con la guía de los equipos de soporte o desarrollo de Rocket.Chat. ¡No los toques si no sabes lo que estás haciendo!", "Troubleshoot_Disable_Data_Exporter_Processor": "Desactivar el procesador de exportación de datos", @@ -4259,7 +4292,7 @@ "Troubleshoot_Disable_Notifications": "Deshabilitar notificaciones", "Troubleshoot_Disable_Notifications_Alert": "Esta configuración desactiva completamente el sistema de notificaciones; ¡los sonidos, las notificaciones de escritorio, las notificaciones de móvil y los correos electrónicos se detendrán!", "Troubleshoot_Disable_Presence_Broadcast": "Desactivar la transmisión de la presencia", - "Troubleshoot_Disable_Presence_Broadcast_Alert": "Esta configuración evita que todas las instancias envíen los cambios de estado de los usuarios a sus clientes, manteniendo a todos los usuarios con su estado de presencia desde la primera carga!", + "Troubleshoot_Disable_Presence_Broadcast_Alert": "¡Esta configuración evita que todas las instancias envíen los cambios de estado de los usuarios a sus clientes, manteniendo a todos los usuarios con su estado de presencia desde la primera carga!", "Troubleshoot_Disable_Sessions_Monitor": "Desactivar el Monitor de Sesiones", "Troubleshoot_Disable_Sessions_Monitor_Alert": "¡Esta configuración detiene el procesamiento de las sesiones de visita de Omnichannel causando que las estadísticas dejen de funcionar correctamente!", "Troubleshoot_Disable_Statistics_Generator": "Desactivar el generador de estadísticas", @@ -4270,8 +4303,10 @@ "Tuesday": "Martes", "Turn_OFF": "Apagar", "Turn_ON": "Encender", + "Turn_on_video": "Activar el video", + "Turn_off_video": "Desactivar el video", "Two Factor Authentication": "Autenticación de dos factores", - "Two-factor_authentication": "Autenticación en 2 pasos vía TOTP", + "Two-factor_authentication": "Autenticación de dos factores a través de TOTP", "Two-factor_authentication_disabled": "Autenticación en 2 pasos deshabilitada", "Two-factor_authentication_email": "Autenticación en 2 pasos vía correo electrónico", "Two-factor_authentication_email_is_currently_disabled": "La autenticación en 2 pasos vía correo electrónico está actualmente desactivada", @@ -4294,7 +4329,7 @@ "UI_Click_Direct_Message_Description": "Omita la pestaña de perfil de apertura, en lugar de ir directamente a la conversación", "UI_DisplayRoles": "Mostrar Roles", "UI_Group_Channels_By_Type": "Agrupar canales por tipo", - "UI_Merge_Channels_Groups": "Unir grupos privados con Canales", + "UI_Merge_Channels_Groups": "Unir grupos privados con Channels", "UI_Show_top_navbar_embedded_layout": "Mostrar la barra de navegación superior en el diseño incrustado", "UI_Unread_Counter_Style": "Estilo de contador no leído", "UI_Use_Name_Avatar": "Utilice las iniciales del nombre completo para generar un avatar predeterminado", @@ -4304,7 +4339,7 @@ "unarchive-room": "Habitación desarchivada", "unarchive-room_description": "Permiso para desarchivar canales", "Unavailable": "No disponible", - "Unblock_User": "Desbloquear usuario", + "Unblock_User": "Desbloquear usuario ", "Uncheck_All": "Desmarcar todo", "Uncollapse": "Desplegar", "Undefined": "No definido", @@ -4315,21 +4350,23 @@ "Unit_removed": "Unidad eliminada", "Unknown_Import_State": "Estado de importación desconocido", "Unlimited": "Ilimitado", - "Unmute_someone_in_room": "Des-silenciar a alguien en la sala", + "Unmute": "Activar sonido", + "Unmute_someone_in_room": "Activar el sonido de alguien en la sala", "Unmute_user": "Des-silenciar usuario", "Unnamed": "Sin nombre", "Unpin": "Quitar de fijados", "Unpin_Message": "Desfijar Mensaje", "unpinning-not-allowed": "No se permite quitar de fijados", "Unread": "No leído", - "Unread_Count": "Cuenta no leída", - "Unread_Count_DM": "Cuenta no leída para mensajes directos", + "Unread_Count": "Recuento de no leídos", + "Unread_Count_DM": "Recuento de mensajes no leídos para mensajes directos", "Unread_Messages": "Mensajes no leídos", "Unread_on_top": "No leídos arriba", "Unread_Rooms": "Salas sin leer", "Unread_Rooms_Mode": "Modo Salas sin leer", "Unread_Tray_Icon_Alert": "Ícono de alerta de no leidos", "Unstar_Message": "Eliminar Destacado", + "Unmute_microphone": "Activar sonido del micrófono", "Update": "Actualización", "Update_EnableChecker": "Habilitar el Update Checker", "Update_EnableChecker_Description": "Comprueba automáticamente si hay nuevas actualizaciones / mensajes importantes de los desarrolladores de Rocket.Chat y recibe notificaciones cuando están disponibles. La notificación aparece una vez por nueva versión como un banner en el que se puede hacer clic y como un mensaje del bot Rocket.Cat, ambos visibles solo para los administradores.", @@ -4337,7 +4374,7 @@ "Update_LatestAvailableVersion": "Actualizar a la última versión disponible", "Update_to_version": "Actualizar a la __version__", "Update_your_RocketChat": "Actualiza tu Rocket.Chat", - "Updated_at": "Actualizado", + "Updated_at": "Actualizado en", "Upload": "Subir", "Uploads": "Cargas", "Upload_app": "Subir la aplicación", @@ -4364,7 +4401,7 @@ "Use_Room_configuration": "Sobrescribe la configuración del servidor y utiliza la configuración de la sala", "Use_Server_configuration": "Usar la configuración del servidor", "Use_service_avatar": "Usar %s avatar", - "Use_this_response": "Usar esta respuesta", + "Use_this_response": "Utilizar esta respuesta", "Use_response": "Respuesta de uso", "Use_this_username": "Usar este nombre de usuario", "Use_uploaded_avatar": "Utilizar avatar subido", @@ -4378,7 +4415,7 @@ "User__username__is_now_a_owner_of__room_name_": "El usuario __username__ es ahora un propietario de __room_name__", "User__username__muted_in_room__roomName__": "Usuario __username__ silenciado en la sala __roomName__", "User__username__removed_from__room_name__leaders": "El usuario __username__ fue removido de los líderes de __room_name__", - "User__username__removed_from__room_name__moderators": "El usuario __username__ fue removido de los moderadores de __room_name__ ", + "User__username__removed_from__room_name__moderators": "El usuario __username__ fue retirado de los moderadores de __room_name__", "User__username__removed_from__room_name__owners": "El usuario __username__ fue removido de los propietarios de __room_name__", "User__username__unmuted_in_room__roomName__": "Usuario __username__ sin silenciar en la sala __roomName__", "User_added": "Usuario __user_added__ añadido.", @@ -4411,7 +4448,7 @@ "User_joined_team": "Se ha unido al equipo.", "User_joined_team_female": "Se ha unido al equipo.", "User_joined_team_male": "Se ha unido al equipo.", - "User_left": "__user_left__ ha salido del canal.", + "User_left": "Ha salido del canal.", "User_left_female": "Ha salido del canal.", "User_left_male": "Ha salido del canal.", "User_left_team": "Ha dejado el equipo.", @@ -4440,7 +4477,7 @@ "User_uploaded_a_file_to_you": "__username__ le envió un archivo", "User_uploaded_file": "Subió un archivo", "User_uploaded_image": "Subió una imagen", - "user-generate-access-token": "User Generate Access Token", + "user-generate-access-token": "Token de acceso generado por el usuario", "user-generate-access-token_description": "Permiso para que los usuarios generen tokens de acceso", "UserData_EnableDownload": "Habilitar la descarga de datos de usuario", "UserData_FileSystemPath": "Ruta del sistema (archivos exportados)", @@ -4475,7 +4512,7 @@ "Users": "Usuarios", "Users must use Two Factor Authentication": "Los usuarios deben utilizar la autenticación de dos factores", "Users_added": "Los usuarios se han añadido", - "Users_and_rooms": "Usuarios y Salas", + "Users_and_rooms": "Usuarios y Rooms", "Users_by_time_of_day": "Usuarios por hora del día", "Users_in_role": "Usuarios en el rol", "Users_key_has_been_reset": "Se restableció la clave del usuario", @@ -4490,6 +4527,7 @@ "UTF8_User_Names_Validation_Description": "RegExp que se utilizará para validar nombres de usuario", "UTF8_Channel_Names_Validation": "Validación de nombres de channel UTF8", "UTF8_Channel_Names_Validation_Description": "RegExp que se utilizará para validar los nombres de los canales", + "Videocall_enabled": "Videollamada habilitada", "Validate_email_address": "Validar correo electrónico", "Validation": "Validación", "Value_messages": "__value__ messages", @@ -4511,10 +4549,12 @@ "Video_Conference": "Videoconferencia", "Video_message": "Mensaje de video", "Videocall_declined": "Videollamada rechazada.", - "Videocall_enabled": "Videollamada habilitada", + "Video_and_Audio_Call": "Llamada de audio y video", "Videos": "Vídeos", - "View_All": "Ver Todos los Miembros", + "View_All": "Ver todos los miembros", "View_channels": "Ver Channel s", + "view-omnichannel-contact-center": "Ver centro de contacto Omnichannel", + "view-omnichannel-contact-center_description": "Permiso para ver e interactuar con el centro de contacto Omnichannel", "View_Logs": "Ver Registros", "View_mode": "Modo de vista", "View_original": "Ver original", @@ -4523,7 +4563,7 @@ "view-broadcast-member-list_description": "Permiso para ver la lista de usuarios en el canal de transmisión.", "view-c-room": "Ver canal público", "view-c-room_description": "Permiso para ver canales públicos", - "view-canned-responses": "Ver modelos de respuesta", + "view-canned-responses": "Veure respostes emmagatzemades", "view-d-room": "Ver mensajes directos", "view-d-room_description": "Permiso para ver mensajes directos", "View_full_conversation": "Ver conversación completa", @@ -4535,7 +4575,7 @@ "view-join-code_description": "Permiso para ver el código de unión de canal", "view-joined-room": "Ver sala unida", "view-joined-room_description": "Permiso para ver los canales actualmente unidos", - "view-l-room": "Ver salas de Omnichannel", + "view-l-room": "Ver Rooms de Omnichannel", "view-l-room_description": "Permiso para ver salas de Omnichannel", "view-livechat-analytics": "Ver analíticas de Omnichannel", "view-livechat-analytics_description": "Permiso para ver análisis de Omnichannel", @@ -4564,7 +4604,7 @@ "view-livechat-unit": "Ver las unidades de Omnichannel", "view-logs": "Ver los registros", "view-logs_description": "Permiso para ver los registros del servidor", - "view-other-user-channels": "Ver otros canales de usuario", + "view-other-user-channels": "Ver otro usuario Channels ", "view-other-user-channels_description": "Permiso para ver los canales propiedad de otros usuarios", "view-outside-room": "Vista exterior de Room", "view-outside-room_description": "Permiso para ver usuarios fuera de la sala actual", @@ -4584,10 +4624,11 @@ "Visit_Site_Url_and_try_the_best_open_source_chat_solution_available_today": "Visite __Site_URL__ y pruebe la mejor solución de chat de código abierto disponible hoy.", "Visitor": "Visitante", "Visitor_Email": "Correo electrónico del visitante", - "Visitor_Info": "Información para visitantes", + "Visitor_Info": "Información del visitante", "Visitor_message": "Mensajes de visitantes", "Visitor_Name": "Nombre del visitante", "Visitor_Name_Placeholder": "Por favor, introduzca el nombre del visitante...", + "Visitor_does_not_exist": "El visitante no existe!", "Visitor_Navigation": "Navegación visitante", "Visitor_page_URL": "URL de la página del visitante", "Visitor_time_on_site": "Tiempo del visitante en el sitio", @@ -4600,7 +4641,7 @@ "Warnings": "Advertencias", "WAU_value": "WAU __value__", "We_appreciate_your_feedback": "Agradecemos sus comentarios", - "We_are_offline_Sorry_for_the_inconvenience": "Fuera de línea. Disculpe las molestias.", + "We_are_offline_Sorry_for_the_inconvenience": "Estamos fuera de línea. Disculpen las molestias.", "We_have_sent_password_email": "Te hemos enviado un e-mail con las instrucciones para resetear la contraseña. Si no recibes un correo en breve, por favor regresa y vuelve a intentarlo.", "We_have_sent_registration_email": "Te hemos enviado un e-mail para confirmar tu registro. Si no recibes un correo en breve, por favor regresa y vuelve a intentarlo.", "Webdav Integration": "Integración Webdav", @@ -4615,6 +4656,7 @@ "Webhook_Details": "Detalles WebHook", "Webhook_URL": "URL Webhook", "Webhooks": "WebHooks", + "WebRTC_Call": "Llamada WebRTC", "WebRTC_direct_audio_call_from_%s": "Llamada de audio directa de %s", "WebRTC_direct_video_call_from_%s": "Videollamada directa de %s", "WebRTC_Enable_Channel": "Habilitar para Canales Públicos", @@ -4625,6 +4667,8 @@ "WebRTC_monitor_call_from_%s": "Monitoriza la llamada de %s", "WebRTC_Servers": "Servidores STUN / TURN", "WebRTC_Servers_Description": "Una lista de servidores STUN y TURN separadas por comas.
      Nombre de usuario, contraseña y el puerto están permitidos en el formato `username:password@stun:host:port` o `username:password@turn:host:port`.", + "WebRTC_call_ended_message": "La llamada finalizó a las __endTime__ - Duró __callDuration__", + "WebRTC_call_declined_message": " Llamada rechazada por contacto.", "Website": "Sitio web", "Wednesday": "Miércoles", "Weekly_Active_Users": "Usuarios activos semanales", @@ -4658,13 +4702,13 @@ "Yes_unarchive_it": "Sí, desarchivarlo.", "yesterday": "ayer", "Yesterday": "Ayer", - "You": "Tú", + "You": "Usted", "You_are_converting_team_to_channel": "Estás convirtiendo este equipo en un canal.", "you_are_in_preview_mode_of": "Estás en modo vista previa del canal #__room_name__", "you_are_in_preview_mode_of_incoming_livechat": "Estás en el modo de vista previa de este chat", "You_are_logged_in_as": "Ha iniciado sesión como", "You_are_not_authorized_to_view_this_page": "No está autorizado para ver esta página.", - "You_can_change_a_different_avatar_too": "Puedes anular el avatar usado para publicar desde esta integración.", + "You_can_change_a_different_avatar_too": "Puede anular el avatar utilizado para publicar desde esta integración.", "You_can_close_this_window_now": "Ya puedes cerrar esta ventana.", "You_can_search_using_RegExp_eg": "Puede buscar utilizando una Expresión Regular. Por ejemplo: /^text$/i", "You_can_use_an_emoji_as_avatar": "También puede utilizar un emoji como avatar.", @@ -4673,7 +4717,7 @@ "You_followed_this_message": "Seguiste este mensaje.", "You_have_a_new_message": "Tiene un nuevo mensaje", "You_have_been_muted": "Has sido silenciado y no puedes hablar en esta sala", - "You_have_n_codes_remaining": "Usted tiene códigos __number__ restantes.", + "You_have_n_codes_remaining": "Te quedan __number__ códigos.", "You_have_not_verified_your_email": "Aún no ha verificado su correo electrónico.", "You_have_successfully_unsubscribed": "Se ha dado de baja correctamente de nuestra lista de distribución de correos", "You_have_to_set_an_API_token_first_in_order_to_use_the_integration": "Primero debe establecer un token API para usar la integración.", @@ -4682,27 +4726,27 @@ "You_need_install_an_extension_to_allow_screen_sharing": "Necesita instalar una extensión para permitir compartir su pantalla", "You_need_to_change_your_password": "Es necesario cambiar la contraseña", "You_need_to_type_in_your_password_in_order_to_do_this": "¡Usted tiene que escribir su contraseña con el fin de hacer esto!", - "You_need_to_type_in_your_username_in_order_to_do_this": "¡Es necesario teclear su nombre de usuario con el fin de hacer esto!", + "You_need_to_type_in_your_username_in_order_to_do_this": "¡Necesita escribir su nombre de usuario para hacer esto!", "You_need_to_verifiy_your_email_address_to_get_notications": "Es necesario haber comprobado su dirección de correo electrónico para recibir notificaciones", - "You_need_to_write_something": "¡Usted tiene que escribir algo!", + "You_need_to_write_something": "¡Necesitas escribir algo!", "You_reached_the_maximum_number_of_guest_users_allowed_by_your_license": "Alcanzaste el máximo número de usuarios invitados permitido por tu licencia.", "You_should_inform_one_url_at_least": "Debe definir al menos una URL.", - "You_should_name_it_to_easily_manage_your_integrations": "Nombralo para poder administrar fácilmente sus integraciones", + "You_should_name_it_to_easily_manage_your_integrations": "Debería nombrarlo para administrar fácilmente sus integraciones.", "You_unfollowed_this_message": "Dejaste de seguir este mensaje.", - "You_will_be_asked_for_permissions": "Se le pedirá permiso", + "You_will_be_asked_for_permissions": "Se le pedirán permisos", "You_will_not_be_able_to_recover": "No podrás recuperar este mensaje", "You_will_not_be_able_to_recover_email_inbox": "No podrá recuperar esta bandeja de entrada de correo electrónico", - "You_will_not_be_able_to_recover_file": "No será capaz de recuperar este archivo", - "You_wont_receive_email_notifications_because_you_have_not_verified_your_email": "No recibirá notificaciones por correo electrónico, ya que no ha comprobado su correo electrónico.", + "You_will_not_be_able_to_recover_file": "¡No podrá recuperar este archivo!", + "You_wont_receive_email_notifications_because_you_have_not_verified_your_email": "No recibirá notificaciones por correo electrónico porque no ha verificado su correo electrónico.", "Your_e2e_key_has_been_reset": "Su clave E2E ha sido restablecida.", "Your_email_address_has_changed": "Su dirección de correo electrónico ha sido modificada.", - "Your_email_has_been_queued_for_sending": "Su correo electrónico se ha puesto en cola para envío", + "Your_email_has_been_queued_for_sending": "Su correo electrónico se ha puesto en cola para su envío.", "Your_entry_has_been_deleted": "Tu entrada ha sido eliminada", "Your_file_has_been_deleted": "Su archivo ha sido eliminado.", "Your_invite_link_will_expire_after__usesLeft__uses": "Su enlace de invitación expirará después de __usesLeft__ usos.", "Your_invite_link_will_expire_on__date__": "Su enlace de invitación expirará el día __date__.", "Your_invite_link_will_expire_on__date__or_after__usesLeft__uses": "Su enlace de invitación expirará en __date__ o después de __usesLeft__ usos.", - "Your_invite_link_will_never_expire": "Su enlace de invitación no expirará.", + "Your_invite_link_will_never_expire": "Su enlace de invitación nunca caducará.", "Your_mail_was_sent_to_s": "Su correo electrónico fue enviado a %s", "your_message": "su mensaje", "your_message_optional": "su mensaje (opcional)", diff --git a/packages/rocketchat-i18n/i18n/et.i18n.json b/packages/rocketchat-i18n/i18n/et.i18n.json index 1e2d4ba7a29c0..a267d7e212098 100644 --- a/packages/rocketchat-i18n/i18n/et.i18n.json +++ b/packages/rocketchat-i18n/i18n/et.i18n.json @@ -74,5 +74,6 @@ "Type_your_email": "Sisesta oma e-mail", "Type_your_message": "Sisestage oma sõnum", "Type_your_name": "Sisestage oma nimi", - "Upload_file_question": "Faili üles laadima?" + "Upload_file_question": "Faili üles laadima?", + "User_left": "Kasutaja lahkus" } \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/eu.i18n.json b/packages/rocketchat-i18n/i18n/eu.i18n.json index 6b15defb7b1dd..348a9c5a76c23 100644 --- a/packages/rocketchat-i18n/i18n/eu.i18n.json +++ b/packages/rocketchat-i18n/i18n/eu.i18n.json @@ -116,6 +116,7 @@ "User_joined_channel": "Kanalera batu da.", "User_joined_channel_female": "Kanalera batu da.", "User_joined_channel_male": "kanalera batu da.", + "User_left": "Erabiltzailea irten da", "Users": "Erabiltzaileak", "We_are_offline_Sorry_for_the_inconvenience": "Lineaz kanpo gaude. Barkatu eragozpenak.", "Yes": "Bai" diff --git a/packages/rocketchat-i18n/i18n/fa.i18n.json b/packages/rocketchat-i18n/i18n/fa.i18n.json index 962f5dbde45ed..3fd78b586a0b9 100644 --- a/packages/rocketchat-i18n/i18n/fa.i18n.json +++ b/packages/rocketchat-i18n/i18n/fa.i18n.json @@ -380,6 +380,8 @@ "Apps_Marketplace_Login_Required_Description": "خرید برنامه از Rocket.Chat Marketplace نیاز به ثبت فضای کاری شما و ورود به سیستم دارد.", "Apps_Marketplace_Login_Required_Title": "نیازمند ورود به بازار", "Apps_Marketplace_Modify_App_Subscription": "تصحیح کردن اشتراک", + "Apps_Marketplace_pricingPlan_yearly": null, + "Apps_Marketplace_pricingPlan_yearly_perUser": null, "Apps_Marketplace_Uninstall_App_Prompt": "آیا واقعاً می خواهید این برنامه را حذف کنید؟", "Apps_Marketplace_Uninstall_Subscribed_App_Anyway": "در هر صورت حذف کنید", "Apps_Marketplace_Uninstall_Subscribed_App_Prompt": "این برنامه اشتراک دارد و حذف آن لغو نخواهد شد. اگر می خواهید این کار را انجام دهید ، لطفاً قبل از حذف اشتراک ، اشتراک خود را اصلاح کنید.", @@ -1533,7 +1535,7 @@ "hours": "ساعت ها", "Hours": "ساعت ها", "How_friendly_was_the_chat_agent": "عامل چت چقدر دوستانه بود؟", - "How_knowledgeable_was_the_chat_agent": "عامل چت چقدر آگاه بود؟", + "How_knowledgeable_was_the_chat_agent": "مامور گفتگو تا چه حد مسلط و آگاه بود؟", "How_long_to_wait_after_agent_goes_offline": "چقدر طول می کشد پس از اینکه نماینده به صورت آفلاین می رود", "How_responsive_was_the_chat_agent": "عامل چت چقدر پاسخگو بود؟", "How_satisfied_were_you_with_this_chat": "چه میزان از این چت راضی بودید؟", @@ -2279,7 +2281,7 @@ "Please_fill_a_name": "لطفا یک نام را پر کنید", "Please_fill_a_username": "لطفا یک نام کاربری پر کردن", "Please_fill_all_the_information": "لطفا تمام اطلاعات را پر کنید", - "Please_fill_name_and_email": "لطفا نام و ایمیل را پر کنید", + "Please_fill_name_and_email": "لطفا نام و ایمیل را وارد نمایید", "Please_go_to_the_Administration_page_then_Livechat_Facebook": "لطفا به صفحه مدیریت و سپس کانال همه‌کاره > فیسبوک بروید", "Please_select_an_user": "لطفا یک کاربر را انتخاب کنید", "Please_select_enabled_yes_or_no": "لطفا یک گزینه برای فعال را انتخاب کنید", @@ -2442,6 +2444,8 @@ "RetentionPolicy_MaxAge_Groups": "حداکثر سن پیام در گروه های خصوصی", "RetentionPolicy_Precision": "تایمر دقیق", "RetentionPolicy_Precision_Description": "هر چند وقت یکبار تایمر بره باید اجرا شود تنظیم این به یک مقدار دقیق تر باعث می شود کانال های با تایمر نگهداری سریع کار بهتر، اما ممکن است پردازش قدرت اضافی در جوامع بزرگ هزینه.", + "RetentionPolicy_RoomWarning_FilesOnly": null, + "RetentionPolicy_RoomWarning_UnpinnedFilesOnly": null, "RetentionPolicyRoom_Enabled": "پیام های قدیمی را به طور خودکار خرد کنید", "RetentionPolicyRoom_ExcludePinned": "پیام های پین شده را حذف کنید", "RetentionPolicyRoom_FilesOnly": "فقط پرونده ها را ببندید، پیام ها را نگه دارید", @@ -2536,7 +2540,7 @@ "seconds": "ثانیه", "Secret_token": "علامت رمز", "Security": "امنیت", - "Select_a_department": "انتخاب بخش", + "Select_a_department": "یک بخش را انتخاب کنید", "Select_a_user": "یک کاربر را انتخاب کنید", "Select_an_avatar": "انتخاب تصویر", "Select_an_option": "یک گزینه را انتخاب کنید", @@ -2825,6 +2829,8 @@ "This_email_has_already_been_used_and_has_not_been_verified__Please_change_your_password": "این ایمیل قبلا استفاده شده است و تأیید نشده است. لطفا رمز عبور خود را تغییر دهید.", "This_is_a_desktop_notification": "این یک اعلان دسکتاپ است", "This_is_a_push_test_messsage": "این messsage آزمون فشار است", + "This_room_has_been_archived_by__username_": null, + "This_room_has_been_unarchived_by__username_": null, "Thursday": "پنج شنبه", "Time_in_seconds": "زمان در ثانیه", "Title": "عنوان", @@ -2873,7 +2879,7 @@ "Type_your_email": "نوع ایمیل خود را", "Type_your_job_title": "عنوان شغلی خود را تایپ کنید", "Type_your_message": "نوع پیام خود را", - "Type_your_name": "نامتان را بنویسید", + "Type_your_name": "نام خود را وارد نمایید", "Type_your_new_password": "کلمه عبور جدید را وارد کنید", "Type_your_password": "رمز عبور خود را تایپ کنید", "Type_your_username": "نام کاربری خود را وارد کنید", @@ -3011,6 +3017,7 @@ "Users_added": "کاربران اضافه شده اند", "Users_in_role": "کاربران در نقش", "UTF8_Names_Slugify": "UTF8 نام slugify را", + "Videocall_enabled": "تماس ویدیویی فعال شد", "Validate_email_address": "اعتبار آدرس ایمیل", "Verification": "تایید", "Verification_Description": "شما ممکن است از متغیرهایی زیر استفاده کنید:
      • [Verification_Url] برای URL تأیید.
      • [نام]، [نام خانوادگی]، [lname] برای نام کامل، نام یا نام خانوادگی کاربر، به ترتیب.
      • [ایمیل] برای ایمیل کاربر.
      • [نام سایت] و [Site_URL] برای نام برنامه و URL به ترتیب.
      ", @@ -3025,7 +3032,6 @@ "Video_Conference": "ویدیو کنفرانس", "Video_message": "پیام ویدویی", "Videocall_declined": "تماس ویدیویی رد شد", - "Videocall_enabled": "تماس ویدیویی فعال شد", "View_All": "مشاهده همه", "View_Logs": "نمایش سیاهههای مربوط", "View_mode": "شیوه نمایش", @@ -3125,6 +3131,7 @@ "you_are_in_preview_mode_of": "شما در حالت پیش نمایش از کانال # __room_name__ هستند", "You_are_logged_in_as": "شما وارد شدید با عنوان", "You_are_not_authorized_to_view_this_page": "شما به این صفحه مجاز است.", + "You_can_change_a_different_avatar_too": "شما می توانید نماد مورد استفاده برای ارسال از این ادغام را لغو کنید.", "You_can_search_using_RegExp_eg": "شما میتوانید با استفاده از عبارت با قاعده جستجو کنید. به عنوان مثلا/^text$/i", "You_can_use_an_emoji_as_avatar": "همچنین می توانید از یک شکلک برای تصویر استفاده کنید.", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "می توانید از وب‌قلاب‌ها برای یکپارچه‌سازی کانال همه‌کاره با مدیریت ارتباط مشتری استفاده کنید.", @@ -3157,4 +3164,4 @@ "Your_push_was_sent_to_s_devices": "فشار خود را به دستگاه %s را ارسال شد", "Your_server_link": "لینک سرور شما", "Your_workspace_is_ready": "فضای کاری شما آماده استفاده است" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/fi.i18n.json b/packages/rocketchat-i18n/i18n/fi.i18n.json index f0da58be89490..f816082b7fb79 100644 --- a/packages/rocketchat-i18n/i18n/fi.i18n.json +++ b/packages/rocketchat-i18n/i18n/fi.i18n.json @@ -555,6 +555,7 @@ "Continuous_sound_notifications_for_new_livechat_room": "Jatkuva ääniilmoitukset uudelle livechat-huoneelle", "Conversation": "Keskustelu", "Conversation_closed": "Keskustelu suljettu: __comment__.", + "Conversation_finished": "Keskustelu päättynyt", "Conversation_finished_message": "Keskustelun valmiin viestin", "conversation_with_s": "keskustelu %s: lla", "Convert_Ascii_Emojis": "Muunna ASCII-merkit Emojiksi", @@ -1687,6 +1688,7 @@ "Max_length_is": "Suurin pituus on%s", "Media": "tiedotusvälineet", "Medium": "keskikokoinen", + "Members": "Jäsenet", "Members_List": "Jäsenlista", "mention-all": "Mainitse kaikki", "mention-all_description": "Käyttöoikeus käyttää @all-maininta", @@ -2302,6 +2304,7 @@ "Show_Setup_Wizard": "Näytä ohjattu asennustoiminto", "Show_the_keyboard_shortcut_list": "Näytä pikanäppäinten luettelo", "Showing_archived_results": "

      Näytetään %s arkistoitua tulosta

      ", + "Showing_online_users": null, "Showing_results": "

      Näytetään %s tulosta

      ", "Sidebar": "sivupalkki", "Sidebar_list_mode": "Sivupalkin kanavaluettelotila", @@ -2681,6 +2684,7 @@ "Users_added": "Käyttäjät on lisätty", "Users_in_role": "Roolin käyttäjiä", "UTF8_Names_Slugify": "Siisti UTF8-nimet", + "Videocall_enabled": "Videopuhelu käytössä", "Validate_email_address": "Validoi sähköpostiosoite", "Verification": "Varmistus", "Verification_Description": "Voit käyttää seuraavia paikanvaraajia:
      • [Verification_Url] vahvistus-URL-osoitteelle.
      • [nimi], [fname], [lname] käyttäjän koko nimen, etunimen tai sukunimen osalta.
      • [email] käyttäjän sähköposti.
      • [Sivuston nimi] ja [Sivusto_URL].
      ", @@ -2695,7 +2699,6 @@ "Video_Conference": "Videoneuvottelu", "Video_message": "VIdeoviesti", "Videocall_declined": "Videopuhelu hylätty", - "Videocall_enabled": "Videopuhelu käytössä", "View_All": "Katso kaikki", "View_Logs": "Katso lokit", "View_mode": "Näkymätila", @@ -2817,4 +2820,4 @@ "Your_push_was_sent_to_s_devices": "Push-viestisi lähetettiin %s laitteeseen", "Your_server_link": "Palvelimesi linkki", "Your_workspace_is_ready": "Työtila on valmis käyttämään 🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/fr.i18n.json b/packages/rocketchat-i18n/i18n/fr.i18n.json index dda149f2be845..cad2e6136c124 100644 --- a/packages/rocketchat-i18n/i18n/fr.i18n.json +++ b/packages/rocketchat-i18n/i18n/fr.i18n.json @@ -13,16 +13,16 @@ "%_of_conversations": "% des conversations", "0_Errors_Only": "0 - Erreurs seulement", "1_Errors_and_Information": "1 - Erreurs et informations", - "2_Erros_Information_and_Debug": "2 - Erreurs, informations et débogage ", - "12_Hour": "Horloge 12h", + "2_Erros_Information_and_Debug": "2 - Erreurs, informations et débogage", + "12_Hour": "Horloge de 12 heures", "24_Hour": "Horloge 24h", - "A_new_owner_will_be_assigned_automatically_to__count__rooms": "Un nouveau propriétaire sera automatiquement assigné à __count__ salons.", + "A_new_owner_will_be_assigned_automatically_to__count__rooms": "Un nouveau propriétaire sera automatiquement attribué à __count__ salons.", "A_new_owner_will_be_assigned_automatically_to_the__roomName__room": "Un nouveau propriétaire sera automatiquement assigné au salon __roomName__.", "A_new_owner_will_be_assigned_automatically_to_those__count__rooms__rooms__": "Un nouveau propriétaire sera automatiquement assigné à ces __count__ salons :
      __rooms__.", "Accept": "Accepter", "Accept_incoming_livechat_requests_even_if_there_are_no_online_agents": "Acceptez les demandes omnicanales entrantes même si il n'y a pas d'agents en ligne", "Accept_new_livechats_when_agent_is_idle": "Accepter les nouvelles demandes omnicanal lorsque l'agent est inactif", - "Accept_with_no_online_agents": "Accepter sans agent en ligne", + "Accept_with_no_online_agents": "Accepter sans agents en ligne", "Access_not_authorized": "Accès non autorisé", "Access_Token_URL": "URL du jeton d'accès", "access-mailer": "Accéder à l'écran Mailer", @@ -114,6 +114,8 @@ "Accounts_OAuth_Custom_Merge_Users": "Fusionner les utilisateurs", "Accounts_OAuth_Custom_Name_Field": "Champ du nom", "Accounts_OAuth_Custom_Roles_Claim": "Nom du champ Rôles/Groupes", + "Accounts_OAuth_Custom_Roles_To_Sync": "Rôles à synchroniser", + "Accounts_OAuth_Custom_Roles_To_Sync_Description": "Rôles OAuth à synchroniser lors de la connexion et de la création de l'utilisateur (séparés par des virgules).", "Accounts_OAuth_Custom_Scope": "Portée", "Accounts_OAuth_Custom_Secret": "Secret", "Accounts_OAuth_Custom_Show_Button_On_Login_Page": "Montrer le bouton sur la page de connexion", @@ -268,7 +270,7 @@ "Add_Sender_To_ReplyTo": "Ajouter l'expéditeur à répondre à", "Add_user": "Ajouter un utilisateur", "Add_User": "Ajouter un utilisateur", - "Add_users": "Ajouter des utilisateurs", + "Add_users": "Ajouter plusieurs utilisateurs", "Add_members": "Ajouter des membres", "add-livechat-department-agents": "Ajouter des agents omnicanaux aux départements", "add-livechat-department-agents_description": "Autorisation d'ajouter des agents omnicanaux aux départements", @@ -370,6 +372,8 @@ "API_Enable_Rate_Limiter_Dev": "Activer le limiteur de débit en développement", "API_Enable_Rate_Limiter_Dev_Description": "Faut-il limiter le nombre d'appels aux points de terminaison dans l'environnement de développement ?", "API_Enable_Rate_Limiter_Limit_Calls_Default": "Le numéro par défaut appelle le limiteur de débit", + "Rate_Limiter_Limit_RegisterUser": "Nombre d'appels par défaut au limiteur de débit pour l'enregistrement d'un utilisateur", + "Rate_Limiter_Limit_RegisterUser_Description": "Nombre d'appels par défaut pour les points de terminaison d'enregistrement des utilisateursr (API REST et en temps réel), autorisées dans la plage de temps définie dans la section limiteur de taux API.", "API_Enable_Rate_Limiter_Limit_Calls_Default_Description": "Nombre d'appels par défaut pour chaque point de terminaison de l'API REST, autorisés dans la plage de temps définie ci-dessous", "API_Enable_Rate_Limiter_Limit_Time_Default": "Limite de temps par défaut pour le limiteur de débit (en ms)", "API_Enable_Rate_Limiter_Limit_Time_Default_Description": "Délai d'expiration par défaut pour limiter le nombre d'appels à chaque point de terminaison de l'API REST (en ms)", @@ -385,13 +389,14 @@ "API_Personal_Access_Tokens_Regenerate_Modal": "Si vous avez perdu ou oublié votre jeton, vous pouvez le régénérer, mais n'oubliez pas que toutes les applications utilisant ce jeton doivent être mises à jour", "API_Personal_Access_Tokens_Remove_Modal": "Voulez-vous vraiment supprimer ce jeton d'accès personnel ?", "API_Personal_Access_Tokens_To_REST_API": "Jetons d'accès personnels à l'API REST", + "API_Rate_Limiter": "Limiteur de taux API (API Rate Limiter)", "API_Shield_Types": "Types de bouclier", "API_Shield_Types_Description": "Types de boucliers à activer en tant que liste séparée par des virgules, choisissez parmi `online`,` channel` ou `*` pour tout", "API_Shield_user_require_auth": "Exiger une authentification pour les boucliers des utilisateurs", "API_Token": "Jeton API", "API_Tokenpass_URL": "URL du serveur Tokenpass", "API_Tokenpass_URL_Description": "Exemple : https://domain.com (ne pas mettre le / de fin)", - "API_Upper_Count_Limit": "Nombre maximum d'enregistrements", + "API_Upper_Count_Limit": "Nombre maximal d'enregistrements", "API_Upper_Count_Limit_Description": "Quel est le nombre maximum d'enregistrements que REST API doit retourner (si pas défini en tant qu'illimité) ?", "API_Use_REST_For_DDP_Calls": "Utilisez REST au lieu de websocket pour les appels Meteor", "API_User_Limit": "Limite de l'utilisateur pour ajouter tous les utilisateurs au canal", @@ -415,7 +420,7 @@ "App_status_manually_disabled": "Désactivé : manuellement", "App_status_manually_enabled": "Activé", "App_status_unknown": "Inconnu", - "App_support_url": "support url", + "App_support_url": "URL de support", "App_Url_to_Install_From": "Installer depuis l'URL", "App_Url_to_Install_From_File": "Installer à partir d'un fichier", "App_user_not_allowed_to_login": "Les utilisateurs de l'application ne sont pas autorisés à se connecter directement.", @@ -550,7 +555,7 @@ "assign-roles": "Attribuer des rôles", "assign-roles_description": "Autorisation d'attribuer des rôles à d'autres utilisateurs", "at": "à", - "At_least_one_added_token_is_required_by_the_user": "Au moins un jeton ajouté est requis par l'utilisateur", + "At_least_one_added_token_is_required_by_the_user": "L'utilisateur a besoin d'au moins un jeton ajouté", "AtlassianCrowd": "Atlassian Crowd", "Attachment_File_Uploaded": "Fichier envoyé", "Attribute_handling": "Gestion des attributs", @@ -678,7 +683,7 @@ "Browse_Files": "Parcourir les fichiers", "Browser_does_not_support_audio_element": "Votre navigateur ne supporte pas l'élément audio.", "Browser_does_not_support_video_element": "Votre application ne supporte pas le format de l'élément vidéo.", - "Bugsnag_api_key": "Clé d'API Bugsnag", + "Bugsnag_api_key": "Clé API Bugsnag", "Build_Environment": "Construire l'environnement", "bulk-register-user": "Créer des utilisateurs en masse", "bulk-register-user_description": "Permission de créer des utilisateurs en masse", @@ -701,6 +706,9 @@ "By_author": "Par __author__", "cache_cleared": "Cache effacé", "Call": "Appel", + "Call_declined": "Appel refusé !", + "Call_provider": "Fournisseur d'appels", + "Call_Already_Ended": "Appel déjà terminé", "call-management": "Gestion des appels", "call-management_description": "Autorisation de démarrer une réunion", "Caller": "Appelant", @@ -729,7 +737,7 @@ "CAS_Creation_User_Enabled": "Autoriser la création d'utilisateurs", "CAS_Creation_User_Enabled_Description": "Autoriser la création d'utilisateurs CAS à partir des données fournies dans le ticket CAS.", "CAS_enabled": "Activé", - "CAS_Login_Layout": "Disposition de connexion CAS", + "CAS_Login_Layout": "Présentation de la connexion CAS", "CAS_login_url": "URL de login SSO", "CAS_login_url_Description": "URL de connexion pour votre service externe de connexion SSO, par exemple : https://sso.example.undef/sso/login", "CAS_popup_height": "Hauteur de la fenêtre popup de connexion", @@ -761,7 +769,7 @@ "Channel_doesnt_exist": "Le canal `#%s` n'existe pas.", "Channel_Export": "Exportation de canal", "Channel_name": "Nom du canal", - "Channel_Name_Placeholder": "Veuillez entrer le nom du canal...", + "Channel_Name_Placeholder": "Veuillez saisir le nom du canal...", "Channel_to_listen_on": "Canal à écouter", "Channel_Unarchived": "Canal avec le nom `#%s` a été désarchivé avec succès", "Channels": "Canaux", @@ -770,7 +778,7 @@ "Channels_list": "Liste des canaux publics", "Channel_what_is_this_channel_about": "De quoi parle ce canal ?", "Chart": "Graphique", - "Chat_button": "Bouton chat", + "Chat_button": "Bouton de chat", "Chat_close": "Fermer chat", "Chat_closed": "Chat fermé", "Chat_closed_by_agent": "Chat fermé par l'agent", @@ -799,7 +807,7 @@ "Chatpal_Base_URL": "Url de base", "Chatpal_Base_URL_Description": "Trouver une description de la façon d'exécuter une instance locale sur github. L'URL doit être absolue et pointer vers le noyau chatpal, par ex. http://localhost:8983/solr/chatpal.", "Chatpal_Batch_Size": "Taille du lot d'index", - "Chatpal_Batch_Size_Description": "La taille du lot des documents d'index (lors de l'amorçage)", + "Chatpal_Batch_Size_Description": "La taille du lot de documents d'index (lors de l'amorçage)", "Chatpal_channel_not_joined_yet": "Canal pas encore rejoint", "Chatpal_create_key": "Créer une clé", "Chatpal_created_key_successfully": "Clé d'API créée avec succès", @@ -815,7 +823,7 @@ "Chatpal_Get_more_information_about_chatpal_on_our_website": "Obtenez plus d'informations sur Chatpal sur http://chatpal.io!", "Chatpal_go_to_message": "Sauter", "Chatpal_go_to_room": "Sauter", - "Chatpal_go_to_user": "Envoyer un message privé", + "Chatpal_go_to_user": "Envoyer un message direct", "Chatpal_HTTP_Headers": "En-têtes HTTP", "Chatpal_HTTP_Headers_Description": "Liste des en-têtes HTTP, un en-tête par ligne. Format: nom: valeur", "Chatpal_Include_All_Public_Channels": "Inclure tous les canaux publics", @@ -829,10 +837,10 @@ "Chatpal_no_search_results": "Aucun résultat", "Chatpal_one_search_result": "1 résultat trouvé", "Chatpal_Rooms": "Salons", - "Chatpal_run_search": "Recherche", + "Chatpal_run_search": "Rechercher", "Chatpal_search_page_of": "Page %s sur %s", "Chatpal_search_results": "%s résultats trouvés ", - "Chatpal_Search_Results": "Résultats de la recherche", + "Chatpal_Search_Results": "Résultats de recherche", "Chatpal_Suggestion_Enabled": "Suggestions activées", "Chatpal_TAC_read": "J'ai lu les termes et conditions", "Chatpal_Terms_and_Conditions": "Termes et conditions", @@ -1265,7 +1273,7 @@ "Created_as": "Créé comme", "Created_at": "Créé le", "Created_at_s_by_s": "Créé le %s par %s", - "Created_at_s_by_s_triggered_by_s": "Créé à %s en %s déclenché par %s", + "Created_at_s_by_s_triggered_by_s": "Créé à %s par %s déclenché par %s", "Created_by": "Créé par", "CRM_Integration": "Intégration CRM (GRC)", "CROWD_Allow_Custom_Username": "Autoriser le nom d'utilisateur personnalisé dans Rocket.Chat", @@ -1340,6 +1348,7 @@ "Days": "Jours", "DB_Migration": "Mise à jour de la base de données", "DB_Migration_Date": "Date de mise à jour de la base de données", + "DDP_Rate_Limit": "Limite du taux DDP", "DDP_Rate_Limit_Connection_By_Method_Enabled": "Limite par connexion par méthode : activée", "DDP_Rate_Limit_Connection_By_Method_Interval_Time": "Limite par connexion par méthode : temps d'intervalle", "DDP_Rate_Limit_Connection_By_Method_Requests_Allowed": "Limite par connexion par méthode : demandes autorisées", @@ -1369,7 +1378,7 @@ "Delete_Role_Warning": "La suppression d'un rôle est définitive. Elle ne peut être annulée.", "Delete_Room_Warning": "Supprimer un salon supprimera également tous les messages postés dans le salon. Cette action est irréversible.", "Delete_User_Warning": "Supprimer un utilisateur va également supprimer tous les messages de celui-ci. Cette action est irréversible.", - "Delete_User_Warning_Delete": "Supprimer un utilisateur va également supprimer tous les messages de celui-ci. Cette action est irréversible.", + "Delete_User_Warning_Delete": "Supprimer un utilisateur supprimera également tous les messages de celui-ci. Cette action est irréversible.", "Delete_User_Warning_Keep": "L'utilisateur sera supprimé, mais ses messages resteront visibles. Ça ne peut pas être annulé.", "Delete_User_Warning_Unlink": "Supprimer un utilisateur supprimera le nom d'utilisateur de tous ses messages. Ça ne peut pas être annulé.", "delete-c": "Supprimer les canaux publics", @@ -1396,7 +1405,7 @@ "Desktop": "Bureau", "Desktop_Notification_Test": "Test des notifications sur le bureau", "Desktop_Notifications": "Notifications de bureau", - "Desktop_Notifications_Default_Alert": "Alerte notification de bureau par défaut", + "Desktop_Notifications_Default_Alert": "Alerte par défaut pour les notifications de bureau", "Desktop_Notifications_Disabled": "Les notifications de bureau sont désactivées, Modifiez les préférences de votre navigateur si vous avez besoin de les activer.", "Desktop_Notifications_Duration": "Durée des notifications de bureau", "Desktop_Notifications_Duration_Description": "Secondes pour afficher une notification de bureau. Cela peut affecter le Centre de Notification de OS X. Entrez 0 pour utiliser les paramètres du navigateur par défaut et ne pas affecter le Centre de Notification de OS X.", @@ -1609,6 +1618,8 @@ "Encryption_key_saved_successfully": "Votre clé de chiffrement a été enregistrée avec succès.", "EncryptionKey_Change_Disabled": "Vous ne pouvez pas définir de mot de passe pour votre clé de chiffrement car votre clé privée n'est pas présente sur ce client. Afin de définir un nouveau mot de passe, vous devez charger votre clé privée en utilisant votre mot de passe existant ou utiliser un client où la clé est déjà chargée.", "End": "Fin", + "End_call": "Mettre fin à l'appel", + "Expand_view": "Agrandir la vue", "End_OTR": "Arrêter OTR", "Engagement_Dashboard": "Tableau de bord d'engagement", "Enter": "Entrer", @@ -1750,7 +1761,7 @@ "error-password-same-as-current": "Mot de passe saisi identique au mot de passe actuel", "error-personal-access-tokens-are-current-disabled": "Les jetons d'accès personnels sont actuellement désactivés", "error-pinning-message": "Le message n'a pas pu être épinglé", - "error-push-disabled": "Push est désactivé", + "error-push-disabled": "Le Push est désactivé", "error-remove-last-owner": "Cet utilisateur est le dernier propriétaire. Veuillez sélectionner un nouveau propriétaire avant de retirer celui-ci.", "error-returning-inquiry": "Erreur lors du renvoi de la demande dans la file d'attente", "error-role-in-use": "Impossible de supprimer le rôle car il est utilisé", @@ -1836,7 +1847,9 @@ "Favorite": "Favori", "Favorite_Rooms": "Activer les salons favoris", "Favorites": "Favoris", + "Feature_depends_on_selected_call_provider_to_be_enabled_from_administration_settings": "Cette fonction dépend du fournisseur d'appel sélectionné ci-dessus à activer à partir des paramètres d'administration.", "Feature_Depends_on_Livechat_Visitor_navigation_as_a_message_to_be_enabled": "Cette fonctionnalité dépend de l'activation de \"Envoyer l'historique de navigation des visiteurs sous forme de message\".", + "Feature_Limiting": "Limitation des fonctionnalités", "Features": "Fonctionnalités", "Features_Enabled": "Fonctionnalités activées", "Feature_Disabled": "Fonction désactivée", @@ -2012,7 +2025,7 @@ "force-delete-message_description": "Autorisation de supprimer un message en contournant toutes les restrictions", "Forgot_password": "Mot de passe oublié ?", "Forgot_Password_Description": "Vous pouvez utiliser les espaces réservés suivants :
      • [Forgot_Password_Url] pour l'URL de récupération du mot de passe.
      • [nom], [fname], [lname] pour le nom complet, le prénom ou le nom de famille de l'utilisateur, respectivement.
      • [email] pour l'adresse e-mail de l'utilisateur.
      • [Site_Name] et [Site_URL] pour le nom de l'application et l'URL, respectivement.
      ", - "Forgot_Password_Email": "Cliquez ici pour remettre à zéro votre mot de passe.", + "Forgot_Password_Email": "Cliquez ici pour réinitialiser votre mot de passe.", "Forgot_Password_Email_Subject": "[Site_Name] - Récupération du mot de passe", "Forgot_password_section": "Mot de passe oublié", "Forward": "Transmettre", @@ -2073,7 +2086,7 @@ "Healthcare": "Soins de santé", "Helpers": "Aides", "Here_is_your_authentication_code": "Voici votre code d'authentification :", - "Hex_Color_Preview": "Aperçu des couleurs hexadécimales", + "Hex_Color_Preview": "Aperçu de la couleur hexadécimale", "Hi": "Salut", "Hi_username": "Bonjour __name__", "Hidden": "Caché", @@ -2101,10 +2114,10 @@ "Hours": "Heures", "How_friendly_was_the_chat_agent": "Votre interlocuteur était-il amical ?", "How_knowledgeable_was_the_chat_agent": "Votre interlocuteur était-il clair ?", - "How_long_to_wait_after_agent_goes_offline": "Combien de temps attendre après que l'agent soit hors ligne", + "How_long_to_wait_after_agent_goes_offline": "Combien de temps attendre une fois que l'agent est hors ligne", "How_long_to_wait_to_consider_visitor_abandonment": "Combien de temps faut-il attendre avant d'envisager l'abandon d'un visiteur?", "How_long_to_wait_to_consider_visitor_abandonment_in_seconds": "Combien de temps faut-il attendre avant d'envisager l'abandon d'un visiteur?", - "How_responsive_was_the_chat_agent": "Quel a été la réactivité de l'agent de chat ?", + "How_responsive_was_the_chat_agent": "Quelle a été la réactivité de l'agent de chat ?", "How_satisfied_were_you_with_this_chat": "Étiez-vous satisfait de ce chat?", "How_to_handle_open_sessions_when_agent_goes_offline": "Comment gérer les sessions ouvertes lorsque l'agent est déconnecté", "I_Saved_My_Password": "J'ai enregistré mon mot de passe", @@ -2116,7 +2129,7 @@ "If_you_are_sure_type_in_your_username": "Si vous êtes certain(e), saisissez votre nom d'utilisateur :", "If_you_didnt_ask_for_reset_ignore_this_email": "Si vous n'avez pas demandé la réinitialisation de votre mot de passe, vous pouvez ignorer cet e-mail.", "If_you_didnt_try_to_login_in_your_account_please_ignore_this_email": "Si vous n'avez pas essayé de vous connecter à votre compte, veuillez ignorer cet e-mail.", - "If_you_dont_have_one_send_an_email_to_omni_rocketchat_to_get_yours": "Si vous n'en avez pas, envoyez un courriel à [omni@rocket.chat](mailto: omni@rocket.chat) pour obtenir le vôtre.", + "If_you_dont_have_one_send_an_email_to_omni_rocketchat_to_get_yours": "Si vous n'en avez pas, envoyez un e-mail à [omni@rocket.chat](mailto: omni@rocket.chat) pour obtenir le vôtre.", "Iframe_Integration": "Intégration Iframe", "Iframe_Integration_receive_enable": "Activer la réception", "Iframe_Integration_receive_enable_Description": "Autoriser la fenêtre parente à envoyer des commandes à Rocket.Chat.", @@ -2149,7 +2162,7 @@ "Importer_finishing": "Finalisation de l'importation.", "Importer_From_Description": "Importer les données de __from__ dans Rocket.Chat.", "Importer_HipChatEnterprise_BetaWarning": "Veuillez noter que cette importation est toujours en cours de développement, veuillez signaler toute erreur qui se produit dans GitHub :", - "Importer_HipChatEnterprise_Information": "Le fichier téléchargé doit être un tar.gz déchiffré, veuillez lire la documentation pour plus d'informations :", + "Importer_HipChatEnterprise_Information": "Le fichier envoyé doit être un tar.gz déchiffré, veuillez lire la documentation pour plus d'informations :", "Importer_import_cancelled": "Importation annulée.", "Importer_import_failed": "Une erreur est survenue lors de l'importation.", "Importer_importing_channels": "Importation des canaux.", @@ -2205,7 +2218,7 @@ "Install": "Installer", "Install_Extension": "Installer l'extension", "Install_FxOs": "Installez Rocket.Chat sur votre Firefox", - "Install_FxOs_done": "Génial ! Vous pouvez désormais utiliser Rocket.Chat via l'icône sur votre écran d'accueil. Amusez-vous avec Rocket.Chat !", + "Install_FxOs_done": "Génial ! Vous pouvez maintenant utiliser Rocket.Chat via l'icône sur votre écran d'accueil. Amusez-vous avec Rocket.Chat !", "Install_FxOs_error": "Désolé, cela n'a pas fonctionné comme prévu ! L'erreur suivante est apparue :", "Install_FxOs_follow_instructions": "Veuillez confirmer l'installation de l'application sur votre appareil (appuyez sur \"Installer\" lorsque c'est demandé).", "Install_package": "Installer le paquet", @@ -2215,7 +2228,7 @@ "Instance": "Instance", "Instances": "Instances", "Instances_health": "Santé des instances", - "Instance_Record": "Instance Record", + "Instance_Record": "Enregistrement d'instance", "Instructions": "Instructions", "Instructions_to_your_visitor_fill_the_form_to_send_a_message": "Instructions à votre visiteur remplissez le formulaire pour envoyer un message", "Insert_Contact_Name": "Insérez le nom du contact", @@ -2292,7 +2305,7 @@ "Invitation": "Invitation", "Invitation_Email_Description": "Vous pouvez utiliser les espaces réservés suivants :
      • [email] pour l'adresse e-mail du destinataire,
      • [Site_Name] et [Site_URL] pour le nom de l'application et l'URL respectivement.
      ", "Invitation_HTML": "HTML d'invitation", - "Invitation_HTML_Default": "

      Vous avez été invité à rejoindre [Site_Name]

      Accédez à [Site_URL] et essayez la meilleure solution de chat open source disponible aujourd'hui !

      ", + "Invitation_HTML_Default": "

      Vous avez été invité à rejoindre [Site_Name]

      Allez sur [Site_URL] et essayez la meilleure solution de chat open source disponible aujourd'hui !

      ", "Invitation_Subject": "Sujet de l'invitation", "Invitation_Subject_Default": "Vous avez été invité à [Site_Name]", "Invite": "Inviter", @@ -2339,12 +2352,14 @@ "Jitsi_Limit_Token_To_Room": "Limiter le jeton au salon Jitsi", "Job_Title": "Titre d'emploi", "join": "Rejoindre", + "Join_call": "Rejoindre l'appel", "Join_audio_call": "Rejoindre l'appel audio", "Join_Chat": "Rejoindre le chat", "Join_default_channels": "Rejoindre les canaux par défaut", "Join_the_Community": "Rejoignez la communauté", "Join_the_given_channel": "Rejoindre le canal choisi", "Join_video_call": "Rejoindre l'appel vidéo", + "Join_my_room_to_start_the_video_call": "Rejoignez mon salon pour démarrer l'appel vidéo", "join-without-join-code": "Rejoindre sans code d'adhésion", "join-without-join-code_description": "Autorisation de contourner le code de participation dans les canaux avec le code de participation activé", "Joined": "A rejoint", @@ -2353,7 +2368,7 @@ "Jump_to_first_unread": "Aller au premier non lu", "Jump_to_message": "Aller au message", "Jump_to_recent_messages": "Aller aux messages récents", - "Just_invited_people_can_access_this_channel": "Seules les personnes invitées peuvent accéder à cette chaîne.", + "Just_invited_people_can_access_this_channel": "Seules les personnes invitées peuvent accéder à ce canal.", "Katex_Dollar_Syntax": "Autoriser la syntaxe dollar", "Katex_Dollar_Syntax_Description": "Autoriser les syntaxes : $$bloc katex$$ et $katex inline$", "Katex_Enabled": "Katex activé", @@ -2373,7 +2388,7 @@ "Keyboard_Shortcuts_Mark_all_as_read": "Marquer tous les messages (dans tous les canaux) comme lus", "Keyboard_Shortcuts_Move_To_Beginning_Of_Message": "Aller au début du message", "Keyboard_Shortcuts_Move_To_End_Of_Message": "Aller à la fin du message", - "Keyboard_Shortcuts_New_Line_In_Message": "Nouvelle ligne dans l'entrée de composition du message", + "Keyboard_Shortcuts_New_Line_In_Message": "Nouvelle ligne dans l'entrée de composition de message", "Keyboard_Shortcuts_Open_Channel_Slash_User_Search": "Ouvrir la recherche de canal / utilisateur", "Keyboard_Shortcuts_Title": "Raccourcis clavier", "Knowledge_Base": "Base de connaissances", @@ -2552,6 +2567,10 @@ "LDAP_Sync_User_Data_Roles_Filter_Description": "Le filtre de recherche LDAP utilisé pour vérifier si un utilisateur fait partie d'un groupe.", "LDAP_Sync_User_Data_RolesMap": "Carte des groupes de données utilisateur", "LDAP_Sync_User_Data_RolesMap_Description": "Mappez les groupes LDAP aux rôles des utilisateurs Rocket.Chat
      Par exemple, `{\"rocket-admin\":\"admin\", \"tech-support\":\"support\"}` mappera le groupe LDAP rocket-admin au rôle \"admin\" de Rocket.", + "LDAP_Teams_BaseDN": "LDAP teams BaseDN", + "LDAP_Teams_BaseDN_Description": "Le LDAP BaseDN utilisé pour rechercher des équipes d'utilisateurs.", + "LDAP_Teams_Name_Field": "Attribut de nom d'équipe LDAP", + "LDAP_Teams_Name_Field_Description": "L'attribut LDAP que Rocket.Chat doit utiliser pour charger le nom de l'équipe. Vous pouvez spécifier plusieurs noms d'attributs possibles si vous les séparez par une virgule.", "LDAP_Timeout": "Délai d'attente (ms)", "LDAP_Timeout_Description": "Combien de millisecondens faut-il attendre pour obtenir un résultat de recherche avant de renvoyer une erreur", "LDAP_Unique_Identifier_Field": "Champ d'identifiant unique", @@ -2665,6 +2684,7 @@ "Livechat_Triggers": "Déclencheurs de chat en direct", "Livechat_user_sent_chat_transcript_to_visitor": "__agent__ a envoyé la transcription du chat à __guest__", "Livechat_Users": "Utilisateurs omnicanal", + "Livechat_Calls": "Appels de chat en direct", "Livechat_visitor_email_and_transcript_email_do_not_match": "L'e-mail du visiteur et l'e-mail de la transcription ne correspondent pas", "Livechat_visitor_transcript_request": "__guest__ a demandé la transcription du chat", "LiveStream & Broadcasting": "LiveStream et diffusion", @@ -2975,6 +2995,8 @@ "Mobex_sms_gateway_restful_address_desc": "IP ou hôte de votre REST API Mobex, par exemple `http://192.168.1.1:8080` ou `https://www.example.com:8080`", "Mobex_sms_gateway_username": "Nom d'utilisateur", "Mobile": "Mobile", + "mobile-download-file": "Autoriser le téléchargement de fichiers sur les appareils mobiles", + "mobile-upload-file": "Autoriser l'envoi de fichiers sur les appareils mobiles", "Mobile_Push_Notifications_Default_Alert": "Alerte par défaut des notifications push", "Monday": "Lundi", "Mongo_storageEngine": "Moteur de stockage Mongo", @@ -3005,6 +3027,7 @@ "Mute_Group_Mentions": "Ignorer les mentions @all et @here", "Mute_someone_in_room": "Rendre quelqu'un muet dans ce salon", "Mute_user": "Rendre l'utilisateur muet", + "Mute_microphone": "Couper le micro", "mute-user": "Rendre l'utilisateur muet", "mute-user_description": "Autorisation de couper le son des autres utilisateurs du même canal", "Muted": "En sourdine", @@ -3172,6 +3195,8 @@ "Omnichannel": "Omnicanal", "Omnichannel_Directory": "Annuaire omnicanal", "Omnichannel_appearance": "Apparence omnicanal", + "Omnichannel_calculate_dispatch_service_queue_statistics": "Calculer et diffuser les statistiques de file d'attente omnicanal", + "Omnichannel_calculate_dispatch_service_queue_statistics_Description": "Traitement et distribution des statistiques de file d'attente telles que la position et le temps d'attente estimé. Si *Livechat canal* n'est pas utilisé, il est recommandé de désactiver ce paramètre et d'empêcher le serveur d'effectuer des processus inutiles.", "Omnichannel_Contact_Center": "Centre de contact omnicanal", "Omnichannel_contact_manager_routing": "Attribuer de nouvelles conversations au gestionnaire de contacts", "Omnichannel_contact_manager_routing_Description": "Ce paramètre attribue un chat au gestionnaire de contacts attribué, tant que le gestionnaire de contacts est en ligne lors que le chat commence", @@ -3182,6 +3207,7 @@ "Omnichannel_External_Frame_URL": "URL du cadre externe", "On": "Sur", "On_Hold_Chats": "En attente", + "On_Hold_conversations": "Conversations en attente", "online": "en ligne", "Online": "Connecté", "Only_authorized_users_can_write_new_messages": "Seuls les utilisateurs autorisés peuvent écrire de nouveaux messages", @@ -3398,6 +3424,7 @@ "Query_description": "Conditions supplémentaires pour déterminer à quels utilisateurs envoyer l'e-mail. Les utilisateurs désabonnés sont automatiquement supprimés de la requête. La requête doit être au format JSON. Exemple : \"{\"createdAt\":{\"$gt\":{\"$date\": \"2015-01-01T00:00:00.000Z\"}}}\"", "Query_is_not_valid_JSON": "La requête n'est pas valide JSON", "Queue": "File d'attente", + "Queue_delay_timeout": "Délai d'attente du traitement de la file d'attente", "Queue_Time": "Temps de file d'attente", "Queue_management": "Gestion des files d'attente", "quote": "citation", @@ -3903,7 +3930,7 @@ "Smileys_and_People": "Émojis & Portraits", "SMS": "SMS", "SMS_Default_Omnichannel_Department": "Département omnicanal (par défaut)", - "SMS_Default_Omnichannel_Department_Description": "S'il est défini, toutes les nouvelles discussions entrantes, initiées par cette intégration, seront acheminées vers ce département.", + "SMS_Default_Omnichannel_Department_Description": "S'il est défini, tous les nouveaux chats entrants initiés par cette intégration seront acheminés vers ce département.\nCe paramètre peut être écrasé en passant le paramètre de requête department dans la demande.\nExemple : https:///api/v1/livechat/sms-incoming/twilio?department=.\nRemarque : si vous utilisez le nom du département, l'URL doit être sécurisée.", "SMS_Enabled": "SMS activés", "SMTP": "SMTP", "SMTP_Host": "Hôte SMTP", @@ -4277,6 +4304,8 @@ "Tuesday": "Mardi", "Turn_OFF": "Éteindre", "Turn_ON": "Allumer", + "Turn_on_video": "Activer la vidéo", + "Turn_off_video": "Désactiver la vidéo", "Two Factor Authentication": "Authentification à deux facteurs", "Two-factor_authentication": "Authentification à deux facteurs via TOTP", "Two-factor_authentication_disabled": "Authentification à deux facteurs désactivée", @@ -4338,6 +4367,7 @@ "Unread_Rooms_Mode": "Mode des salons non-lus", "Unread_Tray_Icon_Alert": "Icône d'alerte dans la barre de tâches pour les messages non lus", "Unstar_Message": "Supprimer des favoris", + "Unmute_microphone": "Activer le micro", "Update": "Mettre à jour", "Update_EnableChecker": "Activer la vérification des mises à jour", "Update_EnableChecker_Description": "Vérifie automatiquement les nouvelles mises à jour / messages importants des développeurs Rocket.Chat et reçoit des notifications lorsqu'elles sont disponibles. La notification apparaît une fois par nouvelle version sous forme de bannière cliquable et de message du bot Rocket.Cat, tous deux visibles uniquement pour les administrateurs.", @@ -4365,7 +4395,7 @@ "Usage": "Utilisation", "Use": "Utiliser", "Use_account_preference": "Utiliser les préférences du compte", - "Use_Emojis": "Utiliser les émoticônes", + "Use_Emojis": "Utiliser les Emojis", "Use_Global_Settings": "Utiliser les paramètres globaux", "Use_initials_avatar": "Utiliser les initiales de votre nom d'utilisateur", "Use_minor_colors": "Utiliser une palette de couleurs mineure (les valeurs par défaut héritent des couleurs principales)", @@ -4498,6 +4528,7 @@ "UTF8_User_Names_Validation_Description": "RegExp qui sera utilisé pour valider les noms d'utilisateur", "UTF8_Channel_Names_Validation": "Validation des noms de canaux UTF8", "UTF8_Channel_Names_Validation_Description": "RegExp qui sera utilisé pour valider les noms de canaux", + "Videocall_enabled": "Appel vidéo activé", "Validate_email_address": "Valider l'adresse e-mail", "Validation": "Validation", "Value_messages": "__value__ messages", @@ -4508,7 +4539,7 @@ "Verification_email_body": "Vous avez créé un compte avec succès sur [Site_Name]. Cliquez sur le bouton ci-dessous pour confirmer votre adresse e-mail et terminer votre inscription.", "Verification_email_sent": "E-mail de vérification envoyé", "Verification_Email_Subject": "[Site_Name] - Vérification de l'adresse e-mail", - "Verified": "Vérifié", + "Verified": "Vérifié(e)", "Verify": "Vérifier", "Verify_your_email": "Vérifiez votre e-mail", "Verify_your_email_for_the_code_we_sent": "Vérifiez votre e-mail pour le code que nous avons envoyé", @@ -4519,10 +4550,12 @@ "Video_Conference": "Conférence vidéo", "Video_message": "Message vidéo", "Videocall_declined": "Appel vidéo refusé.", - "Videocall_enabled": "Appel vidéo activé", + "Video_and_Audio_Call": "Appel vidéo et audio", "Videos": "Vidéos", "View_All": "Voir tous les membres", "View_channels": "Afficher les canaux", + "view-omnichannel-contact-center": "Afficher le centre de contact omnicanal", + "view-omnichannel-contact-center_description": "Autorisation d'afficher et d'interagir avec le centre de contact omnicanal", "View_Logs": "Voir les logs (journaux)", "View_mode": "Mode d'affichage", "View_original": "Voir l'original", @@ -4596,6 +4629,7 @@ "Visitor_message": "Messages des visiteurs", "Visitor_Name": "Nom du visiteur", "Visitor_Name_Placeholder": "Veuillez saisir un nom de visiteur...", + "Visitor_does_not_exist": "Le visiteur n'existe pas !", "Visitor_Navigation": "Navigation des visiteur", "Visitor_page_URL": "Page d'accueil du visiteur (URL)", "Visitor_time_on_site": "Temps des visiteurs sur le site", @@ -4623,6 +4657,7 @@ "Webhook_Details": "Détails du WebHook", "Webhook_URL": "Webhook URL", "Webhooks": "Webhooks", + "WebRTC_Call": "Appel WebRTC", "WebRTC_direct_audio_call_from_%s": "Appel audio direct de %s", "WebRTC_direct_video_call_from_%s": "Appel vidéo direct de %s", "WebRTC_Enable_Channel": "Activer pour les canaux publics", @@ -4633,6 +4668,8 @@ "WebRTC_monitor_call_from_%s": "Surveiller l'appel de %s", "WebRTC_Servers": "Serveurs STUN/TURN", "WebRTC_Servers_Description": "Une liste de serveurs STUN et TURN séparés par une virgule.
      Vous pouvez utiliser utilisateur, mot de passe et port selon le format `utilisateur:motdepasse@stun:hôte:port` ou `utilisateur:motdepasse@turn:hôte:port`.", + "WebRTC_call_ended_message": " Appel terminé à __endTime__ - Durée __callDuration__", + "WebRTC_call_declined_message": " Appel refusé par le contact.", "Website": "Site Internet", "Wednesday": "Mercredi", "Weekly_Active_Users": "Utilisateurs actifs chaque semaine", @@ -4717,10 +4754,10 @@ "Your_new_email_is_email": "Votre nouvelle adresse mail est [email].", "Your_password_is_wrong": "Votre mot de passe est incorrect !", "Your_password_was_changed_by_an_admin": "Votre mot de passe a été changé par un administrateur.", - "Your_push_was_sent_to_s_devices": "Votre notification a été envoyée à %s appareils", + "Your_push_was_sent_to_s_devices": "Votre push a été envoyé à %s appareils", "Your_question": "Votre question", "Your_server_link": "Le lien de votre serveur", "Your_temporary_password_is_password": "Votre mot de passe temporaire est [password].", "Your_TOTP_has_been_reset": "Votre TOTP à deux facteurs a été réinitialisé.", - "Your_workspace_is_ready": "Votre espace de travail est prêt à l'emploi 🎉" -} + "Your_workspace_is_ready": "Votre espace de travail est prêt à être utilisé 🎉" +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/gl.i18n.json b/packages/rocketchat-i18n/i18n/gl.i18n.json index 6c7048846587d..8bcb59556fc3f 100644 --- a/packages/rocketchat-i18n/i18n/gl.i18n.json +++ b/packages/rocketchat-i18n/i18n/gl.i18n.json @@ -184,8 +184,8 @@ "Type_your_message": "Escribe a túa mensaxe", "Unpin_Message": "Anular fixación da mensaxe", "User_Info": "Información do usuario", - "Videocall_declined": "Videochamada rexeitada.", "Videocall_enabled": "Videochamada habilitada", + "Videocall_declined": "Videochamada rexeitada.", "view-full-other-user-info": "Ver toda a información do usuario", "You_can_close_this_window_now": "Xa podes pechar esta ventá.", "You_can_use_an_emoji_as_avatar": "Tamén podes usar un emoji como avatar.", diff --git a/packages/rocketchat-i18n/i18n/he.i18n.json b/packages/rocketchat-i18n/i18n/he.i18n.json index c19169fedd627..e81fe86c3a3ee 100644 --- a/packages/rocketchat-i18n/i18n/he.i18n.json +++ b/packages/rocketchat-i18n/i18n/he.i18n.json @@ -42,6 +42,7 @@ "Accounts_EmailVerification": "אימות דוא״ל", "Accounts_EmailVerification_Description": "בדוק שיש לך הגדרות SMTP נכונות כדי להשתמש בתכונה זו", "Accounts_Enrollment_Email": "אימייל הרשמה", + "Accounts_Enrollment_Email_Description": "אתה יכול להשתמש [name], [fname], [lname] עבור השם המלא של המשתמש, שם פרטי או שם משפחה, בהתאמה.
      אתה יכול להשתמש [email] עבור הדוא\"ל של המשתמש.", "Accounts_Enrollment_Email_Subject_Default": "ברוכים הבאים ל[Site_name]", "Accounts_Iframe_api_method": "שיטת Api", "Accounts_Iframe_api_url": "Url Api", @@ -118,6 +119,7 @@ "Accounts_ShowFormLogin": "טופס מבוסס צג כניסה", "Accounts_UseDefaultBlockedDomainsList": "השתמש בברירת מחדל רשימת Domains חסימה", "Accounts_UseDNSDomainCheck": "הסימון השתמש דומיין DNS", + "Accounts_UserAddedEmail_Description": "הנך רשאי להשתמש המשתנים הבאים:
      • [name], [fname], [lname] עבור השם המלא של המשתמש, שם פרטי או שם משפחה, בהתאמה.
      • [email] עבור הדוא\"ל של המשתמש.
      • [password] להזין את הסיסמה של המשתמש.
      • [Site_Name] ו [Site_URL] עבור שם היישום וה- URL בהתאמה.
      ", "Accounts_UserAddedEmailSubject_Default": "נוספת ל[Site_Name]", "Activate": "הפעל", "Active": "פָּעִיל", @@ -337,6 +339,7 @@ "Continue": "המשך", "Conversation": "שיחה", "Conversation_closed": "בשיחה סגורה: __comment__.", + "Conversation_finished": "שיחת סיים", "Conversation_finished_message": "הודעת סיום שיחה", "conversation_with_s": "השיחה עם s%", "Convert_Ascii_Emojis": "המרת ASCII לאימוג׳י", @@ -459,6 +462,7 @@ "Email_already_exists": "כתובת הדוא״ל כבר קיימת", "Email_body": "גוף הדוא\"ל", "Email_Change_Disabled": "מנהל ה-Rocket.Chat שלך ביטל את שינוי הדוא\"ל", + "Email_Footer_Description": "הנך רשאי להשתמש המשתנים הבאים:
      • [Site_Name] ו [Site_URL] עבור שם היישום וה- URL בהתאמה.
      ", "Email_from": "מאת", "Email_Notification_Mode": "הודעות דוא\"ל כשמשתמשים לא מקוונים", "Email_Notification_Mode_All": "כל אזכור / הודעה פרטית", @@ -656,7 +660,7 @@ "hours": "שעות", "Hours": "שעות", "How_friendly_was_the_chat_agent": "איך ידידותי היה סוכן הצ'אט?", - "How_knowledgeable_was_the_chat_agent": "איך ידע היה סוכן הצ'אט?", + "How_knowledgeable_was_the_chat_agent": "כמה ידע היה לסוכן הצ'אט?", "How_responsive_was_the_chat_agent": "מידת ההיענות היה סוכן צ'אט?", "How_satisfied_were_you_with_this_chat": "איך הייתם מרוצה הצ'אט הזה?", "if_they_are_from": "(אם הם מ%s)", @@ -725,6 +729,7 @@ "Invisible": "בלתי נראה", "Invitation": "הזמנה", "Invitation_HTML": "תבנית HTML להזמנה", + "Invitation_HTML_Default": "

      הוזמנת אל

      [Site_Name]

      עבור אל [Site_URL] ולנסות פתרון הצ'אט פתוח המקור הטוב ביותר הזמינים כיום!

      ", "Invitation_Subject": "נושא ההזמנה", "Invitation_Subject_Default": "הוזמנת ל[Site_Name]", "Invite_user_to_join_channel": "הזמן משתמש להצטרף לחדר", @@ -867,6 +872,7 @@ "Me": "אני", "Media": "מדיה", "Medium": "בינוני", + "Members": "חברים", "Members_List": "רשימת חברים", "mention-all": "תייג הכל", "mention-all_description": "הרשאה להשתמש בתיוג @all", @@ -998,7 +1004,7 @@ "Nothing": "שום דבר", "Nothing_found": "אין תוצאות", "Notification_Desktop_Default_For": "הצג נוטיפיקציות דסקטופ", - "Notifications": "התראות", + "Notifications": "התרעות", "Notifications_Max_Room_Members": "מקסימום חברים בRoom שלאחרם מבטלים את כל הנוטיפיקציות של ההודעות", "Notify_active_in_this_room": "להודיע לכל המחוברים שבחדר", "Notify_all_in_this_room": "להודיע לכל מי שבחדר", @@ -1060,7 +1066,7 @@ "Placeholder_for_password_login_field": "שומר מקום בשדה Login סיסמא", "Please_add_a_comment": "נא להוסיף הערה", "Please_add_a_comment_to_close_the_room": "אנא, הוסף תגובה כדי לסגור את החדר", - "Please_answer_survey": "קדש דק כדי לענות על סקר קצר על הצ'אט הזה", + "Please_answer_survey": "אנא הקדש מספר דקות כדי לענות על סקר קטן בנוגע לשיחה זו", "Please_enter_usernames": "הכנס רשימת משתמשים", "Please_enter_value_for_url": "הכנס ערך עבור הכתובת של תמונת הפרופיל שלך.", "Please_enter_your_new_password_below": "נא להזין את הסיסמה החדשה שלך למטה:", @@ -1198,7 +1204,7 @@ "Select_a_department": "בחירת מחלקה", "Select_an_avatar": "בחירת תמונה", "Select_department": "בחירת מחלקה", - "Select_file": "בחר קובץ", + "Select_file": "בחירת קובץ", "Select_role": "בחר תפקיד", "Select_service_to_login": "יש לבחור בשירות להתחבר דרכו לטעינת התמונה שלך או להעלות אחת ישירות מהמחשב שלך", "Select_user": "בחירת משתמש", @@ -1233,6 +1239,7 @@ "Show_only_online": "הצג רק מחוברים", "Show_preregistration_form": "צג טופס הרשמה מראש", "Showing_archived_results": "

      מציג %s תוצאות בארכיון

      ", + "Showing_online_users": null, "Showing_results": "

      מוצגות %s תוצאות

      ", "since_creation": "מאז %s", "Site_Name": "שם האתר", @@ -1469,6 +1476,7 @@ "Users": "משתמשים", "Users_in_role": "משתמשים בתפקיד", "UTF8_Names_Slugify": "UTF8 שמות Slugify", + "Videocall_enabled": "שיחות וידאו מאופשרות", "Verification_email_sent": "נשלחה הודעת דוא״ל לאימות", "Verified": "מְאוּמָת", "Version": "גרסה", @@ -1476,7 +1484,6 @@ "Video_Chat_Window": "צ'אט וידאו", "Video_Conference": "ועידת וידאו", "Videocall_declined": "שיחות וידאו נדחתה.", - "Videocall_enabled": "שיחות וידאו מאופשרות", "View_All": "הצגת הכול", "View_Logs": "יומנים", "View_mode": "מצב תצוגה", @@ -1552,4 +1559,4 @@ "Your_password_is_wrong": "הסיסמה שלך שגויה!", "Your_push_was_sent_to_s_devices": "הודעת ה-push נשלח בהצלחה ל-%s מכשירים", "Your_question": "השאלה שלך" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/hi-IN.i18n.json b/packages/rocketchat-i18n/i18n/hi-IN.i18n.json index a1aaae598c1ae..e7c19485f7bac 100644 --- a/packages/rocketchat-i18n/i18n/hi-IN.i18n.json +++ b/packages/rocketchat-i18n/i18n/hi-IN.i18n.json @@ -209,6 +209,7 @@ "Type_your_message": "अपना संदेश टाइप करें", "Type_your_name": "अपना नाम लिखें", "Upload_file_question": "दस्तावेज अपलोड करें?", + "User_left": "उपयोगकर्ता छोड़ दिया", "We_are_offline_Sorry_for_the_inconvenience": "हम ऑफ़लाइन हैं। असुविधा के लिए खेद है।", "Yes": "हाँ", "You": "आप" diff --git a/packages/rocketchat-i18n/i18n/hr.i18n.json b/packages/rocketchat-i18n/i18n/hr.i18n.json index b99a18d099640..b11a1485c6660 100644 --- a/packages/rocketchat-i18n/i18n/hr.i18n.json +++ b/packages/rocketchat-i18n/i18n/hr.i18n.json @@ -638,6 +638,7 @@ "Continuous_sound_notifications_for_new_livechat_room": "Neprekidne obavijesti o zvuku za novu sobu za livechat", "Conversation": "Razgovor", "Conversation_closed": "Razgovor je zatvoren: __comment__.", + "Conversation_finished": "Razgovor je završio", "Conversation_finished_message": "Poruka za završetak razgovora", "conversation_with_s": "razgovor s %s", "Conversations": "Razgovori", @@ -1100,6 +1101,7 @@ "Editing_room": "Uređivanje sobe", "Editing_user": "Uređivanje korisnika", "Education": "Obrazovanje", + "Email": "Email", "Email_address_to_send_offline_messages": "Adresa e-pošte za slanje offline poruka", "Email_already_exists": "Email već postoji", "Email_body": "Tijelo emaila", @@ -1818,6 +1820,7 @@ "Max_length_is": "Maksimalna dužina je %s", "Media": "media", "Medium": "Srednji", + "Members": "Članovi", "Members_List": "Lista Članova", "mention-all": "Spominjati sve", "mention-all_description": "Dopuštenje za korištenje @all spomen", @@ -2815,6 +2818,7 @@ "Users_added": "Korisnici su dodani", "Users_in_role": "Korisnici u ulozi", "UTF8_Names_Slugify": "UTF8 Imena Slugify", + "Videocall_enabled": "Videopoziv omogućen", "Validate_email_address": "Validiraj email adresu", "Verification": "Verifikacija", "Verification_Description": "Možete upotrebljavati sljedeća rezervirana mjesta:
      • [Verification_Url] za URL za potvrdu.
      • [ime], [fname], [lname] za puni naziv, ime ili prezime korisnika.
      • [e-pošta] za e-poštu korisnika.
      • [Site_Name] i [Site_URL] za naziv aplikacije i URL.
      ", @@ -2829,7 +2833,6 @@ "Video_Conference": "Video Konferencija", "Video_message": "Video poruka", "Videocall_declined": "Videopoziv odbijen", - "Videocall_enabled": "Videopoziv omogućen", "View_All": "Prikaži Sve", "View_Logs": "Pogledaj izvještaje", "View_mode": "Pregled", diff --git a/packages/rocketchat-i18n/i18n/hu.i18n.json b/packages/rocketchat-i18n/i18n/hu.i18n.json index fc96ff018f393..418a550eefaf7 100644 --- a/packages/rocketchat-i18n/i18n/hu.i18n.json +++ b/packages/rocketchat-i18n/i18n/hu.i18n.json @@ -1,10 +1,12 @@ { "403": "Tiltott", - "500": "Belső kiszolgálóhiba", + "500": "Belső szerver hiba", "__count__empty_rooms_will_be_removed_automatically": "__count__ üres szobát automatikusan eltávolítunk.", "__count__empty_rooms_will_be_removed_automatically__rooms__": "__count__ üres szobát automatikusan eltávolítunk:
      __rooms__.", "__username__is_no_longer__role__defined_by__user_by_": "__user_by__ elvette __username__ __role__ jogosultságát", "__username__was_set__role__by__user_by_": "__user_by__ beállította __username__ __role__ jogosultságát", + "This_room_encryption_has_been_enabled_by__username_": "Ennek a szobának a titkosítását __username__ engedélyezte", + "This_room_encryption_has_been_disabled_by__username_": "Ennek a szobának a titkosítását __username__ letiltotta", "@username": "@felhasználónév", "@username_message": "@felhasználónév ", "#channel": "#csatorna", @@ -28,6 +30,7 @@ "access-permissions": "Hozzáférés a jogosultságok képernyőhöz", "access-permissions_description": "Különböző szerepek jogosultságainak módosítása.", "access-setting-permissions": "Beállításalapú jogosultságok módosítása", + "access-setting-permissions_description": "A beállításalapú engedélyek módosításának engedélyezése", "Accessing_permissions": "Hozzáférési jogosultságok", "Account_SID": "Fiók SID", "Accounts": "Fiókok", @@ -39,6 +42,7 @@ "Accounts_AllowDeleteOwnAccount": "Lehetővé tétel a felhasználóknak a saját fiókjuk törléséhez", "Accounts_AllowedDomainsList": "Engedélyezett tartományok listája", "Accounts_AllowedDomainsList_Description": "Engedélyezett tartományok vesszővel elválasztott listája", + "Accounts_AllowInvisibleStatusOption": "Láthatatlan állapot opció engedélyezése", "Accounts_AllowEmailChange": "E-mail-cím megváltoztatásának engedélyezése", "Accounts_AllowEmailNotifications": "E-mail értesítések engedélyezése", "Accounts_AllowPasswordChange": "Jelszó megváltoztatásának engedélyezése", @@ -96,18 +100,24 @@ "Accounts_OAuth_Custom_Button_Label_Color": "Gomb szövegének színe", "Accounts_OAuth_Custom_Button_Label_Text": "Gomb szövege", "Accounts_OAuth_Custom_Channel_Admin": "Felhasználói adatcsoport térkép", + "Accounts_OAuth_Custom_Channel_Map": "OAuth csoport Channel térkép", "Accounts_OAuth_Custom_Email_Field": "E-mail mező", "Accounts_OAuth_Custom_Enable": "Engedélyezés", + "Accounts_OAuth_Custom_Groups_Claim": "Szerepkörök/csoportok mező a csatornák leképezéséhez", "Accounts_OAuth_Custom_id": "Azonosító", "Accounts_OAuth_Custom_Identity_Path": "Személyazonosság útvonala", "Accounts_OAuth_Custom_Identity_Token_Sent_Via": "Személyazonossági token elküldve ezzel:", + "Accounts_OAuth_Custom_Key_Field": "Kulcsmező", "Accounts_OAuth_Custom_Login_Style": "Bejelentkezés stílusa", + "Accounts_OAuth_Custom_Map_Channels": "Szerepkörök/csoportok hozzárendelése csatornákhoz", "Accounts_OAuth_Custom_Merge_Roles": "Szerepek egyesítése SSO-ból", "Accounts_OAuth_Custom_Merge_Users": "Felhasználók egyesítése", "Accounts_OAuth_Custom_Name_Field": "Név mező", "Accounts_OAuth_Custom_Roles_Claim": "Szerepek vagy csoportok mezőjének neve", + "Accounts_OAuth_Custom_Roles_To_Sync": "Szinkronizálandó szerepkörök", + "Accounts_OAuth_Custom_Roles_To_Sync_Description": "A felhasználó bejelentkezésekor és létrehozásakor szinkronizálandó OAuth-szerepkörök (vesszővel elválasztva).", "Accounts_OAuth_Custom_Scope": "Hatókör", - "Accounts_OAuth_Custom_Secret": "Titok", + "Accounts_OAuth_Custom_Secret": "Titkos kulcs", "Accounts_OAuth_Custom_Show_Button_On_Login_Page": "Gomb megjelenítése a bejelentkezési oldalon", "Accounts_OAuth_Custom_Token_Path": "Token útvonala", "Accounts_OAuth_Custom_Token_Sent_Via": "Token elküldve ezzel:", @@ -137,7 +147,7 @@ "Accounts_OAuth_Google": "Google bejelentkezés", "Accounts_OAuth_Google_callback_url": "Google visszahívási URL", "Accounts_OAuth_Google_id": "Google azonosító", - "Accounts_OAuth_Google_secret": "Google titok", + "Accounts_OAuth_Google_secret": "Google titkos kulcs", "Accounts_OAuth_Linkedin": "LinkedIn bejelentkezés", "Accounts_OAuth_Linkedin_callback_url": "LinkedIn visszahívási URL", "Accounts_OAuth_Linkedin_id": "LinkedIn azonosító", @@ -195,6 +205,8 @@ "Accounts_Registration_AuthenticationServices_Default_Roles": "Hitelesítési szolgáltatások alapértelmezett szerepei", "Accounts_Registration_AuthenticationServices_Default_Roles_Description": "Alapértelmezett szerepek (vesszővel elválasztva), amelyeket a felhasználók akkor kapnak meg, ha hitelesítési szolgáltatásokon keresztül regisztrálnak", "Accounts_Registration_AuthenticationServices_Enabled": "Regisztráció hitelesítési szolgáltatásokkal", + "Accounts_Registration_Users_Default_Roles": "Alapértelmezett szerepkörök a felhasználók számára", + "Accounts_Registration_Users_Default_Roles_Enabled": "Alapértelmezett szerepkörök engedélyezése a kézi regisztrációhoz", "Accounts_Registration_InviteUrlType": "Meghívó URL típusa", "Accounts_Registration_InviteUrlType_Direct": "Közvetlen", "Accounts_Registration_InviteUrlType_Proxy": "Proxy", @@ -212,11 +224,15 @@ "Accounts_SearchFields": "A keresés során figyelembe vett mezők", "Accounts_Send_Email_When_Activating": "E-mail küldése a felhasználónak, ha a felhasználó aktiválva lett", "Accounts_Send_Email_When_Deactivating": "E-mail küldése a felhasználónak, ha a felhasználó inaktiválva lett", + "Accounts_Set_Email_Of_External_Accounts_as_Verified": "Külső fiókok e-mail címének beállítása ellenőrzöttként", "Accounts_SetDefaultAvatar": "Alapértelmezett profilkép beállítása", "Accounts_SetDefaultAvatar_Description": "Megpróbálja meghatározni az alapértelmezett profilképet az OAuth fiók vagy a Gravatar alapján", "Accounts_ShowFormLogin": "Alapértelmezett bejelentkezési űrlap megjelenítése", + "Accounts_TwoFactorAuthentication_By_TOTP_Enabled": "Kétfaktoros hitelesítés engedélyezése TOTP-n keresztül", + "Accounts_TwoFactorAuthentication_By_Email_Auto_Opt_In": "Új felhasználók automatikus bejelentkezése a kétfaktoros rendszerhez e-mailben", "Accounts_TwoFactorAuthentication_By_Email_Enabled": "Engedélyezze a kétfaktoros hitelesítést e-mailben", "Accounts_TwoFactorAuthentication_Enabled": "Kétlépcsős hitelesítés engedélyezése", + "Accounts_TwoFactorAuthentication_Enforce_Password_Fallback": "Jelszó-visszavonás kényszerítése", "Accounts_TwoFactorAuthentication_MaxDelta": "Legnagyobb delta", "Accounts_TwoFactorAuthentication_MaxDelta_Description": "A legnagyobb delta határozza meg, hogy hány token érvényes bármely adott időpontban. A tokeneket 30 másodpercenként állítják elő, és (30 × legnagyobb delta) másodpercig érvényesek.
      Példa: Ha a legnagyobb delta 10-re van állítva, akkor minden token legfeljebb 300 másodpercig használható az időbélyegük előtt vagy után. Ez akkor hasznos, ha az ügyfél órája nincs megfelelően szinkronizálva a kiszolgálóéval.", "Accounts_UseDefaultBlockedDomainsList": "Alapértelmezetten blokkolt tartományok listájának használata", @@ -224,6 +240,7 @@ "Accounts_UserAddedEmail_Default": "

      Üdvözli a(z) [Site_Name]

      Menjen a(z) [Site_URL] címre, és még ma próbálja ki az elérhető legjobb nyílt forráskódú csevegőmegoldást!

      Bejelentkezhet az e-mail-címe ([email]) és jelszava ([password]) használatával. Arra kérhetik, hogy változtassa meg az első bejelentkezés után.", "Accounts_UserAddedEmail_Description": "A következő helykitöltőket használhatja:

      • [name] a felhasználó teljes nevéhez, [lname] a felhasználó vezetéknevéhez és [fname] a felhasználó keresztnevéhez.
      • [email] a felhasználó e-mail-címéhez.
      • [password] a felhasználó jelszavához.
      • [Site_Name] az alkalmazás nevéhez és [Site_URL] az alkalmazás URL-jéhez.
      ", "Accounts_UserAddedEmailSubject_Default": "Hozzá lett adva a(z) [Site_Name] alkalmazáshoz", + "Accounts_Verify_Email_For_External_Accounts": "E-mail ellenőrzése külső fiókok esetén", "Action_required": "Beavatkozás szükséges", "Activate": "Aktiválás", "Active": "Aktív", @@ -241,7 +258,9 @@ "Add_user": "Felhasználó hozzáadása", "Add_User": "Felhasználó hozzáadása", "Add_users": "Felhasználók hozzáadása", + "Add_members": "Tagok hozzáadása", "add-livechat-department-agents": "Livechat ügyintézők hozzáadása a részlegekhez", + "add-livechat-department-agents_description": "Engedély az omnichannel ügynökök hozzáadására az osztályokhoz", "add-oauth-service": "Oauth szolgáltatás hozzáadása", "add-oauth-service_description": "Jogosultság új Oauth szolgáltatás hozzáadásához", "add-user": "Felhasználó hozzáadása", @@ -264,10 +283,13 @@ "Administration": "Adminisztráció", "Adult_images_are_not_allowed": "Felnőtt tartalmú képek nem engedélyezettek", "After_OAuth2_authentication_users_will_be_redirected_to_this_URL": "Az OAuth2 hitelesítés után a felhasználók át lesznek irányítva egy ezen a listán lévő URL-re. Soronként egy URL-t adhat hozzá.", - "Agent": "Ügyintéző", - "Agent_added": "Ügyintéző hozzáadva", + "Agent": "Ügynök", + "Agent_added": "Ügynök hozzáadva", "Agent_Info": "Ügyintéző információk", + "Agent_messages": "Ügynök üzenetek", + "Agent_Name_Placeholder": "Kérjük, adja meg az ügynök nevét...", "Agent_removed": "Ügyintéző eltávolítva", + "Agent_deactivated": "Az ügynököt deaktiválták", "Agents": "Ügyintézők", "Alerts": "Riasztások", "Alias": "Álnév", @@ -288,10 +310,13 @@ "Allow_Invalid_SelfSigned_Certs": "Érvénytelen saját aláírású tanúsítványok engedélyezése", "Allow_Invalid_SelfSigned_Certs_Description": "Érvénytelen és saját aláírású SSL-tanúsítvány engedélyezése a hivatkozás-ellenőrzéshez és az előnézetekhez.", "Allow_Marketing_Emails": "Marketing e-mailek engedélyezése", + "Allow_Online_Agents_Outside_Business_Hours": "Online ügynökök engedélyezése nyitvatartási időn kívül", "Allow_Online_Agents_Outside_Office_Hours": "Munkaidőn kívül elérhető ügyintézők engedélyezése", + "Allow_Save_Media_to_Gallery": "Média mentésének engedélyezése a galériába", "Allow_switching_departments": "Lehetővé tétel a látogató számára, hogy részleget váltson", "Almost_done": "Majdnem kész", "Alphabetical": "Ábécé sorrend", + "Also_send_to_channel": "Szintén küldje el a csatornára", "Always_open_in_new_window": "Megnyitás mindig új ablakban", "Analytics": "Analitika", "Analytics_features_enabled": "Funkciók engedélyezve", @@ -348,6 +373,7 @@ "API_Personal_Access_Tokens_Regenerate_Modal": "Ha elvesztette vagy elfelejtette a tokenjét, akkor újra előállíthatja azt, de ne feledje, hogy a tokent használó összes alkalmazást frissíteni kell", "API_Personal_Access_Tokens_Remove_Modal": "Biztosan el szeretné eltávolítani ezt a személyes hozzáférési tokent?", "API_Personal_Access_Tokens_To_REST_API": "Személyes hozzáférési tokenek a REST API-hoz", + "API_Rate_Limiter": "API sebességkorlátozó", "API_Shield_Types": "Pajzstípusok", "API_Shield_Types_Description": "Az engedélyezendő pajzsok típusai vesszővel elválasztott listaként, válasszon „online”, „channel” vagy „*” (összes) közül", "API_Token": "API token", @@ -358,6 +384,7 @@ "API_User_Limit": "Felhasználókorlát a csatornához történő összes felhasználó hozzáadásánál", "API_Wordpress_URL": "WordPress URL", "api-bypass-rate-limit": "REST API gyakoriságkorlát megkerülése", + "api-bypass-rate-limit_description": "Engedély az API hívására sebességkorlátozás nélkül", "Apiai_Key": "Api.ai kulcs", "Apiai_Language": "Api.ai nyelv", "APIs": "API-k", @@ -381,17 +408,33 @@ "App_user_not_allowed_to_login": "Az alkalmazás felhasználóinak nem engedélyezett a közvetlen bejelentkezés.", "Appearance": "Megjelenés", "Application_added": "Alkalmazás hozzáadva", + "Application_delete_warning": "Ezt az alkalmazást nem fogod tudni visszaállítani!", "Application_Name": "Alkalmazás neve", "Application_updated": "Alkalmazás frissítve", "Apply": "Alkalmaz", "Apply_and_refresh_all_clients": "Alkalmaz és minden kliens frissítése", "Apps": "Alkalmazások", "Apps_Engine_Version": "Alkalmazás motorjának verziója", + "Apps_Essential_Alert": "Ez az alkalmazás elengedhetetlen a következő eseményekhez:", "Apps_Framework_Development_Mode": "Fejlesztői mód engedélyezése", "Apps_Framework_Development_Mode_Description": "A fejlesztői mód lehetővé teszi olyan alkalmazások telepítését, amelyek nem a Rocket.Chat alkalmazásboltjából származnak.", "Apps_Framework_enabled": "Az alkalmazás-keretrendszer engedélyezése", + "Apps_Framework_Source_Package_Storage_Type": "Alkalmazás forráscsomagjának tárolási típusa", + "Apps_Framework_Source_Package_Storage_FileSystem_Path": "Az alkalmazások forráscsomagjának tárolási könyvtára", "Apps_Game_Center": "Játék központ", "Apps_Game_Center_Back": "Vissza a játék központba", + "Apps_Game_Center_Invite_Friends": "Hívja meg barátait, hogy csatlakozzanak", + "Apps_Game_Center_Play_Game_Together": "@here Játsszunk együtt __name__!", + "Apps_Interface_IPreRoomCreatePrevent": "A szoba létrehozása előtt bekövetkező esemény", + "Apps_Interface_IPreRoomDeletePrevent": "A szoba törlése előtt bekövetkező esemény", + "Apps_License_Message_appId": "Ehhez az alkalmazáshoz nem adtak ki licenszet", + "Apps_License_Message_expire": "Az licensz már nem érvényes, meg kell újítani", + "Apps_License_Message_renewal": "Az licensz lejárt és meg kell újítani", + "Apps_Logs_TTL": "Az alkalmazások naplóinak tárolási napjainak száma", + "Apps_Logs_TTL_7days": "7 nap", + "Apps_Logs_TTL_14days": "14 nap", + "Apps_Logs_TTL_30days": "30 nap", + "Apps_Logs_TTL_Alert": "A Naplók méretétől függően a beállítás megváltoztatása néhány pillanatig lassúságot okozhat", "Apps_Marketplace_Deactivate_App_Prompt": "Valóban le szeretné tiltani ezt az alkalmazást?", "Apps_Marketplace_Login_Required_Description": "A Rocket.Chat alkalmazásboltból történő alkalmazások vásárlásához a munkaterület regisztrálása és bejelentkezés szükséges.", "Apps_Marketplace_Login_Required_Title": "Alkalmazásbolt bejelentkezés szükséges", @@ -400,12 +443,40 @@ "Apps_Marketplace_pricingPlan_monthly_perUser": "__price__ / hónap felhasználónként", "Apps_Marketplace_pricingPlan_startingAt_monthly": "__price__ / hónaptól", "Apps_Marketplace_pricingPlan_startingAt_monthly_perUser": "felhasználónként __price__ / hónaptól", + "Apps_Marketplace_pricingPlan_startingAt_yearly": "__price__ / évtől kezdődően", + "Apps_Marketplace_pricingPlan_startingAt_yearly_perUser": "Felhasználónként __price__ / évtől kezdődően", "Apps_Marketplace_pricingPlan_yearly": "__price__ / év", "Apps_Marketplace_pricingPlan_yearly_perUser": "__price__ / év felhasználónként", "Apps_Marketplace_Uninstall_App_Prompt": "Valóban el akarja távolítani ezt az alkalmazást?", "Apps_Marketplace_Uninstall_Subscribed_App_Anyway": "Eltávolítás mindenképp", "Apps_Marketplace_Uninstall_Subscribed_App_Prompt": "Ennek az alkalmazásnak aktív előfizetése van, és az eltávolítása nem fogja megszakítani azt. Ha ezt szeretné tenni, akkor módosítsa az előfizetését az eltávolítás előtt.", + "Apps_Permissions_Review_Modal_Title": "Szükséges engedélyek", + "Apps_Permissions_Review_Modal_Subtitle": "Ez az alkalmazás a következő engedélyekhez szeretne hozzáférni. Egyetértesz?", + "Apps_Permissions_No_Permissions_Required": "Az alkalmazás nem igényel további engedélyeket", + "Apps_Permissions_cloud_workspace-token": "Interakció a felhőszolgáltatásokkal a kiszolgáló nevében", + "Apps_Permissions_user_read": "Hozzáférés a felhasználói adatokhoz", + "Apps_Permissions_user_write": "Felhasználói adatok módosítása", + "Apps_Permissions_upload_read": "A szerverre feltöltött fájlok elérése", + "Apps_Permissions_upload_write": "Fájlok feltöltése a szerverre", + "Apps_Permissions_server-setting_read": "Hozzáférési beállítások a szerveren", + "Apps_Permissions_server-setting_write": "Beállítások módosítása a szerveren", + "Apps_Permissions_room_read": "Hozzáférés a szoba információihoz", + "Apps_Permissions_room_write": "Szobák létrehozása és módosítása", + "Apps_Permissions_message_read": "Hozzáférési üzenetek", + "Apps_Permissions_message_write": "Üzenetek küldése és módosítása", + "Apps_Permissions_livechat-status_read": "Hozzáférés a Livechat státusz információkhoz", + "Apps_Permissions_livechat-visitor_read": "Hozzáférés a Livechat látogatói információkhoz", + "Apps_Permissions_livechat-department_write": "Livechat részleg információk módosítása", + "Apps_Permissions_slashcommand": "Új slash parancsok regisztrálása", + "Apps_Permissions_api": "Új HTTP végpontok regisztrálása", + "Apps_Permissions_env_read": "Hozzáférés minimális információkhoz a kiszolgáló környezetéről", + "Apps_Permissions_networking": "Hozzáférés ehhez a szerverhálózathoz", + "Apps_Permissions_persistence": "Belső adatok tárolása az adatbázisban", + "Apps_Permissions_scheduler": "Ütemezett munkák nyilvántartása és karbantartása", + "Apps_Permissions_ui_interact": "Interakció a felhasználói felülettel", "Apps_Settings": "Alkalmazás beállításai", + "Apps_Manual_Update_Modal_Title": "Ez az alkalmazás már telepítve van", + "Apps_Manual_Update_Modal_Body": "Szeretné frissíteni?", "Apps_User_Already_Exists": "A(z) „__username__” felhasználónév már használatban van. Az alkalmazás telepítéshez nevezze át vagy távolítsa el az ezt használó felhasználót", "Apps_WhatIsIt": "Alkalmazások: mik ezek?", "Apps_WhatIsIt_paragraph1": "Új ikon az adminisztrációs területen! Mit jelent ez és mik azok az alkalmazások?", @@ -417,6 +488,7 @@ "archive-room_description": "Jogosultság egy csatorna archiválásához", "are_typing": "gépel", "Are_you_sure": "Biztos benne?", + "Are_you_sure_you_want_to_close_this_chat": "Biztos, hogy be akarod zárni ezt a csevegést?", "Are_you_sure_you_want_to_delete_this_record": "Biztosan törölni szeretné ezt a rekordot?", "Are_you_sure_you_want_to_delete_your_account": "Biztosan törölni szeretné a fiókját?", "Are_you_sure_you_want_to_disable_Facebook_integration": "Biztosan le szeretné tiltani a Facebook integrációt?", @@ -502,13 +574,25 @@ "Back_to_Manage_Apps": "Vissza az alkalmazások kezeléséhez", "Back_to_permissions": "Vissza a jogosultságokhoz", "Back_to_room": "Vissza szobához", + "Back_to_threads": "Vissza a témákhoz", "Backup_codes": "Kódok biztonsági mentése", "ban-user": "Felhasználó kitiltása", "ban-user_description": "Jogosultság egy felhasználó kitiltásához egy csatornáról", + "BBB_End_Meeting": "Megbeszélés vége", + "BBB_Enable_Teams": "Engedélyezés a csapatok számára", + "BBB_Join_Meeting": "Csatlakozz a megbeszéléshez", + "BBB_Start_Meeting": "Megbeszélés kezdése", + "BBB_Video_Call": "BBB videóhívás", + "BBB_You_have_no_permission_to_start_a_call": "Nincs engedélye hívás indítására", + "Belongs_To": "Tartozik", "Best_first_response_time": "Legjobb első válasz idő", "Beta_feature_Depends_on_Video_Conference_to_be_enabled": "Béta funkció. Attól függ, hogy a videokonferencia engedélyezve van-e.", "Better": "Jobb", + "Bio": "Adatlap", + "Bio_Placeholder": "Adatlap helyfoglaló", + "Block_Multiple_Failed_Logins_Enabled": "Bejelentkezési adatok gyűjtésének engedélyezése", "Block_Multiple_Failed_Logins_Ip_Whitelist": "IP fehérlista", + "Block_Multiple_Failed_Logins_Notify_Failed": "Értesítés a sikertelen bejelentkezési kísérletekről", "Block_User": "Felhasználó blokkolása", "Blockchain": "Blokklánc", "Blockstack_Auth_Description": "Hitelesítés leírása", @@ -549,22 +633,34 @@ "by": "–", "cache_cleared": "Gyorsítótár törölve", "Call": "Hívás", + "Call_declined": "Hívás elutasítva!", + "Call_provider": "Hívás Szolgáltató", + "Call_Already_Ended": "A hívás már befejeződött", "call-management": "Híváskezelés", + "call-management_description": "Engedély a megbeszélés megkezdésére", "Caller": "Hívó", "Cancel": "Mégse", "Cancel_message_input": "Mégse", "Canceled": "Megszakítva", + "Canned_Response_Created": "Válasz sablon létrehozva", + "Canned_Response_Updated": "Válasz sablon frissítve", + "Canned_Response_Delete_Warning": "A válasz sablon törlése nem vonható vissza.", "Canned_Response_Removed": "Válasz sablon eltávolítva", + "Canned_Response_Sharing_Public_Description": "Ehhez a válasz sablonhoz bárki hozzáférhet", "Canned_Responses": "Válasz sablonok", "Canned_Responses_Enable": "Válasz sablonok engedélyezése", + "Create_your_First_Canned_Response": "Az első válasz sablon létrehozása", "Cannot_invite_users_to_direct_rooms": "Nem hívhat meg felhasználókat a közvetlen szobákba", "Cannot_open_conversation_with_yourself": "Nem küldhet közvetlen üzenetet önmagának", + "Cannot_share_your_location": "Nem oszthatja meg a tartózkodási helyét...", "CAS_autoclose": "Bejelentkezési felugró ablak automatikus bezárása", "CAS_base_url": "SSO alap URL", "CAS_base_url_Description": "A külső SSO szolgáltatás alap URL-je, például: https://sso.example.undef/sso/", "CAS_button_color": "Bejelentkezés gomb háttérszíne", "CAS_button_label_color": "Bejelentkezés gomb szövegszíne", "CAS_button_label_text": "Bejelentkezés gomb felirata", + "CAS_Creation_User_Enabled": "Felhasználó létrehozásának engedélyezése", + "CAS_Creation_User_Enabled_Description": "CAS-felhasználó létrehozásának engedélyezése a CAS-jegy által megadott adatokból.", "CAS_enabled": "Engedélyezve", "CAS_Login_Layout": "CAS bejelentkezés elrendezése", "CAS_login_url": "SSO bejelentkezési URL", @@ -574,7 +670,8 @@ "CAS_Sync_User_Data_Enabled": "Mindig szinkronizálja a felhasználói adatokat", "CAS_Sync_User_Data_Enabled_Description": "Mindig szinkronizálja a külső CAS felhasználói adatokat a rendelkezésre álló attribútumokkal bejelentkezéskor. Megjegyzés: Az attribútumok mindig szinkronizálódnak a fiók létrehozásakor.", "CAS_Sync_User_Data_FieldMap": "Tulajdonság térkép", - "CAS_Sync_User_Data_FieldMap_Description": "Használja ezt a JSON bemenetet belső attribútumok (kulcs) külső attribútumok (érték) létrehozására. A (z) \"%\" verziójú külső attribútumnevek interpolálják az érték-karakterláncokat.
      Példa, \"{email\": \"% email%\", \"név\": \"% firstname%,% utónév%\"} `

      Az attribútum térkép mindig interpolált. A CAS 1.0-ban csak a `username` attribútum áll rendelkezésre. Az elérhető belső jellemzők: felhasználónév, név, e-mail, szobák; szobák a vesszõvel elválasztott szobák listája, amelyek a felhasználók létrehozásához csatlakoznak, például: {\"rooms\": \"% team%,% department%\"} csatlakozhat a CAS felhasználókhoz a létrehozásukhoz csapatuk és osztályuk csatornájához.", + "CAS_Sync_User_Data_FieldMap_Description": "Használja ezt a JSON bemenetet belső attribútumok (kulcs) külső attribútumok (érték) létrehozására. A (z) \"%\" verziójú külső attribútumnevek interpolálják az érték-karakterláncokat.
      Példa, `{\"email\":\"%email%\", \"name\":\"%firstname%, %lastname%\"}`

      Az attribútum térkép mindig interpolált. A CAS 1.0-ban csak a `username` attribútum áll rendelkezésre. Az elérhető belső jellemzők: felhasználónév, név, e-mail, szobák; szobák a vesszővel elválasztott szobák listája, amelyek a felhasználók létrehozásához csatlakoznak, például: {\"rooms\": \"%team%,%department%\"} csatlakozhat a CAS felhasználókhoz a létrehozásukhoz csapatuk és osztályuk csatornájához.", + "CAS_trust_username": "Trust CAS felhasználónév", "CAS_version": "CAS verzió", "CAS_version_Description": "Csak CAS által támogatott CAS-verziót használjon.", "Categories": "Kategóriák", @@ -598,13 +695,24 @@ "Channel_to_listen_on": "Csatorna figyelő", "Channel_Unarchived": "A `#%s` nevű csatorna sikeresen visszaállítva", "Channels": "Csatornák", + "Channels_added": "Channel hozzáadva", "Channels_are_where_your_team_communicate": "Csatorna, ahol a csapatod kommunikálhat", "Channels_list": "Nyilvános csatornák listája", + "Chart": "Diagram", "Chat_button": "Chat gomb", + "Chat_close": "Csevegés bezárása", "Chat_closed": "Chat lezárva", "Chat_closed_by_agent": "Chat lezárva az operátor által", "Chat_closed_successfully": "Chat sikeresen lezárult", + "Chat_History": "Csevegés története", "Chat_Now": "Csevegj most", + "Chat_On_Hold": "Várakozó csevegés", + "Chat_On_Hold_Successfully": "Ez a csevegés sikeresen várakoztatásra került", + "Chat_queued": "Csevegés várakoztatva", + "Chat_removed": "Chat eltávolítva", + "Chat_resumed": "Csevegés folytatódik", + "Chat_start": "Csevegés kezdete", + "Chat_started": "Csevegés megkezdve", "Chat_window": "Chat ablak", "Chatops_Enabled": "Chatops engedélyezése", "Chatops_Title": "Chatops felület", @@ -658,11 +766,13 @@ "Chatpal_Welcome": "Élvezze a keresést!", "Chatpal_Window_Size": "Index ablakméret", "Chatpal_Window_Size_Description": "Az index ablakok mérete órában (bootstrapoláskor)", + "Chats_removed": "Csevegések eltávolítva", "Check_All": "Összes kijelölése", "Choose_a_room": "Válasszon egy szobát", "Choose_messages": "Üzenetek kiválasztása", "Choose_the_alias_that_will_appear_before_the_username_in_messages": "Válassza Alias ​​előtt jelenik meg a felhasználónevét üzeneteket.", "Choose_the_username_that_this_integration_will_post_as": "Válassza ki a felhasználónevét, hogy ez az integráció utáni mint.", + "Choose_users": "Válasszon felhasználókat", "Clean_Usernames": "Felhasználó nevek törlése", "clean-channel-history": "Tiszta csatornaelőzmények", "clean-channel-history_description": "Engedély a csatornák történetének törlésére", @@ -677,17 +787,20 @@ "Click_here_to_view_and_copy_your_password": "Kattints ide a jelszó megtekintéséhez és másolásához.", "Click_the_messages_you_would_like_to_send_by_email": "Kattints az üzenetekre, amiket szeretnél e-mailben elküldeni", "Click_to_join": "Kattints a csatlakozáshoz!", + "Click_to_load": "Kattints a betöltéshez", "Client_ID": "ügyfél-azonosító", "Client_Secret": "Client Secret", "Clients_will_refresh_in_a_few_seconds": "Az ügyfelek néhány másodpercen belül frissülnek", "close": "bezárás", "Close": "Bezárás", + "Close_chat": "Csevegés bezárása", "close-livechat-room": "Livechat szoba bezárása", "close-livechat-room_description": "Engedély az aktuális LiveChat csatorna bezárásához", "Close_menu": "Menü bezárása", "close-others-livechat-room": "Livechat szoba bezárása", "close-others-livechat-room_description": "Engedély az egyéb LiveChat csatornák bezárására", "Closed": "Zárva", + "Closed_At": "Lezárva", "Closed_by_visitor": "Látogató által bezárva", "Closing_chat": "Beszélgetés bezáráse", "Cloud": "Felhő", @@ -695,6 +808,7 @@ "Cloud_console": "Felhő konzol", "Cloud_error_code": "Kód: __errorCode__", "Cloud_error_in_authenticating": "Hiba a hitelesítés során:", + "Cloud_Info": "Felhő információ", "Cloud_login_to_cloud": "Belépés a Rocket.Chat Felhőbe", "Cloud_logout": "Kilépés a Rocket.Chat Felhőből", "Cloud_manually_input_token": "Add meg kézzel a tokent, ami a Felhő regisztrációs e-mailben szerepel.", @@ -731,17 +845,22 @@ "Connectivity_Services": "Kapcsolódási szolgáltatások", "Consulting": "Tanácsadó", "Contact": "Kapcsolat", + "Contacts": "Kapcsolat", + "Contact_Name": "Kapcsolattartó neve", "Contains_Security_Fixes": "Biztonsági javításokat tartalmaz", "Content": "Tartalom", "Continue": "Folytatás", "Continuous_sound_notifications_for_new_livechat_room": "Folyamatos hang értesítések az új livechat szobához", "Conversation": "Beszélgetés", "Conversation_closed": "Beszélgetés zárva: __comment__.", + "Conversation_finished": "Beszélgetés befejeződött", "Conversation_finished_message": "Beszélgetés befejezése üzenet", "conversation_with_s": "a beszélgetés %s-val", "Conversations": "Beszélgetések", "Conversations_per_day": "Napi beszélgetések", + "Convert": "Átalakítás", "Convert_Ascii_Emojis": "ASCII hangulatjelek átalakítása", + "Converting_channel_to_a_team": "Ezt a Channel csapattá alakítod át. Minden tag megmarad.", "Copied": "Másolva", "Copy": "Másolás", "Copy_text": "Szöveg másolása", @@ -952,7 +1071,7 @@ "Country_Spain": "Spanyolország", "Country_Sri_Lanka": "Srí Lanka", "Country_Sudan": "Szudán", - "Country_Suriname": "Suriname", + "Country_Suriname": "Vezetéknév", "Country_Svalbard_and_Jan_Mayen": "Svalbard és Jan Mayen", "Country_Swaziland": "Szváziföld", "Country_Sweden": "Svédország", @@ -992,27 +1111,38 @@ "Country_Zimbabwe": "Zimbabwe", "Cozy": "Kényelmes", "Create": "Létrehoz", + "Create_Canned_Response": "Sablon válasz létrehozása", + "Create_channel": "Channel létrehozása", "Create_A_New_Channel": "Új csatorna létrehozása", "Create_new": "Új létrehozása", + "Create_new_members": "Új tagok létrehozása", "Create_unique_rules_for_this_channel": "Hozzon létre egyedi szabályokat ehhez a csatornához", "create-c": "Nyilvános csatornák létrehozása", "create-c_description": "Engedély nyilvános csatornák létrehozására", "create-d": "Közvetlen üzenetek létrehozása", "create-d_description": "Engedély a közvetlen üzenetek indításához", + "create-invite-links": "Meghívó linkek létrehozása", + "create-invite-links_description": "Engedély meghívó linkek létrehozására a csatornákhoz", "create-p": "Privát csatornák létrehozása", "create-p_description": "Privát csatornák létrehozásának engedélye", "create-personal-access-tokens": "Személyi hozzáférési token létrehozása", + "create-personal-access-tokens_description": "Személyes hozzáférési tokenek létrehozásának engedélyezése", "create-user": "Felhasználó létrehozása", "create-user_description": "Engedély a felhasználók létrehozásához", + "Created": "Létrehozva", + "Created_as": "Létrehozva mint", "Created_at": "Készült", "Created_at_s_by_s": "Létrehozva %s, %s által", "Created_at_s_by_s_triggered_by_s": "%s hatására %s által készítve %s időpontban.", + "Created_by": "Létrehozta", "CRM_Integration": "CRM integráció", "CROWD_Allow_Custom_Username": "Egyedi nevek engedélyezése a Rocket.Chat-en", "CROWD_Reject_Unauthorized": "Jogosulatlanok elutasítása", + "Crowd_Remove_Orphaned_Users": "Árva felhasználók eltávolítása", "Crowd_sync_interval_Description": "A szinkronizálás közötti idő. Példa \"24 óránként\" vagy \"a hét első napján\", további példák a [Cron szövegszerkesztőben] (http://bunkat.github.io/later/parsers.html#text)", "Current_Chats": "Jelenlegi beszélgetések", "Current_File": "Aktuális fájl", + "Current_Import_Operation": "Aktuális importálási művelet", "Current_Status": "Jelenlegi állapot", "Custom": "Egyedi", "Custom CSS": "Egyéni CSS", @@ -1029,6 +1159,9 @@ "Custom_Emoji_Info": "Egyedi hangulatjel információ", "Custom_Emoji_Updated_Successfully": "Az egyéni hangulatjel feltöltése sikeres", "Custom_Fields": "Egyedi mezők", + "Custom_Field_Removed": "Egyéni mező eltávolítva", + "Custom_Field_Not_Found": "Egyéni mező nem található", + "Custom_Integration": "Egyedi integráció", "Custom_oauth_helper": "Amikor beállítja OAuth Szolgáltató, akkor tájékoztatni visszahívás URL. Használat
       %s 
      .", "Custom_oauth_unique_name": "Egyedi OAuth egyedi név", "Custom_Script_Logged_In": "Egyedi szkript bejelentkezett felhasználóknak", @@ -1046,12 +1179,14 @@ "Custom_Sound_Info": "Egyedi hang információ", "Custom_Sound_Saved_Successfully": "Az egyedi hang sikeresen mentve", "Custom_Sounds": "Egyedi Hangok", + "Custom_Status": "Egyéni állapot", "Custom_Translations": "Egyedi fordítás", "Custom_Translations_Description": "Valódi JSON legyen, ahol a kulcsok olyan nyelvek, amelyek kulcsszót tartalmaznak és fordítások. Például:
      {\n \"en\": {\n \"Channels\": \"Rooms\"\n },\n\"pt\":{\n \"Channels\": \"Salas\"\n }\n}", "Custom_User_Status": "Egyedi felhasználó állapot", "Custom_User_Status_Add": "Egyedi felhasználó állapot hozzáadása", "Custom_User_Status_Added_Successfully": "Az egyedi felhasználó állapot sikeresen hozzáadva", "Custom_User_Status_Delete_Warning": "Az egyedi felhasználó állapot törlés nem vonható vissza.", + "Custom_User_Status_Edit": "Egyéni felhasználói státusz szerkesztése", "Custom_User_Status_Error_Invalid_User_Status": "Érvénytelen felhasználó állapot", "Custom_User_Status_Error_Name_Already_In_Use": "Az egyedi felhasználó állapot neve már használatban van.", "Custom_User_Status_Has_Been_Deleted": "Az egyedi felhasználó állapot törölve lett", @@ -1059,6 +1194,7 @@ "Custom_User_Status_Updated_Successfully": "Az egyedi felhasználó állapot sikeresen frissítve", "Customize": "Testreszabás", "CustomSoundsFilesystem": "Egyedi hangok fájlrendszere", + "Daily_Active_Users": "Napi aktív felhasználók", "Dashboard": "Irányítópult", "Data_processing_consent_text": "Hozzájárulás adatfeldolgozáshoz szöveg", "Data_processing_consent_text_description": "Használja ezt a beállítás, ahol tájékoztatja a felhasználót, hogy gyűjti, tárolja és feldolgozza a felhasználó személyes adatait a beszélgetések mellett.", @@ -1066,6 +1202,7 @@ "Date_From": "Ból ből", "Date_to": "nak nek", "days": "nap", + "Days": "Napok", "DB_Migration": "Adatbázis migráció", "DB_Migration_Date": "Adatbázis migráció időpontja", "DDP_Rate_Limit_Connection_Enabled": "Korlátozás kapcsolat alapján: engedélyezve", @@ -1078,8 +1215,12 @@ "Default": "Alapértelmezett", "Default_value": "Alapértelmezett érték", "Delete": "Töröl", + "Deleting": "Törlés", + "Delete_all_closed_chats": "Minden lezárt csevegés törlése", + "Delete_File_Warning": "Egy fájl törlése véglegesen törli azt. Ezt nem lehet visszacsinálni.", "Delete_message": "Üzenet törlése", "Delete_my_account": "A fiókom törlése", + "Delete_Role_Warning": "Egy szerepkör törlése véglegesen törli azt. Ezt nem lehet visszacsinálni.", "Delete_Room_Warning": "Törlése szoba törli az összes üzenetet küldte a szobában. Ezt nem lehet visszacsinálni.", "Delete_User_Warning": "Törlése felhasználó összes üzenetet törölni, hogy a felhasználó is. Ezt nem lehet visszacsinálni.", "Delete_User_Warning_Delete": "Törlése felhasználó összes üzenetet törölni, hogy a felhasználó is. Ezt nem lehet visszacsinálni.", @@ -1091,16 +1232,20 @@ "delete-d_description": "Engedély a közvetlen üzenetek törlésére", "delete-message": "Üzenet törlése", "delete-message_description": "Engedély az üzenet törlésére egy szobában", + "delete-own-message": "Saját üzenet törlése", + "delete-own-message_description": "Saját üzenet törlésének engedélyezése", "delete-p": "Privát csatornák törlése", "delete-p_description": "Engedély privát csatornák törléséhez", "delete-user": "Felhasználó törlése", "delete-user_description": "Engedély a felhasználók törléséhez", "Deleted": "Törölve!", "Department": "Részleg", + "Department_name": "Részleg neve", "Department_not_found": "Az osztály nem található", "Department_removed": "Részleg eltávolítva", "Departments": "Részlegek", "Deployment_ID": "Telepítés azonosító", + "Deployment": "Telepítés", "Description": "Leírás", "Desktop": "Asztal", "Desktop_Notification_Test": "Asztali értesítés teszt", @@ -1135,12 +1280,14 @@ "Direct_Reply_Username": "Felhasználónév", "Direct_Reply_Username_Description": "Kérem, használja az abszolút e-mailt, a címkézés nem megengedett, túlzás lenne", "Directory": "Címtár", + "Disable": "Tilt", "Disable_Facebook_integration": "Facebook integráció tiltása", "Disable_Notifications": "Értesítések letiltása", "Disable_two-factor_authentication": "Kétlépcsős azonosítása tiltása", "Disabled": "Tiltva", "Disallow_reacting": "Reagálás tiltása", "Disallow_reacting_Description": "Hatástalanítja a reagálást", + "Discard": "Eldob", "Disconnect": "Megszakítás", "Discussion": "Beszélgetés", "Discussion_first_message_title": "Az Ön üzenete", @@ -1152,11 +1299,18 @@ "Discussion_title": "Új beszélgetés létrehozása", "discussion-created": "__message__", "Discussions": "Beszélgetések", + "Display": "Megjelenítés", + "Display_avatars": "Avatarok megjelenítése", + "Display_Avatars_Sidebar": "Avatárok megjelenítése az oldalsávban", "Display_chat_permissions": "Chat engedélyek megjelenítése", "Display_offline_form": "Offline űrlap megjelenítése", + "Display_setting_permissions": "A beállítások módosítására vonatkozó engedélyek megjelenítése", "Display_unread_counter": "Az olvasatlan üzenetek számának megjelenítése", "Displays_action_text": "Kijelzők akció szöveg", + "Do_It_Later": "Csináld később", "Do_not_display_unread_counter": "Ne jelenjen meg a csatorna számlálója", + "Do_not_provide_this_code_to_anyone": "Ne add át ezt a kódot senkinek.", + "Do_Nothing": "Ne csinálj semmit", "Do_you_want_to_accept": "Szeretné elfogadni?", "Do_you_want_to_change_to_s_question": "Szeretné megváltoztatni %s?", "Document_Domain": "Dokumentumtartomány", @@ -1168,7 +1322,10 @@ "Dont_ask_me_again": "Ne kérdezzen újra!", "Dont_ask_me_again_list": "Ne kérdezze meg újra lista", "Download": "Letöltés", + "Download_Info": "Letöltési információ", "Download_My_Data": "Adataim letöltése", + "Download_Pending_Avatars": "Függő avatárok letöltése", + "Download_Pending_Files": "Függő fájlok letöltése", "Download_Snippet": "Letöltés", "Downloading_file_from_external_URL": "Fájl letöltése külső URL címről", "Drop_to_upload_file": "Dobja ide a fájlt a feltöltéshez", @@ -1176,18 +1333,30 @@ "Dry_run_description": "Csak akkor küldünk egy e-mailt, hogy ugyanaz a cím, mint a From. Az e-mail kell tartozniuk felhasználó érvényes.", "Duplicate_archived_channel_name": "Archivált Channel névvel ' %s' létezik", "Duplicate_archived_private_group_name": "Archivált Private csoport név ' %s' létezik", - "Duplicate_channel_name": "A csatorna névvel '%s' létezik", + "Duplicate_channel_name": "A Channel névvel '%s' létezik", + "Duplicate_file_name_found": "Duplikált fájlnevet találtunk.", "Duplicate_private_group_name": "A Private csoport neve ' %s' létezik", "Duplicated_Email_address_will_be_ignored": "A duplikált e-mail címeket nem vesszük figyelembe.", "duplicated-account": "Megkettőzött fiók", "E2E Encryption": "E2E titkosítás", + "E2E_enable": "E2E engedélyezése", + "E2E_disable": "E2E kikapcsolása", "E2E_Enabled": "E2E engedélyezve", + "E2E_Encryption_Password_Change": "Titkosítási jelszó módosítása", + "ECDH_Enabled": "Második rétegű titkosítás engedélyezése az adatátvitelhez", "Edit": "Szerkesztés", + "Edit_Canned_Response": "Sablon válasz szerkesztése", + "Edit_Canned_Responses": "Sablon válaszok szerkesztése", "Edit_Custom_Field": "Egyedi mező szerkesztése", "Edit_Department": "Osztály szerkesztése", + "Edit_Invite": "Meghívó szerkesztése", "Edit_previous_message": "`%s` - Az előző üzenet szerkesztése", + "Edit_Priority": "Prioritás szerkesztése", "Edit_Status": "Állapot szerkesztése", + "Edit_Tag": "Címke szerkesztése", "Edit_Trigger": "Trigger szerkesztése", + "Edit_Unit": "Egység szerkesztése", + "Edit_User": "Felhasználó szerkesztése", "edit-message": "Üzenet szerkesztése", "edit-message_description": "Engedély az üzenet szerkesztésére egy szobában", "edit-other-user-active-status": "Egyéb felhasználói aktív állapot szerkesztése", @@ -1202,19 +1371,26 @@ "edit-privileged-setting_description": "Engedély a beállítások szerkesztéséhez", "edit-room": "Room szerkesztése", "edit-room_description": "Engedély a szoba név, téma, típus (privát vagy nyilvános státusz) és státusz módosítására (aktív vagy archivált)", + "edit-room-avatar": "Room profilkép módosítása", "edit-room-retention-policy": "A szoba megőrzési szabályainak szerkesztése", "edit-room-retention-policy_description": "Engedély a szoba fenntartásának szabályaihoz, hogy automatikusan törölje az üzeneteket", "edited": "szerkesztve", "Editing_room": "vágószobából", "Editing_user": "szerkesztés alatt", + "Editor": "Szerkesztő", "Education": "Oktatás", + "Email": "Email", "Email_address_to_send_offline_messages": "E-mail cím, ahova az offline üzenetek küldésre kerülnek", "Email_already_exists": "Az e-mail cím már létezik", "Email_body": "E-mail szövege", "Email_Change_Disabled": "A Rocket.Chat adminisztrátora letiltotta az e-mail cím változtatását", + "Email_Changed_Email_Subject": "[Site_Name] - Az e-mail cím megváltozott", + "Email_changed_section": "Az e-mail cím megváltozott", "Email_Footer_Description": "Használhatja a következő szimbólumokat:
      • [Site_Name] és [Site_URL] Az Alkalmazás neve és URL ill.
      ", "Email_from": "Feladó", "Email_Header_Description": "Használhatja a következő szimbólumokat:
      • [Site_Name] és [Site_URL] Az Alkalmazás neve és URL ill.
      ", + "Email_Inbox": "Email bejövő levelek", + "Email_Inboxes": "Email bejövő levelek", "Email_Notification_Mode": "Offline E-mail értesítések", "Email_Notification_Mode_All": "Minden említés / Közvetlen üzenet", "Email_Notification_Mode_Disabled": "Tiltva", @@ -1223,6 +1399,7 @@ "Email_or_username": "E-mail vagy felhasználónév", "Email_Placeholder": "Kérjük, adja meg e-mail címét...", "Email_Placeholder_any": "Kérjük, írja be az e-mail címeket ...", + "email_plain_text_only": "Csak egyszerű szöveges e-mailek küldése", "email_style_description": "Egymásba ágyazott kiválasztok mellőzése", "email_style_label": "E-mail stílusa", "Email_subject": "Tárgy", @@ -1233,7 +1410,9 @@ "Empty_title": "Üres címet", "Enable": "Engedélyezze", "Enable_Auto_Away": "Engedélyezze az automatikus kikapcsolást", + "Enable_CSP": "Tartalom-biztonsági házirend (CSP) engedélyezése", "Enable_Desktop_Notifications": "Asztali értesítések engedélyezése", + "Enable_Password_History": "Jelszótörténet engedélyezése", "Enable_Svg_Favicon": "Engedélyezze az SVG favicon szolgáltatást", "Enable_two-factor_authentication": "Kétlépcsős hitelesítés engedélyezése", "Enabled": "Engedélyezett", @@ -1243,6 +1422,9 @@ "Encryption_key_saved_successfully": "A titkosító kulcs mentése sikeres.", "End": "Vége", "End_OTR": "OTR vége", + "Enter": "Belépés", + "Enter_a_custom_message": "Egyéni üzenet megadása", + "Enter_a_department_name": "Adja meg a részleg nevét", "Enter_a_name": "Írja be a nevet", "Enter_a_regex": "Adjon meg egy regex-et", "Enter_a_room_name": "Írja be a szoba nevét", @@ -1252,11 +1434,13 @@ "Enter_authentication_code": "Írja be a hitelesítési kódot", "Enter_Behaviour": "Írja be a kulcsmódot", "Enter_Behaviour_Description": "Ez megváltozik, ha az Enter gomb elküldi az üzenetet vagy megszakítja a sort", + "Enter_E2E_password": "E2E jelszó megadása", "Enter_name_here": "Írd be a nevet", "Enter_Normal": "Normál mód (küldés az Enter billentyűvel)", "Enter_to": "Belépés a", "Enter_your_E2E_password": "Adja meg E2E jelszavát", "Enterprise": "Vállalkozás", + "Enterprise_License": "Vállalati licensz", "Entertainment": "Szórakozás", "Error": "Hiba", "Error_404": "404-es hibakód", @@ -1266,25 +1450,32 @@ "Error_RocketChat_requires_oplog_tailing_when_running_in_multiple_instances_details": "Győződjön meg róla, hogy a MongoDB ReplicaSet módban van és a MONGO_OPLOG_URL környezeti változó helyesen van definiálva az alkalmazáskiszolgálón", "Error_sending_livechat_offline_message": "Hiba a Livechat offline üzenet küldésekor", "Error_sending_livechat_transcript": "Hiba a Livechat átirat küldésekor", + "Error_Site_URL": "Érvénytelen Site_Url", "error-action-not-allowed": "__action__ nem engedélyezett", + "error-agent-offline": "Az ügynök offline", "error-application-not-found": "Alkalmazás nem található", "error-archived-duplicate-name": "Van egy archivált csatorna neve '__room_name__ \"", "error-avatar-invalid-url": "Érvénytelen avatar URL: __url__", "error-avatar-url-handling": "Hiba kezelése avatar beállítás egy URL (__url__) az __username__", + "error-blocked-username": "__field__ blokkolva van és nem használható!", + "error-canned-response-not-found": "Sablon válasz nem található", "error-cant-invite-for-direct-room": "Nem lehet meghívni a felhasználó közvetlen szobák", "error-channels-setdefault-is-same": "A csatorna alapértelmezett beállítása megegyezik a változtatással.", "error-channels-setdefault-missing-default-param": "A bodyParam alapértelmezett beállítása szükséges", "error-could-not-change-email": "Nem sikerült megváltoztatni az e-mail", "error-could-not-change-name": "Nem sikerült megváltoztatni a nevét", "error-could-not-change-username": "Nem sikerült megváltoztatni a felhasználónevet", + "error-custom-field-name-already-exists": "Egyéni mezőnév már létezik", "error-delete-protected-role": "Nem lehet törölni a védett szerepet", "error-department-not-found": "Az osztály nem található", "error-direct-message-file-upload-not-allowed": "A fájlmegosztás nem engedélyezett a közvetlen üzenetekben", + "error-duplicate-channel-name": "Létezik egy csatorna '__channel_name__' névvel", "error-edit-permissions-not-allowed": "A jogosultságok szerkesztése nem engedélyezett", "error-email-domain-blacklisted": "Az e-mail domain feketelistára", "error-email-send-failed": "Hiba történt e-mail küldésénél: __message__", "error-field-unavailable": "__field__ név már használatban van :(", "error-file-too-large": "A fájl túl nagy", + "error-forwarding-chat": "A csevegés továbbítása közben hiba történt, kérjük, próbáld meg később újra.", "error-import-file-extract-error": "Az importált fájl kicsomagolása sikertelen.", "error-import-file-is-empty": "Az importált fájl üres.", "error-import-file-missing": "Az importálandó fájl nem található a megadott útvonalon.", @@ -1298,21 +1489,28 @@ "error-invalid-channel-start-with-chars": "Érvénytelen csatorna. Kezdje @ vagy a #", "error-invalid-custom-field": "Érvénytelen egyéni mező", "error-invalid-custom-field-name": "Érvénytelen egyéni mező nevét. Csak betűk, számok, kötőjel és aláhúzás.", + "error-invalid-custom-field-value": "Érvénytelen érték a __field__ mezőhöz", "error-invalid-date": "Érvénytelen dátum megadva.", "error-invalid-description": "érvénytelen leírás", "error-invalid-domain": "érvénytelen domain", "error-invalid-email": "Érvénytelen e-mail __email__", "error-invalid-email-address": "Érvénytelen e-mail cím", + "error-invalid-email-inbox": "Érvénytelen e-mail postafiók", "error-invalid-file-height": "Érvénytelen fájl magassága", "error-invalid-file-type": "Érvénytelen fájltípus", "error-invalid-file-width": "Érvénytelen fájl szélessége", "error-invalid-from-address": "Informálják érvénytelen feladó címét.", + "error-invalid-inquiry": "Érvénytelen lekérdezés", "error-invalid-integration": "érvénytelen integráció", "error-invalid-message": "érvénytelen üzenet", "error-invalid-method": "érvénytelen módszer", - "error-invalid-name": "érvénytelen név", + "error-invalid-name": "Érvénytelen név", "error-invalid-password": "Érvénytelen jelszó", + "error-invalid-param": "Érvénytelen paraméter", + "error-invalid-params": "Érvénytelen paraméterek", "error-invalid-permission": "Érvénytelen engedély", + "error-invalid-port-number": "Érvénytelen portszám", + "error-invalid-priority": "Érvénytelen prioritás", "error-invalid-redirectUri": "érvénytelen redirectUri", "error-invalid-role": "érvénytelen szerepe", "error-invalid-room": "érvénytelen szoba", @@ -1325,6 +1523,7 @@ "error-invalid-urls": "Érvénytelen URL-ek", "error-invalid-user": "Érvénytelen felhasználó", "error-invalid-username": "Érvénytelen felhasználónév", + "error-invalid-value": "Érvénytelen érték", "error-invalid-webhook-response": "A webhook URL nem 200-as HTTP státusszal válaszolt", "error-logged-user-not-in-room": "Nem vagy a `%s` szobában", "error-message-deleting-blocked": "Üzenet törlés blokkolva", @@ -1344,25 +1543,36 @@ "error-password-policy-not-met-oneUppercase": "A jelszó nem felel meg a szerver házirendjének legalább egy nagybetűs karakterének", "error-password-policy-not-met-repeatingCharacters": "A jelszó nem felel meg a kiszolgáló tiltott ismétlődő karaktereinek (túl sok azonos karakter van egymás mellett)", "error-personal-access-tokens-are-current-disabled": "A személyi hozzáférési tokenek jelenleg le vannak tiltva", + "error-pinning-message": "Az üzenetet nem lehetett kitűzni", "error-push-disabled": "Push le van tiltva", "error-remove-last-owner": "Ez az utolsó tulajdonos. Kérjük, állítsa be az új tulajdonost, mielőtt ezt eltávolítaná.", "error-role-in-use": "Nem lehet törölni a szerepet, mert használatban van", "error-role-name-required": "Szerep neve szükséges", + "error-role-already-present": "Ezzel a névvel már létezik egy szerepkör", "error-room-is-not-closed": "A szoba nincs zárva", + "error-starring-message": "Az üzenetet nem lehetett megcsillagozni", "error-the-field-is-required": "A mező __field__ szükséges.", "error-this-is-not-a-livechat-room": "Ez nem egy Livechat szoba", "error-token-already-exists": "Már létezik token ezen a néven", "error-token-does-not-exists": "A token nem létezik", "error-too-many-requests": "Hiba, túl sok kérés. Kérlek lassíts le. Meg kell várni, __seconds__ másodpercet, mielőtt újra próbálkozna.", + "error-transcript-already-requested": "Az átirat már kérvényezve", + "error-unpinning-message": "Az üzenet kitűzését nem lehetett feloldani", "error-user-has-no-roles": "A felhasználónak nincs szerepe", "error-user-is-not-activated": "Felhasználó nem aktív", + "error-user-is-not-agent": "A felhasználó nem Omnichannel ügynök", + "error-user-is-offline": "Felhasználó ha offline", "error-user-limit-exceeded": "A (z) #channel_name címre meghívott felhasználók száma meghaladja a rendszergazda által meghatározott értéket", + "error-user-not-belong-to-department": "A felhasználó nem tartozik ehhez az osztályhoz", "error-user-not-in-room": "A felhasználó nincs ebben a szobában", "error-user-registration-disabled": "Felhasználó regisztráció letiltva", "error-user-registration-secret": "Felhasználó regisztráció csak titkos URL segítségével lehetséges", + "error-no-owner-channel": "Csak a tulajdonosok adhatják hozzá ezt a csatornát a csapathoz", "error-you-are-last-owner": "Te vagy az utolsó tulajdonos. Kérjük, állítsd be az új tulajdonost, mielőtt elhagyod a szobát.", "Errors_and_Warnings": "Hibák és figyelmeztetések", "Esc_to": "Esc, hogy", + "Estimated_due_time": "Becsült esedékességi idő", + "Estimated_due_time_in_minutes": "Becsült esedékességi idő (percben kifejezve)", "Event_Trigger": "Esemény trigger", "Event_Trigger_Description": "Válassza ki, hogy az esemény típusa melyik kimenő webhook integrációt fogja indítani", "every_5_minutes": "5 percenként", @@ -1374,6 +1584,8 @@ "every_second": "Minden másodpercben", "every_six_hours": "6 óránként", "Everyone_can_access_this_channel": "Mindenki hozzáférhet ehhez a csatornához", + "Exact": "Egyező", + "Example_payload": "Példa payload", "Example_s": "Példa: %s", "except_pinned": "(kivéve azokat, amelyek rögzítettek)", "Exclude_Botnames": "Kizárja a botokat", @@ -1381,13 +1593,21 @@ "Exclude_pinned": "Kizárt rögzített üzenetek", "Execute_Synchronization_Now": "Végezze el a szinkronizálást most", "Exit_Full_Screen": "Kilépés a teljes képernyőből", + "Expand": "Kinyit", + "Expiration": "Lejárat", + "Expiration_(Days)": "Lejárat (napok)", + "Export_as_file": "Exportálás fájlként", + "Export_Messages": "Üzenetek exportálása", "Export_My_Data": "Adataim exportálása", "expression": "Kifejezés", "Extended": "Kiterjesztett", + "External": "Külső", "External_Domains": "Külső domainek", "External_Queue_Service_URL": "Külső sor szolgáltatás URL-je", "External_Service": "Külső szolgáltatás", "External_Users": "Külső felhasználók", + "Extremely_likely": "Rendkívül valószínű", + "Facebook": "Facebook", "Facebook_Page": "Facebook oldal", "Failed": "Sikertelen", "Failed_to_activate_invite_token": "Nem sikerült aktiválni a meghívó tokent", @@ -1399,8 +1619,17 @@ "Favorite_Rooms": "Engedélyezze Kedvenc szobák", "Favorites": "Kedvencek", "Feature_Depends_on_Livechat_Visitor_navigation_as_a_message_to_be_enabled": "Ez a funkció a \"Látogatók navigálási előzményeinek küldése üzenetként való elküldéséhez\" tartozik.", + "Features": "Jellemzők", "Features_Enabled": "Jellemzők Enabled", + "Federation_Username": "Felhasználónév: myfriendsusername@anotherdomain.com", + "Federation_Email": "E-mail cím: joseph@remotedomain.com", + "Federation_Invite_User": "Felhasználó meghívása", + "Federation_Adding_users_from_another_server": "Felhasználók hozzáadása egy másik kiszolgálóról", + "Federation_Configure_DNS": "DNS konfigurálása", "Federation_Domain": "Domain", + "Federation_Fix_now": "Javítsd meg most!", + "Federation_Legacy_support": "Legacy támogatás", + "Federation_HTTP_instead_HTTPS": "Ha a HTTP protokollt használja a HTTPS helyett", "Federation_Public_key": "Publikus kulcs", "Federation_Discovery_method": "Felfedezési mód", "Federation_Protocol": "Protokoll", @@ -1413,13 +1642,19 @@ "Field": "Mező", "Field_removed": "Field eltávolított", "Field_required": "Szükséges mező", + "File": "Fájl", + "File_Downloads_Started": "Fájlok letöltése megkezdődött", "File_exceeds_allowed_size_of_bytes": "Fájl mérete meghaladja a megengedett méretet a __size__ bájt", "File_name_Placeholder": "Fájlok keresése ...", "File_not_allowed_direct_messages": "A fájlmegosztás nem engedélyezett a közvetlen üzenetekben.", + "File_Path": "Fájl elérési útvonal", "File_removed_by_automatic_prune": "A fájl eltávolítása automatikus prúzással történik", "File_removed_by_prune": "A fájl eltávolítása a prúzással történik", + "File_Type": "Fájltípus", "File_type_is_not_accepted": "A fájltípus nem fogadható el.", "File_uploaded": "Fájl feltöltve", + "File_uploaded_successfully": "Fájl sikeresen feltöltve", + "File_URL": "Fájl URL", "files": "fájlok", "Files": "Fájlok", "Files_only": "Csak távolítsa el a csatolt fájlokat, tartsa meg az üzeneteket", @@ -1428,6 +1663,7 @@ "FileSize_KB": "__fileSize__ KB", "FileSize_MB": "__fileSize__ MB", "FileUpload": "Fájlfeltöltés", + "FileUpload_Cannot_preview_file": "Nem lehet előnézetben megjeleníteni a fájlt", "FileUpload_Disabled": "A fájl feltöltések le vannak tiltva.", "FileUpload_Enabled": "Fájlfeltöltések engedélyezve", "FileUpload_Enabled_Direct": "Fájl feltöltés engedélyezése a közvetlen üzeneteknél", @@ -1446,11 +1682,14 @@ "FileUpload_GoogleStorage_Secret_Description": "Kérjük, kövesse ezeket az utasításokat, és illessze ide az eredményt.", "FileUpload_MaxFileSize": "Feltölthető legnagyobb fájlméret (bájtban)", "FileUpload_MaxFileSizeDescription": "Állítsa -1-re a fájlméret korlátozásának eltávolításához.", + "FileUpload_MediaType_NotAccepted__type__": "Nem elfogadott médiatípus: __type__", "FileUpload_MediaType_NotAccepted": "Média típusok Nem Elfogadva", + "FileUpload_MediaTypeBlackList": "Blokkolt médiatípusok", "FileUpload_MediaTypeWhiteList": "Elfogadott média típusok", "FileUpload_MediaTypeWhiteListDescription": "Vesszővel elválasztott listája média típusok. Hagyja üresen elfogadó minden média típus.", "FileUpload_ProtectFiles": "Feltöltött fájlok védelme", "FileUpload_ProtectFilesDescription": "Csak regisztrált felhasználók férhetnek", + "FileUpload_RotateImages_Description": "A beállítás engedélyezése a képminőség romlását okozhatja", "FileUpload_S3_Acl": "Amazon S3 acl", "FileUpload_S3_AWSAccessKeyId": "Amazon S3 AWSAccessKeyId", "FileUpload_S3_AWSSecretAccessKey": "Amazon S3 AWSSecretAccessKey", @@ -1477,7 +1716,10 @@ "FileUpload_Webdav_Upload_Folder_Path_Description": "A WebDAV mappák elérési útja, ahová a fájlokat fel kell tölteni", "FileUpload_Webdav_Username": "WebDAV felhasználónév", "Filter": "Szűrő", + "Filters": "Szűrők", "Financial_Services": "Pénzügyi szolgáltatások", + "Finish": "Befejezés", + "Finish_Registration": "Regisztráció befejezése", "First_Channel_After_Login": "Első csatorna bejelentkezés után", "First_response_time": "Első válasz idő", "Flags": "Zászlók", @@ -1492,9 +1734,12 @@ "For_your_security_you_must_enter_your_current_password_to_continue": "A folytatáshoz újra meg kell adnia jelenlegi jelszavát", "Force_Disable_OpLog_For_Cache": "Távolítsa el az OpLog gyorsítótárat", "Force_Disable_OpLog_For_Cache_Description": "Az OpLog nem használja a gyorsítótár szinkronizálását, még akkor sem, ha rendelkezésre áll", + "Force_Screen_Lock": "Képernyőzárolás kikényszerítése", + "Force_Screen_Lock_After": "Képernyőzárolás kikényszerítése után", "Force_SSL": "SSL kényszerítése", "Force_SSL_Description": "* Figyelem! * _Force SSL_ soha nem lehet fordított proxy. Ha van egy fordított proxy, meg kell tennie az átirányítást OTT. Ez a lehetőség fennáll telepítések mint Heroku, amely nem teszi lehetővé az átirányítás konfiguráció a fordított proxy.", "Force_visitor_to_accept_data_processing_consent": "Adatfeldolgozási hozzájárulás kényszerítése", + "Force_visitor_to_accept_data_processing_consent_description": "A látogatók nem kezdhetnek csevegésbe beleegyezés nélkül.", "force-delete-message": "Törölje az üzenet törlését", "force-delete-message_description": "Engedély az üzenetek törléséhez, az összes korlátozás megkerülésével", "Forgot_password": "Elfelejtetted a jelszavad?", @@ -1506,16 +1751,27 @@ "Forward_chat": "Chat továbbítása", "Forward_to_department": "Továbbítás a részlegnek", "Forward_to_user": "Továbbítás a felhasználónak", + "Forwarding": "Továbbítás", "Free": "Szabad", "Frequently_Used": "Gyakran használt", "Friday": "Péntek", "From": "Feladó", "From_Email": "E-mail feladója", "From_email_warning": "Figyelmeztetés: A mező van kitéve az e-mail szerver beállításait.", + "Full_Name": "Teljes név", "Full_Screen": "Teljes képernyő", "Gaming": "Szerencsejáték", "General": "Általános", + "Generate_new_key": "Új kulcs generálása", + "Generate_New_Link": "Új link generálása", + "Generating_key": "Kulcs generálása", "Get_link": "Hivatkozás", + "get-password-policy-forbidRepeatingCharacters": "A jelszó nem tartalmazhat ismétlődő karaktereket", + "get-password-policy-forbidRepeatingCharactersCount": "A jelszó nem tartalmazhat több mint __forbidRepeatingCharactersCount__ ismétlődő karaktert", + "get-password-policy-maxLength": "A jelszónak legfeljebb __maxLength__ karakter hosszúságúnak kell lennie", + "get-password-policy-minLength": "A jelszónak legalább __minLength__ karakter hosszúságúnak kell lennie", + "get-password-policy-mustContainAtLeastOneLowercase": "A jelszónak legalább egy kisbetűt kell tartalmaznia", + "get-password-policy-mustContainAtLeastOneNumber": "A jelszónak legalább egy számot kell tartalmaznia", "github_no_public_email": "Nincs egy email címed se publikusként megadva a GitHub fiókodban", "Give_a_unique_name_for_the_custom_oauth": "Adj egy egyedi nevet az egyéni OAuth", "Give_the_application_a_name_This_will_be_seen_by_your_users": "Adjon az alkalmazás nevét. Ez látható lesz az Ön számára.", @@ -1529,14 +1785,19 @@ "Government": "Kormány", "Graphql_CORS": "GraphQL CORS", "Graphql_Enabled": "GraphQL engedélyezve", + "Group_by": "Csoportosítás", "Group_by_Type": "Csoportosítás típus szerint", "Group_discussions": "Csoportos beszélgetések", "Group_favorites": "Kedvencek csoportosítása", "Group_mentions_disabled_x_members": "A \"@ all\" és a \"@ itt\" csoport megjegyzi, hogy több mint __total__ taggal rendelkeznek.", "Group_mentions_only": "A csoport csak említi", + "Grouping": "Csoportosítás", + "Guest": "Vendég", "Hash": "hash", "Header": "Fejléc", "Header_and_Footer": "Fejléc és lábléc", + "Pharmaceutical": "Gyógyszeripari", + "Healthcare": "Egészségügy", "Helpers": "Segítők", "Hex_Color_Preview": "Hex színek elölnézet", "Hi": "Szia", @@ -1553,6 +1814,7 @@ "Hide_Room_Warning": "Biztosan el szeretné rejteni a szobában \"%s\"?", "Hide_Unread_Room_Status": "Az olvasatlan állapot helyének elrejtése", "Hide_usernames": "Felhasználói nevek elrejtése", + "Hide_video": "Videó elrejtése", "Highlights": "Kiemelések", "Highlights_How_To": "Értesítést, ha valaki megemlíti a szót vagy kifejezést, add ide. Akkor külön szavak és kifejezések vesszővel. Kiemelés szavak nem érzékenyek.", "Highlights_List": "Kiemelés szavak", @@ -1566,6 +1828,7 @@ "How_responsive_was_the_chat_agent": "Mennyire volt készséges az operátor?", "How_satisfied_were_you_with_this_chat": "Mennyire volt elégedett ezzel a beszélgetéssel?", "How_to_handle_open_sessions_when_agent_goes_offline": "Hogyan kell kezelni az Open Sessions-t, amikor az ügynök offline állapotban van", + "I_Saved_My_Password": "Elmentettem a jelszavamat", "Idle_Time_Limit": "Tétlenségi időkorlát", "Idle_Time_Limit_Description": "Időtartam, amíg a státusz nem változik el. Az értéknek másodpercben kell lennie.", "if_they_are_from": "(ha %s-ból származik)", @@ -1583,6 +1846,7 @@ "Iframe_Integration_send_enable_Description": "Események küldése a szülőablakba", "Iframe_Integration_send_target_origin": "Cél származás küldése", "Iframe_Integration_send_target_origin_Description": "A protokoll előtaggal való származás, mely parancsokat elküldjük pl. 'https: // localhost', vagy * a küldés bárhová történő küldéséhez.", + "Iframe_Restrict_Access": "A hozzáférés korlátozása bármelyik Iframe-en belül", "Ignore": "Figyelmen kívül hagyni", "Ignored": "Figyelmen kívül hagyva", "Images": "Képek", @@ -1619,18 +1883,28 @@ "Importer_Slack_Users_CSV_Information": "A feltöltött fájlnak laza felhasználók exportfájlja, amely CSV fájl. További információk itt találhatók:", "Importer_Source_File": "Forrásfájl kiválasztása", "importer_status_done": "Sikeresen befejezve", + "importer_status_downloading_file": "Fájl letöltése", + "importer_status_file_loaded": "Fájl betöltve", "importer_status_finishing": "Majdnem kész", "importer_status_import_cancelled": "Megszakítva", "importer_status_import_failed": "Hiba", "importer_status_importing_channels": "Csatornák importálása", + "importer_status_importing_files": "Fájlok importálása", "importer_status_importing_messages": "Üzenetek importálása", + "importer_status_importing_started": "Adatok importálása", "importer_status_importing_users": "Felhasználók importálása", "importer_status_new": "Nem megkezdett", + "importer_status_preparing_channels": "Csatorna fájl olvasása", + "importer_status_preparing_messages": "Üzenetfájlok olvasása", + "importer_status_preparing_started": "Fájlok olvasása", + "importer_status_uploading": "Fájl feltöltése", "Importer_Upload_FileSize_Message": "A szerver beállítások a fájlok feltöltését a __maxFileSize__ méretig engedélyezik.", "Importer_Upload_Unlimited_FileSize": "A szerver beállítások bármilyen méretű fájl feltöltését engedélyezik.", "Importing_channels": "Csatornák importálása", + "Importing_Data": "Adatok importálása", "Importing_messages": "Üzenetek importálása", "Importing_users": "Felhasználók importálása", + "Inactivity_Time": "Inaktivitási idő", "In_progress": "Folyamatban", "Inclusive": "befogadó", "Incoming_Livechats": "Bejövő Livechats", @@ -1653,9 +1927,11 @@ "Instance_Record": "Példány nyilvántartás", "Instructions": "Utasítások", "Instructions_to_your_visitor_fill_the_form_to_send_a_message": "Útmutató a látogató töltse ki az űrlapot, hogy küldjön egy üzenetet", + "Insert_Contact_Name": "Írja be a kapcsolattartó nevét", "Insurance": "Biztosítás", "Integration_added": "Integráció hozzáadásra került", "Integration_Advanced_Settings": "Haladó beállítások", + "Integration_Delete_Warning": "Az integráció törlése nem vonható vissza.", "Integration_disabled": "Integráció letiltva", "Integration_History_Cleared": "Az integrációs történet sikeresen törölve", "Integration_Incoming_WebHook": "Bejövő WebHook integráció", @@ -1721,12 +1997,13 @@ "Invitation_Email_Description": "Használhatja a következő szimbólumokat:
      • [email] A címzett e-mail.
      • [Site_Name] és [Site_URL] Az Alkalmazás neve és URL ill.
      ", "Invitation_HTML": "Meghívó HTML", "Invitation_HTML_Default": "

      Ön meghívást kapott [Site_Name] oldalra

      Tovább a [Site_URL], és próbálja ki a ma elérhető legjobb nyílt forráskódú chat megoldást!

      ", - "Invitation_Subject": "Meghívó Tárgy", + "Invitation_Subject": "Meghívó tárgya", "Invitation_Subject_Default": "Ön meghívást kaptak [Site_Name]", "Invite_user_to_join_channel": "Kérj meg egy felhasználó számára, hogy csatlakozzon ehhez a csatornához", "Invite_user_to_join_channel_all_from": "Hívja meg a [#channel] összes felhasználóját, hogy csatlakozzon ehhez a csatornához", "Invite_user_to_join_channel_all_to": "A csatornáról minden felhasználó meghívja a [#channel]", "Invite_Users": "Felhasználók meghívása", + "IP": "IP cím", "IRC_Channel_Join": "A JOIN parancs kimenete.", "IRC_Channel_Leave": "A PART parancs kimenete.", "IRC_Channel_Users": "A NAMES parancs kimenete.", @@ -1755,9 +2032,11 @@ "italics": "dőlt", "Jitsi_Chrome_Extension": "Chrome-bővítmény Id", "Jitsi_Enable_Channels": "Engedélyezze a csatornák", + "Jitsi_Enable_Teams": "Engedélyezés a csapatok számára", "Jitsi_Enabled_TokenAuth": "JWT hitelesítés engedélyezése", "Job_Title": "Munka megnevezése", "join": "Csatlakozás", + "Join_call": "Csatlakozz a híváshoz", "Join_audio_call": "Csatlakozás audio hívást", "Join_Chat": "Csatlakozás beszélgetéshez", "Join_default_channels": "Csatlakozz alapértelmezett csatornák", @@ -1797,17 +2076,37 @@ "Knowledge_Base": "Tudásbázis", "Label": "Címke", "Language": "Nyelv", + "Language_Bulgarian": "Bolgár", + "Language_Chinese": "Kínai", + "Language_Czech": "Cseh", + "Language_Danish": "Dán", "Language_Dutch": "Holland", "Language_English": "Angol", + "Language_Estonian": "Észt", + "Language_Finnish": "Finn", "Language_French": "Francia", "Language_German": "Német", + "Language_Greek": "Görög", + "Language_Hungarian": "Magyar", "Language_Italian": "Olasz", + "Language_Japanese": "Japán", + "Language_Latvian": "Lett", + "Language_Lithuanian": "Litván", "Language_Not_set": "Nincs megadva", "Language_Polish": "Lengyel", "Language_Portuguese": "Portugál", + "Language_Romanian": "Román", "Language_Russian": "Orosz", + "Language_Slovak": "Szlovák", + "Language_Slovenian": "Szlovén", "Language_Spanish": "Spanyol", + "Language_Swedish": "Svéd", "Language_Version": "Angol nyelvű verzió", + "Last_7_days": "Utolsó 7 nap", + "Last_30_days": "Utolsó 30 nap", + "Last_90_days": "Utolsó 90 nap", + "Last_active": "Utoljára aktív", + "Last_Chat": "Utolsó csevegés", "Last_login": "Utolsó bejelentkezés", "Last_Message": "Utolsó üzenet", "Last_Message_At": "Utolsó üzenet", @@ -1827,6 +2126,19 @@ "Layout_Sidenav_Footer_description": "Footer mérete 260 x 70 képpont", "Layout_Terms_of_Service": "Szolgáltatás feltételei", "LDAP": "LDAP", + "LDAP_Documentation": "LDAP dokumentáció", + "LDAP_Connection": "Kapcsolat", + "LDAP_Connection_Authentication": "Hitelesítés", + "LDAP_Connection_Encryption": "Titkosítás", + "LDAP_Connection_Timeouts": "Időtúllépések", + "LDAP_UserSearch": "Felhasználó keresés", + "LDAP_UserSearch_Filter": "Keresés szűrő", + "LDAP_UserSearch_GroupFilter": "Csoport szűrő", + "LDAP_DataSync_BackgroundSync": "Háttér szinkronizálás", + "LDAP_Server_Type": "Kiszolgáló típusa", + "LDAP_Server_Type_AD": "Active Directory", + "LDAP_Server_Type_Other": "Egyéb", + "LDAP_Name_Field": "Név mező", "LDAP_Advanced_Sync": "Haladó szinkronizálás", "LDAP_Authentication": "Engedélyezze", "LDAP_Authentication_Password": "Jelszó", @@ -1843,6 +2155,7 @@ "LDAP_BaseDN_Description": "A teljesen minősített elkülönítő neve (DN) LDAP részfa szeretne keresni felhasználókat és csoportokat. Akkor adjunk hozzá annyi, amennyit akar; azonban minden csoport kell meghatározni ugyanabban a tartományban bázis a felhasználók számára, hogy tartozik hozzá. Ha megadod korlátozott felhasználói csoportok, csak azok a felhasználók, hogy tartoznak azok a csoportok lesznek hatálya alá. Javasoljuk, hogy adja meg a felső szint a LDAP fa, mint a domain bázis és használja keresési szűrő a hozzáférés szabályozására.", "LDAP_CA_Cert": "CA Cert", "LDAP_Connect_Timeout": "Kapcsolódási idő (ms)", + "LDAP_DataSync_AutoLogout": "Inaktivált felhasználók automatikus kijelentkeztetése", "LDAP_Default_Domain": "Alapértelmezett Domain", "LDAP_Default_Domain_Description": "Ha rendelkezésre áll, akkor az Alapértelmezett tartományt egyedi e-mailek létrehozására használják azon felhasználók számára, ahol az e-maileket nem importálták az LDAP-ból. Az e-mailt \"username @ default_domain\" vagy \"unique_id @ default_domain\" néven kell telepíteni.
      Példa: `rocket.chat`", "LDAP_Enable": "Engedélyezve", @@ -1873,7 +2186,7 @@ "LDAP_Login_Fallback_Description": "Ha az LDAP bejelentkezés sikertelen, próbálja meg bejelentkezni az alapértelmezett / helyi számlázási rendszerben. Segít, ha valamilyen okból leáll az LDAP.", "LDAP_Merge_Existing_Users": "Meglévő felhasználók egyesítése", "LDAP_Merge_Existing_Users_Description": "* Figyelmeztetés! * Ha egy LDAP-ból importál egy felhasználót, és ugyanaz a felhasználónév felhasználó van, az LDAP-információ és -jelszó a meglévő felhasználóra lesz beállítva.", - "LDAP_Port": "Kikötő", + "LDAP_Port": "Port", "LDAP_Port_Description": "Port eléréséhez LDAP. pl: `` 389` vagy 636` az LDAPS", "LDAP_Reconnect": "Kösse vissza", "LDAP_Reconnect_Description": "Próbáljon újból csatlakozni, ha a kapcsolat valamilyen oknál fogva megszakad a műveletek végrehajtása közben", @@ -1884,7 +2197,7 @@ "LDAP_Search_Size_Limit": "Keresési méretkorlát", "LDAP_Search_Size_Limit_Description": "A visszaadandó bejegyzések maximális száma
      ** Figyelmeztetés ** Ez a szám nagyobb, mint ** Search Page Size **", "LDAP_Sync_Now": "Háttér szinkronizálása most", - "LDAP_Sync_Now_Description": "A ** Háttér-szinkronizálást ** ** futtatja, nem pedig a ** Szinkronizálási időtartamot ** akkor is, ha a ** Háttér-szinkronizálás ** hamis.
      Ez a művelet aszinkron, kérjük, olvassa el a naplókat a folyamat", + "LDAP_Sync_Now_Description": "Ez a **Háttér szinkronizálás** műveletet indítja el most, anélkül, hogy megvárná a következő ütemezett szinkronizálást.\nEz a művelet aszinkron, további információkért tekintse meg a naplókat.", "LDAP_Sync_User_Avatar": "Szinkronizálás Avatar", "LDAP_Sync_User_Data_Channels_Admin": "Channel Adminisztrátor", "LDAP_Sync_User_Data_Channels_Filter": "Felhasználói csoport szűrő", @@ -1899,8 +2212,9 @@ "LDAP_User_Search_Filter": "Szűrő", "LDAP_User_Search_Filter_Description": "Ha meg van adva, csak azok a felhasználók, amelyek megfelelnek a szűrőt tenni, hogy jelentkezzen be. Ha nincs szűrő megadva, az összes felhasználó körén belül a megadott tartomány bázis lesz képes bejelentkezni.
      Pl Active Directory `MemberOf = cn = ROCKET_CHAT, ou = Általános Groups`.
      Pl OpenLDAP (bővíthető találat keresés) `ou: dn: = ROCKET_CHAT`.", "LDAP_User_Search_Scope": "terület", - "LDAP_Username_Field": "felhasználónév mező", + "LDAP_Username_Field": "Felhasználónév mező", "LDAP_Username_Field_Description": "Mely területen kerül felhasználásra * felhasználónév * az új felhasználók számára. Hagyja üresen használni a felhasználónevét tájékoztatni bejelentkezési oldalon.
      Használhatja sablon címkéket is, mint a `#{givenName}.#{sn}`.
      Az alapértelmezett érték `sAMAccountName`.", + "LDAP_Username_To_Search": "Felhasználónév a kereséshez", "Lead_capture_email_regex": "Lead capture email regex", "Lead_capture_phone_regex": "Lead capture phone regex", "Leave": "Elhagy", @@ -1919,7 +2233,10 @@ "Livechat_agents": "Livechat operátorok", "Livechat_Agents": "Operátorok", "Livechat_AllowedDomainsList": "Livechat engedélyezett domainek", + "Livechat_Appearance": "Livechat megjelenés", + "Livechat_close_chat": "Csevegés bezárása", "Livechat_Dashboard": "GYIK Portál", + "Livechat_enable_message_character_limit": "Üzenet karakterkorlátozás engedélyezése", "Livechat_enabled": "LiveChat engedélyezve", "Livechat_Facebook_API_Key": "OmniChannel API kulcs", "Livechat_Facebook_API_Secret": "OmniChannel API Secret", @@ -1928,16 +2245,20 @@ "Livechat_forward_open_chats_timeout": "Várakozási idő (másodpercben), a beszélgetések továbbításához", "Livechat_guest_count": "Guest Counter", "Livechat_Inquiry_Already_Taken": "A Livechat vizsgálat már megtörtént", + "Livechat_Installation": "Livechat telepítés", "Livechat_managers": "LiveChat kezelők", "Livechat_Managers": "Kezelők", + "Livechat_message_character_limit": "Livechat üzenet karakterkorlátozás", "Livechat_offline": "LiveChat nem elérhető", "Livechat_offline_message_sent": "Livechat offline üzenet elküldve", + "Livechat_OfflineMessageToChannel_enabled": "Livechat offline üzenetek küldése egy csatornára", "Livechat_online": "LiveChat elérhető", "Livechat_Queue": "Livechat sor", "Livechat_registration_form": "Regisztrációs űrlap", "Livechat_registration_form_message": "Regisztrációs űrlap üzenet", "Livechat_room_count": "GYIK szobák száma", "Livechat_Routing_Method": "Livechat útválasztási módszer", + "Livechat_status": "Livechat állapot", "Livechat_Take_Confirm": "Szeretne elvenni ezt az ügyfelet?", "Livechat_title": "LiveChat cím", "Livechat_title_color": "GYIK cím háttérszíne", @@ -1954,6 +2275,7 @@ "Livestream_url_incorrect": "Az élőszöveg URL-címe helytelen", "Load_Balancing": "Terhelés elosztás", "Load_more": "Továbbiak betöltése", + "Loading": "Betöltés", "Loading_more_from_history": "Továbbiak betöltése a történelemből", "Loading_suggestion": "Javaslatok betöltése...", "Loading...": "Betöltés...", @@ -1974,6 +2296,8 @@ "Log_View_Limit": "Naplónézetben Limit", "Logged_out_of_other_clients_successfully": "Kijelentkezett a más ügyfelek sikeresen", "Login": "Bejelentkezés", + "Login_Logs": "Bejelentkezési naplók", + "Login_Logs_Username": "Felhasználónév megjelenítése a sikertelen bejelentkezési kísérletek naplóiban", "Login_with": "Bejelentkezés %s segítségével", "Logistics": "Logisztika", "Logout": "Kijelentkezés", @@ -2039,13 +2363,18 @@ "Markdown_Parser": "Markdown Parser", "Markdown_SupportSchemesForLink": "Árleszállítás támogatási rendszereket link", "Markdown_SupportSchemesForLink_Description": "Vesszővel elválasztott listája az engedélyezett programok", + "Marketplace": "Piactér", "Marketplace_view_marketplace": "Piactér megtekintése", "Max_length_is": "A maximális hossza%s", "Max_number_incoming_livechats_displayed": "A várakozási sorban megjelenő elemet maximális száma", "Max_number_incoming_livechats_displayed_description": "(Opcionális) A Livechat várakozási sorban megjelenő elemek maximális száma.", + "Max_number_of_chats_per_agent": "Egyidejű csevegések maximális száma", + "Max_number_of_chats_per_agent_description": "Az ügynökök által egyidejűleg felvehető csevegések maximális száma", + "Max_number_of_uses": "A felhasználások maximális száma", "Maximum": "Maximális", "Media": "Média", "Medium": "Közepes", + "Members": "Tagok", "Members_List": "Tagok", "mention-all": "Mindent említ", "mention-all_description": "A @all említés engedélyezése", @@ -2066,7 +2395,7 @@ "Message_AllowEditing": "Engedélyezés Message szerkesztése", "Message_AllowEditing_BlockEditInMinutes": "Blokk Üzenet szerkesztése után (n) a jegyzőkönyvet", "Message_AllowEditing_BlockEditInMinutesDescription": "Írja 0 letiltja blokkoló.", - "Message_AllowPinning": "Engedélyezés Message rögzítéssel", + "Message_AllowPinning": "Üzenet kitűzésének engedélyezése", "Message_AllowPinning_Description": "Az üzenetek tűzve bármelyik csatornán.", "Message_AllowSnippeting": "Az üzenetek lekérdezésének engedélyezése", "Message_AllowStarring": "Engedélyezés Message Szereplők", @@ -2076,12 +2405,19 @@ "Message_Attachments": "Üzenet mellékletek", "Message_Attachments_GroupAttach": "Csoportos csatlakozó gombok", "Message_Attachments_GroupAttachDescription": "Ez csoportosítja az ikonokat egy kibontható menü alatt. Kevesebb képernyőteret vesz fel.", + "Message_Attachments_Thumbnails_Height": "A miniatűr maximális magassága (pixelben)", + "Message_Attachments_Strip_Exif": "EXIF metaadatok eltávolítása a támogatott fájlokból", "Message_Audio": "Hangüzenet", "Message_Audio_bitRate": "Hangüzenet bitráta", "Message_AudioRecorderEnabled": "Audio Recorder Enabled", "Message_AudioRecorderEnabled_Description": "Az \"audio / mp3\" fájlok elfogadott médiatípusnak kell lennie a \"Fájl feltöltése\" beállításai között.", + "Message_auditing": "Üzenet ellenőrzés", + "Message_auditing_log": "Üzenet ellenőrzési napló", "Message_BadWordsFilterList": "Add rossz szó a feketelistára", "Message_BadWordsFilterListDescription": "Add listája vesszővel elválasztott listája rossz szó, hogy kiszűrje", + "Message_BadWordsWhitelist": "Szavak eltávolítása a feketelistáról", + "Message_Characther_Limit": "Üzenet karakter limit", + "Message_Code_highlight": "Kódkiemelő nyelvek listája", "message_counter": "__counter__ üzenet", "message_counter_plural": "__counter__ üzenetek", "Message_DateFormat": "Dátum formátum", @@ -2098,6 +2434,7 @@ "Message_GroupingPeriodDescription": "Üzenetek lesznek csoportosítva a korábbi üzenetet, ha mindkettő ugyanannak a felhasználónak és az eltelt idő kevesebb volt, mint a tájékozott időt másodpercben.", "Message_HideType_au": "A \"Felhasználó hozzáadva\" üzenetek elrejtése", "Message_HideType_mute_unmute": "A \"Felhasználó elnémítva / letiltott\" üzenetek elrejtése", + "Message_HideType_r": "\"Room név megváltozott\" üzenetek elrejtése", "Message_HideType_ru": "A \"Felhasználó eltávolítva\" üzenetek elrejtése", "Message_HideType_uj": "A \"Felhasználói Csatlakozás\" üzenetek elrejtése", "Message_HideType_ul": "A \"Felhasználó szabadság\" üzenet elrejtése", @@ -2114,7 +2451,7 @@ "Message_Read_Receipt_Store_Users_Description": "Megjeleníti az egyes felhasználók olvasott beérkezéseit", "Message_removed": "üzenet eltávolított", "Message_sent_by_email": "Az e-mailben küldött üzenet", - "Message_ShowDeletedStatus": "Törölt állapota", + "Message_ShowDeletedStatus": "Törölt állapota megjelenítése", "Message_ShowEditedStatus": "Mutasd Szerkesztette állapota", "Message_ShowFormattingTips": "Itt található Formázási tippek", "Message_starring": "üzenet főszereplésével", @@ -2131,6 +2468,7 @@ "messages": "Üzenetek", "Messages": "Üzenetek", "messages_pruned": "üzenetek metszenek", + "Messages_sent": "Üzenetek elküldve", "Messages_that_are_sent_to_the_Incoming_WebHook_will_be_posted_here": "Küldött üzenetek a Bejövő WebHook felteszik itt.", "Meta": "meta", "Meta_custom": "Egyéni metacímke", @@ -2147,30 +2485,40 @@ "meteor_status_try_now_offline": "Csatlakozzon újra", "meteor_status_try_now_waiting": "Próbálja most", "meteor_status_waiting": "Várakozás a szerver csatlakozásra,", + "Method": "Módszer", "Min_length_is": "A minimális hossza%s", "Minimum": "Minimális", "Minimum_balance": "Minimális egyenleg", + "minute": "perc", "minutes": "perc", "Mobex_sms_gateway_from_number": "Ból ből", "Mobex_sms_gateway_password": "Jelszó", "Mobex_sms_gateway_username": "Felhasználónév", "Mobile": "Mobil", + "mobile-download-file": "Fájlok letöltésének engedélyezése mobileszközökön", + "mobile-upload-file": "Fájlfeltöltés engedélyezése mobileszközökön", "Mobile_Push_Notifications_Default_Alert": "Mobil értesítések alapértelmezett figyelmeztetés", "Monday": "hétfő", "Mongo_storageEngine": "Mongo tároló motor", "Mongo_version": "Mongo verzió", + "MongoDB": "MongoDB", "MongoDB_Deprecated": "A MongoDB elavult", + "MongoDB_version_s_is_deprecated_please_upgrade_your_installation": "A MongoDB %s verziója elavult, kérjük frissítse a telepítést.", "Monitor_history_for_changes_on": "Figyelmeztetési előzmények a változásokhoz", + "Monthly_Active_Users": "Havi aktív felhasználók", "More": "Több", "More_channels": "Még több csatorna", "More_direct_messages": "Több közvetlen üzenetek", "More_groups": "Több privát csoport", - "More_unreads": "több unreads", + "More_unreads": "További olvasatlanok", + "Most_popular_channels_top_5": "Legnépszerűbb csatornák (Top 5)", "Move_beginning_message": "`%s` - Áthelyezés az üzenet elejére", "Move_end_message": "`%s` - Áthelyezés az üzenet végére", + "Move_queue": "Mozgatás a várakozási sorba", "Msgs": "Üzik", "multi": "több", "multi_line": "többsoros", + "Mute": "Némítás", "Mute_all_notifications": "Az összes értesítés elnémítása", "Mute_Focused_Conversations": "Némítás célzott beszélgetések", "Mute_Group_Mentions": "@all és @here említések némítása", @@ -2186,11 +2534,16 @@ "N_new_messages": "%s új üzenet", "Name": "Név", "Name_cant_be_empty": "A név nem lehet üres", - "Name_of_agent": "Az anyag neve", + "Name_of_agent": "Az operátor neve", "Name_optional": "Név (kötelező)", "Name_Placeholder": "Kérem írja be a nevét...", "Navigation_History": "navigációs története", + "Next": "Következő", + "Never": "Soha", + "New": "Új", "New_Application": "új alkalmazás", + "New_Canned_Response": "Új válasz sablon", + "New_Contact": "Új kapcsolat", "New_Custom_Field": "Új egyéni mező", "New_Department": "új Osztály", "New_discussion": "Új beszélgetés", @@ -2203,25 +2556,36 @@ "New_messages": "Új üzenet", "New_password": "Új jelszó", "New_Password_Placeholder": "Adjon meg új jelszót ...", + "New_Priority": "Új prioritás", "New_role": "Új szerep", "New_Room_Notification": "Új szoba értesítés", + "New_Tag": "Új címke", "New_Trigger": "Új trigger", + "New_Unit": "Új egység", + "New_users": "Új felhasználók", "New_version_available_(s)": "Új verzió elérhető (%s)", "New_videocall_request": "Új videohívási kérelem", "New_visitor_navigation": "Új navigáció: __history__", "Newer_than": "Újabbak mint", "Newer_than_may_not_exceed_Older_than": "\"Újabbak, mint\" nem haladhatja meg a \"Régebbiek\"", + "Nickname": "Becenév", + "Nickname_Placeholder": "Add meg a beceneved...", "No": "Nem", "No_available_agents_to_transfer": "Nem áll rendelkezésre átruházható anyagok", + "No_Canned_Responses": "Nincsenek válasz sablonok", + "No_Canned_Responses_Yet": "Még nincsenek válasz sablonok", "No_channel_with_name_%s_was_found": "Nem található \"%s\" nevű csatorna!", "No_channels_yet": "Még nem vagy tagja egy csatornának sem.", + "No_data_found": "Nem találhatóak adatok", "No_direct_messages_yet": "Még nem kezdeményeztél beszélgetést", "No_discussions_yet": "Még nincsenek beszélgetések", "No_emojis_found": "Nem találhatóak hangulatjelek", "No_Encryption": "Nincs titkosítás", + "No_files_left_to_download": "Nincs letölthető fájl", "No_group_with_name_%s_was_found": "Nem található \"%s\" nevű privát csoport!", "No_groups_yet": "Még nincsenek privát csoportjaid", "No_integration_found": "Nincs integráció a megadott id alapján.", + "No_Limit": "Nincs korlát", "No_livechats": "Nincsenek livechats.", "No_mentions_found": "Nincs említés talált", "No_messages_yet": "Nincs üzenet még", @@ -2256,7 +2620,9 @@ "Notifications_Preferences": "Értesítések beállításai", "Notifications_Sound_Volume": "Értesítések hangereje", "Notify_active_in_this_room": "Értesítsen aktív felhasználókat ebben a szobában", - "Notify_all_in_this_room": "Értesíti az összes ebben a szobában", + "Notify_all_in_this_room": "Értesítse az összes tagot ebben a szobában", + "Default_Server_Timezone": "Szerver időzóna", + "Default_Custom_Timezone": "Egyéni időzóna", "Num_Agents": "# Ügynök", "Number_of_events": "Események száma", "Number_of_messages": "Üzenetek száma", @@ -2279,6 +2645,8 @@ "Offline_Mention_All_Email": "Az összes e-mail tárgyának említése", "Offline_Mention_Email": "Megemlítés e-mail tárgya", "Offline_message": "Offline üzenet", + "Offline_Message": "Offline üzenet", + "Offline_messages": "Offline üzenetek", "Offline_success_message": "Offline siker üzenet", "Offline_unavailable": "Offline nem érhető el", "Old Colors": "Régi színek", @@ -2288,7 +2656,9 @@ "online": "online", "Online": "Elérhető", "Only_authorized_users_can_write_new_messages": "Csak az engedélyezett felhasználók írhatnak új üzeneteket", + "Only_authorized_users_can_react_to_messages": "Csak a bejelentkezett felhasználók reagálhatnak az üzenetekre", "Only_from_users": "Csak szúrja be ezeket a felhasználók tartalmát (üresen hagyja az összes felhasználót)", + "Only_Members_Selected_Department_Can_View_Channel": "Csak a kiválasztott részleg tagjai láthatják a csatornán zajló csevegéseket", "Only_On_Desktop": "Asztali mód (csak az asztali gépen lévő Enter billentyűvel küldi el)", "Only_you_can_see_this_message": "Csak akkor látni ezt az üzenetet", "Oops_page_not_found": "Hoppá, az oldal nem található", @@ -2323,32 +2693,47 @@ "OS_Uptime": "Operációs rendszer indítása óta eltelt idő", "Other": "Más", "others": "mások", + "Others": "Egyebek", "OTR": "OTR", "OTR_is_only_available_when_both_users_are_online": "OTR csak ha mindkét online", + "Out_of_seats": "Nincs több hely", "Outgoing_WebHook": "Kimenő webhook", "Outgoing_WebHook_Description": "Készítsen adatokat a Rocket.Chatból valós időben.", + "Output_format": "Kimeneti formátum", "Override_URL_to_which_files_are_uploaded_This_url_also_used_for_downloads_unless_a_CDN_is_given": "Felülírása URL, amelyre fájlok feltöltése. Ez az URL is használható letöltések hacsak CDN kap", "Page_title": "Lap cím", "Page_URL": "Az oldal URL-je", + "Pages": "Oldalak", "Parent_channel_doesnt_exist": "A Channel nem létezik.", + "Participants": "Résztvevők", "Password": "Jelszó", "Password_Change_Disabled": "Az Rocket.Chat rendszergazda letiltotta a változó jelszavakat", + "Password_Changed_Email_Subject": "[Site_Name] - A jelszó megváltozott", + "Password_changed_section": "A jelszó megváltozott", "Password_changed_successfully": "Jelszó sikeresen módosítva", + "Password_History": "Jelszó előzmények", + "Password_History_Amount": "Jelszó előzmények hossza", "Password_Policy": "Jelszószabályzat", + "Password_to_access": "Jelszó a hozzáféréshez", + "Passwords_do_not_match": "A jelszavak nem egyeznek", "Past_Chats": "Korábbi beszélgetések", + "Paste_here": "Beillesztés ide...", + "Paste": "Beillesztés", "Payload": "Hasznos teher", + "PDF": "PDF", "Peer_Password": "Peer jelszó", "People": "Emberek", "Permalink": "Permalink", "Permissions": "Engedélyek", - "Personal_Access_Tokens": "Személyes hozzférési token", + "Personal_Access_Tokens": "Személyes hozzáférési token", + "Phone": "Telefon", "Phone_number": "Telefonszám", "Pin": "Pin", "Pin_Message": "Rögzít", "pin-message": "Rögzít", "pin-message_description": "Engedély a csatorna üzenetének beillesztésére", "Pinned_a_message": "Üzenet rögzítve:", - "Pinned_Messages": "Rögzített üzenetek", + "Pinned_Messages": "Kitűzött üzenetek", "PiwikAdditionalTrackers": "További Piwik webhelyek", "PiwikAdditionalTrackers_Description": "Adj meg további Piwik webhely URL-eket és webhelyazonosítóit a következő formátumban, ha követheti szeretnéd ugyanazokat az adatokat a különböző webhelyekhez: [{ \"trackerURL\" : \"https://my.piwik.domain2/\", \"siteId\" : 42 }, { \"trackerURL\" : \"https://my.piwik.domain3/\", \"siteId\" : 15 }]", "PiwikAnalytics_cookieDomain": "Minden aldomain", @@ -2397,15 +2782,20 @@ "Preparing_list_of_messages": "Az üzenetek listájának előkészítése", "Preparing_list_of_users": "A felhasználó listájának előkészítése", "Presence": "Jelenlét", + "Preview": "Előnézet", "preview-c-room": "Nyilvános csatorna megtekintése", "preview-c-room_description": "Engedély a nyilvános csatorna tartalmának megtekintéséhez a csatlakozás előtt", "Previous_month": "Előző hónap", "Previous_week": "Előző hét", + "Priorities": "Prioritások", + "Priority": "Prioritás", + "Priority_removed": "Prioritás eltávolítva", "Privacy": "Adatvédelem", "Privacy_Policy": "Adatvédelmi irányelvek", "Private": "Magán", "Private_Channel": "Privát csatorna", - "Private_Group": "Private Csoport", + "Private_Chats": "Privát csevegések", + "Private_Group": "Privát csoport", "Private_Groups": "Privát csoportok", "Private_Groups_list": "List of Private Groups", "Private_Team": "Privát csapat", @@ -2415,6 +2805,9 @@ "Profile_picture": "Profilkép", "Profile_saved_successfully": "Profil sikeresen mentve", "Prometheus": "Prométheusz", + "Prometheus_API_User_Agent": "API: User Agent követése", + "Prometheus_Garbage_Collector": "NodeJS GC gyűjtése", + "Prometheus_Reset_Interval": "Visszaállítási időköz (ms)", "Protocol": "Protokoll", "Prune": "Aszalt szilva", "Prune_finished": "Prune kész", @@ -2429,10 +2822,12 @@ "Public": "Nyilvános", "Public_Channel": "Nyilvános csatorna", "Public_Community": "Nyilvános közösség", + "Public_URL": "Nyilvános URL", "Purchase_for_free": "Vedd meg INGYEN", "Purchase_for_price": "Vedd", "Purchased": "Megvásárolt", "Push": "Nyom", + "Push_Notifications": "Push értesítések", "Push_apn_cert": "APN Cert", "Push_apn_dev_cert": "APN Dev Cert", "Push_apn_dev_key": "APN Dev Key", @@ -2450,7 +2845,8 @@ "Push_show_username_room": "-Es csatorna / csoport / felhasználónév az értesítési", "Push_test_push": "Teszt", "Query": "kérdés", - "Query_description": "További feltételek meghatározására, hogy mely felhasználók küldeni a levelet. Feliratkozott felhasználók automatikusan eltávolításra kerülnek a lekérdezés. Meg kell egy érvényes JSON. Példa: \"{\" CreatedAt \": {\" $ gt \": {\" $ date \":\" 2015-01-01T00: 00: 00.000Z \"}}}\"", + "Query_description": "További feltételek meghatározására, hogy mely felhasználók küldeni a levelet. Feliratkozott felhasználók automatikusan eltávolításra kerülnek a lekérdezés. Meg kell egy érvényes JSON. Példa: \"{\"createdAt\":{\"$gt\":{\"$date\": \"2015-01-01T00:00:00.000Z\"}}}\"", + "Query_is_not_valid_JSON": "A lekérdezés nem érvényes JSON", "Queue": "sorban áll", "quote": "idézet", "Quote": "Idézet", @@ -2473,7 +2869,7 @@ "Receive_alerts": "Értesítések fogadása", "Receive_Group_Mentions": "Fogadja a @all és a @here utalásokat", "Recent_Import_History": "Legutóbbi importálás történet", - "Record": "Rekord", + "Record": "Bejegyzés", "Redirect_URI": "Átirányítás URI", "Refresh": "Frissítés", "Refresh_keys": "Frissítés kulcsok", @@ -2502,7 +2898,7 @@ "Reload": "Reload", "Reload_Pages": "Oldalak újratöltése", "Remove": "Eltávolít", - "Remove_Admin": "Távolítsuk Admin", + "Remove_Admin": "Admin eltávolítása", "Remove_as_leader": "Vegye le a vezetőt", "Remove_as_moderator": "Távolítsuk el a moderátor", "Remove_as_owner": "Távolítsuk el a tulajdonos", @@ -2529,6 +2925,7 @@ "Report_sent": "Report küldött", "Report_this_message_question_mark": "Üzenet jelentése?", "Reporting": "Jelentés", + "required": "szükséges", "Require_all_tokens": "Szükséges minden token", "Require_any_token": "Kérjen minden tokenet", "Require_password_change": "Igényel jelszócsere", @@ -2574,6 +2971,7 @@ "Return_to_home": "Vissza a főoldalra", "Return_to_previous_page": "Vissza az előző oldalra", "Robot_Instructions_File_Content": "Robots.txt fájl tartalma", + "No_Referrer": "Nincs ajánló", "Rocket_Chat_Alert": "Rockat.Chat figyelmeztetés", "Role": "Szerep", "Role_Editing": "szerep szerkesztése", @@ -2607,7 +3005,7 @@ "Room_type_of_default_rooms_cant_be_changed": "Ez az alapértelmezett hely, és a típus nem módosítható, kérjük, forduljon a rendszergazdájához.", "Room_unarchived": "szoba archivált", "Room_uploaded_file_list": "Fájlok", - "Room_uploaded_file_list_empty": "Nincs fájl is elérhető.", + "Room_uploaded_file_list_empty": "Nincsenek elérhető fájlok.", "Rooms": "Szobák", "Routing": "Útválasztás", "Run_only_once_for_each_visitor": "Csak egyszer fusson látogatónként", @@ -2647,10 +3045,14 @@ "SAML_Default_User_Role_Description": "Több szerepkört is megadhat, vesszővel elválasztva.", "SAML_Role_Attribute_Name": "Szerepkör attribútum neve", "SAML_Section_1_User_Interface": "Felhasználói felület", + "SAML_Section_2_Certificate": "Tanúsítvány", + "SAML_Section_3_Behavior": "Viselkedés", + "SAML_Section_4_Roles": "Szerepkörök", + "SAML_Section_6_Advanced": "Haladó", "Saturday": "szombat", "Save": "Mentés", "Save_changes": "Változtatások mentése", - "Save_Mobile_Bandwidth": "Sávszélesség kímélő mód", + "Save_Mobile_Bandwidth": "Mobil sávszélesség kímélő mód", "Save_to_enable_this_action": "Mentse, hogy ezt a műveletet", "Save_To_Webdav": "Mentés a WebDAV-ba", "save-others-livechat-room-info": "Mások mentése a Livechat szobapiacon", @@ -2677,10 +3079,14 @@ "seconds": "másodperc", "Secret_token": "titkos token", "Security": "Biztonság", - "Select_a_department": "Válasszon egy osztály", + "See_full_profile": "Teljes profil megtekintése", + "Select_a_department": "Válasszon részleget", + "Select_a_room": "Válasszon ki egy szobát", "Select_a_user": "Válasszon ki egy felhasználót", "Select_an_avatar": "Válassz képet", "Select_an_option": "Válassz egy lehetőséget", + "Select_at_least_one_user": "Válasszon legalább egy felhasználót", + "Select_at_least_two_users": "Válasszon legalább két felhasználót", "Select_department": "Válasszon egy osztály", "Select_file": "File kiválasztása", "Select_role": "Válasszon egy szerepkört", @@ -2690,7 +3096,7 @@ "Select_users": "felhasználók kiválasztása", "Selected_agents": "Válogatott szerek", "Selecting_users": "Felhasználók kiválasztása", - "Send": "Elküld", + "Send": "Küldés", "Send_a_message": "Üzenetet küldeni", "Send_a_test_mail_to_my_user": "Küldj egy teszt mailt a használati", "Send_a_test_push_to_my_user": "Küldj egy teszt push a használati", @@ -2716,6 +3122,7 @@ "Sent_an_attachment": "Küldött egy mellékletet", "Served_By": "Szolgált", "Server": "Kiszolgáló", + "Server_File_Path": "Szerver fájl elérési útvonal", "Server_Info": "Szerverinformáció", "Server_Type": "Szerver típusa", "Service": "Szolgáltatás", @@ -2737,7 +3144,10 @@ "Setup_Wizard": "Beállítási varázslót", "Setup_Wizard_Info": "Útmutatunk az első adminisztrátor felhasználójának beállításához, a szervezet konfigurálásához és a szerver regisztrációjához, hogy ingyenes push-értesítéseket kapjunk és így tovább.", "Share_Location_Title": "Megosztás helye?", + "Share_screen": "Képernyő megosztása", + "Sharing": "Megosztás", "Shared_Location": "Megosztott hely", + "Shared_Secret": "Megosztott titkos kulcs", "Should_be_a_URL_of_an_image": "Kell egy kép URL-je.", "Should_exists_a_user_with_this_username": "A felhasználó már léteznie kell.", "Show_agent_email": "Az ügynök e-mailjének megjelenítése", @@ -2757,7 +3167,9 @@ "Show_room_counter_on_sidebar": "Mutasd a helyiségszámlálót az oldalsávon", "Show_Setup_Wizard": "A telepítővarázsló megjelenítése", "Show_the_keyboard_shortcut_list": "Mutassa be a billentyűparancsok listáját", + "Show_video": "Videó megjelenítése", "Showing_archived_results": "

      A következő %s archivált eredmények

      ", + "Showing_online_users": null, "Showing_results": "

      %s eredmény megjelenítve

      ", "Sidebar": "Oldalsáv", "Sidebar_list_mode": "Oldalsáv csatornalista mód", @@ -2799,6 +3211,7 @@ "Smarsh_MissingEmail_Email_Description": "Az e-mail, amelyet egy felhasználói fiók megjelenítéséhez használnak, amikor az e-mail címük hiányzik, általában a bot-fiókokkal történik.", "Smarsh_Timezone": "Smarsh időzóna", "Smileys_and_People": "Hangulatjelek és emberek", + "SMS": "SMS", "SMS_Enabled": "SMS-ek bekapcsolva", "SMTP": "SMTP", "SMTP_Host": "SMTP Host", @@ -2815,6 +3228,7 @@ "Social_Network": "Közösségi háló", "Sorry_page_you_requested_does_not_exist_or_was_deleted": "Sajnáljuk, a kért oldal nem létezik, vagy törölték!", "Sort": "Rendezés", + "Sort_By": "Rendezés", "Sort_by_activity": "Rendezés tevékenység szerint", "Sound": "Hang", "Sound_File_mp3": "Hangfájl (mp3)", @@ -2838,7 +3252,8 @@ "Statistics": "Statisztika", "Statistics_reporting": "Küldje statisztikák Rocket.Chat", "Statistics_reporting_Description": "Elküldi a statisztikát, akkor segítenek azonosítani, hogy hány példányban Rocket.Chat telepítettek, valamint, hogy milyen jó a rendszer úgy viselkedik, így tudjuk tovább javítani. Ne aggódj, a felhasználói információ nem küldött, és az összes információt kapunk bizalmasan kezeljük.", - "Stats_Active_Users": "aktív felhasználók", + "Stats_Active_Users": "Aktivált felhasználók", + "Stats_App_Users": "Rocket.Chat alkalmazás felhasználók", "Stats_Avg_Channel_Users": "Átlagos Channel Felhasználók", "Stats_Avg_Private_Group_Users": "Átlagos Private Csoport Felhasználók", "Stats_Away_Users": "Idegenben felhasználók", @@ -2863,11 +3278,11 @@ "Stats_Total_Messages_Livechat": "Total Messages in Livechats", "Stats_Total_Messages_PrivateGroup": "Összes üzenet privát csoportokban", "Stats_Total_Outgoing_Integrations": "Összes kimenő integráció", - "Stats_Total_Private_Groups": "Összesen Private Groups", + "Stats_Total_Private_Groups": "Összes privát csoport", "Stats_Total_Rooms": "Összesen szobák", "Stats_Total_Uploads": "Összes feltöltés", "Stats_Total_Uploads_Size": "Összes feltöltés mérete", - "Stats_Total_Users": "felhasználók", + "Stats_Total_Users": "Összes felhasználó", "Status": "Állapot", "StatusMessage": "Állapot üzenet", "StatusMessage_Change_Disabled": "A Rockat.Chat adminisztrátor letiltotta az állapot üzenetek módosítását", @@ -2891,7 +3306,7 @@ "Sunday": "vasárnap", "Support": "Támogatás", "Survey": "Felmérés", - "Survey_instructions": "Szavazz minden kérdésre az Ön elégedettsége, 1 ami azt jelenti, teljesen elégedetlen, és 5 ami azt jelenti, hogy teljesen elégedett.", + "Survey_instructions": "Értékelje a kérdéseket elégedettségétől függően. (1: teljesen elégedetlen, 5: teljesen elégedett)", "Symbols": "Szimbólumok", "Sync": "Szinkronizálás", "Sync / Import": "Szinkronizálás / Importálás", @@ -2901,19 +3316,39 @@ "Sync_Users": "Szinkronizálás felhasználók", "System_messages": "Rendszerüzenetek", "Tag": "Címke", + "Tags": "Címkék", + "Tag_removed": "Címke eltávolítva", + "Tag_already_exists": "A címke már létezik", "Take_it": "Vedd el!", "Target user not allowed to receive messages": "A célfelhasználó nem fogadhat üzeneteket", "TargetRoom": "Target szoba", "TargetRoom_Description": "Az a hely, ahol az üzenetek elküldésre kerülnek, amelyek ennek az eseménynek a következményei. Csak egy célteret engedélyezhet és léteznie kell.", + "Team_Add_existing": "Meglévő hozzáadása", + "Team_Channels": "Csapat Channel", + "Team_Delete_Channel_modal_content_danger": "Ez nem vonható vissza.", + "Team_Info": "Csapat információ", + "Team_Remove_from_team": "A csapatból való eltávolítás", "Team": "Csapat", + "Teams": "Csapatok", + "Teams_Errors_Already_exists": "A `__name__` csapat már létezik.", + "Teams_Errors_team_name": "Nem használhatod a \"__name__\" szót csapatnévként.", "Teams_New_Name_Label": "Név", + "Teams_leave": "Csapat elhagyása", + "Teams_left_team_successfully": "Sikeresen elhagyta a csapatot", + "Teams_members": "Csapatok tagjai", + "Teams_New_Add_members_Label": "Tagok hozzáadása", "Teams_New_Broadcast_Description": "Csak az engedélyezett felhasználók írhatnak új üzeneteket, de a többi felhasználó is tud válaszolni", "Teams_New_Description_Label": "Téma", "Teams_New_Encrypted_Label": "Titkosított", "Teams_New_Private_Label": "Magán", + "Teams_Public_Team": "Nyilvános csapat", "Teams_Private_Team": "Privát csapat", + "Teams_removing__username__from_team": "Ön eltávolítja __username__-t ebből a csapatból", + "Teams_Select_a_team": "Válasszon egy csapatot", + "Teams_Search_teams": "Keresés a csapatok között", "Teams_New_Read_only_Label": "Csak olvasható", "Technology_Services": "Technológiai szolgáltatások", + "Terms": "Feltételek", "Test_Connection": "kapcsolat tesztelése", "Test_Desktop_Notifications": "Asztali értesítések tesztelése", "Texts": "Szövegek", @@ -2921,7 +3356,8 @@ "Thank_you_for_your_feedback": "Köszönjük a visszajelzést", "The_application_name_is_required": "Az alkalmazás neve szükséges", "The_channel_name_is_required": "A csatorna neve szükséges", - "The_emails_are_being_sent": "Az e-mailek küldésére is.", + "The_emails_are_being_sent": "Az e-maileket elküldtük.", + "The_empty_room__roomName__will_be_removed_automatically": "Az üres szoba __roomName__ automatikusan eltávolításra kerül.", "The_field_is_required": "A %s mezőben van szükség.", "The_image_resize_will_not_work_because_we_can_not_detect_ImageMagick_or_GraphicsMagick_installed_in_your_server": "A kép átméretezés nem fog működni, mert nem tudjuk észlelni ImageMagick vagy GraphicsMagick szerveren telepítve.", "The_message_is_a_discussion_you_will_not_be_able_to_recover": "Az üzenet egy beszélgetés, amit nem fogsz tudni visszaállítani!", @@ -2960,6 +3396,7 @@ "theme-color-rc-color-error-light": "Hiba jelzőfény", "theme-color-rc-color-link-active": "Active link", "theme-color-rc-color-primary": "Elsődleges", + "theme-color-rc-color-primary-background": "Elsődleges háttér", "theme-color-rc-color-primary-dark": "Elsődleges sötét", "theme-color-rc-color-primary-darkest": "Elsődleges sötét", "theme-color-rc-color-primary-light": "Elsődleges fény", @@ -2993,6 +3430,7 @@ "There_are_no_users_in_this_role": "Nincsenek felhasználók ebben a szerepben.", "There_is_one_or_more_apps_in_an_invalid_state_Click_here_to_review": "Egy vagy több alkalmazás érvénytelen állapotban van. Kattintson ide a felülvizsgálathoz.", "This_agent_was_already_selected": "Az operátort már kiválasztották", + "This_cant_be_undone": "Ez nem visszavonható.", "This_conversation_is_already_closed": "Ez a beszélgetés már lezárult.", "This_email_has_already_been_used_and_has_not_been_verified__Please_change_your_password": "Ez az e-mail már felhasználták, és még nem igazolták. Kérjük, változtassa meg a jelszavát.", "This_is_a_desktop_notification": "Ez egy asztali értesítés", @@ -3006,8 +3444,10 @@ "Thread_message": "A (z) * __username__ * üzenetéhez kommentált: _ __msg__ _", "Threads": "Témák", "Thursday": "csütörtök", + "Time_in_minutes": "Idő percben", "Time_in_seconds": "Az idő másodpercben", "Timeouts": "Szünetek", + "Timezone": "Időzóna", "Title": "Cím", "Title_bar_color": "Cím sáv színe", "Title_bar_color_offline": "Cím sáv színe nem elérhető", @@ -3019,6 +3459,7 @@ "To_users": "Ahhoz, hogy a felhasználók", "Today": "Ma", "Toggle_original_translated": "Váltás az eredeti / lefordítva", + "toggle-room-e2e-encryption_description": "Engedély az e2e titkosítási szoba átkapcsolására", "Token": "Token", "Token_Access": "Token hozzáférés", "Token_Controlled_Access": "Token ellenőrzött hozzáférés", @@ -3034,14 +3475,19 @@ "Tokens_Required_Input_Placeholder": "Jelvények névsora", "Topic": "Téma", "Total": "Összes", + "Total_abandoned_chats": "Összes elhagyott csevegés", "Total_conversations": "Összes beszélgetés", "Total_Discussions": "Összes beszélgetés", "Total_messages": "Összes üzenet", "Total_Threads": "Összes téma", "Total_visitors": "Össze látogató", + "TOTP Invalid [totp-invalid]": "Kód vagy jelszó érvénytelen", + "totp-invalid": "Kód vagy jelszó érvénytelen", + "Transcript": "Átirat", "Transcript_Enabled": "Kérdezd meg a látogatót, hogy szeretne-e egy átiratát lezárt csevegés után", "Transcript_message": "Üzenet a mutatóhoz, amikor a transzkriptről kérdez", "Transcript_of_your_livechat_conversation": "Az élő chat-beszélgetés átiratai.", + "Transcript_Request": "Átirat kérés", "transfer-livechat-guest": "Livechat vendégek továbbítása", "Translate": "Fordítás", "Translated": "Lefordított", @@ -3058,10 +3504,14 @@ "Two Factor Authentication": "Kétlépcsős azonosítás", "Two-factor_authentication": "Kétlépcsős azonosítás", "Two-factor_authentication_disabled": "Kétlépcsős azonosítás letiltva", + "Two-factor_authentication_email": "Kétfaktoros hitelesítés e-mailben", + "Two-factor_authentication_email_is_currently_disabled": "Az e-mailen keresztüli kétfaktoros hitelesítés jelenleg le van tiltva", "Two-factor_authentication_enabled": "Kétlépcsős azonosítás engedélyezve", "Two-factor_authentication_is_currently_disabled": "A kétlépcsős azonosítás jelenleg letiltott", "Two-factor_authentication_native_mobile_app_warning": "FIGYELMEZTETÉS: Miután engedélyezte ezt, a natív mobilalkalmazásokra (Rocket.Chat +) nem tud bejelentkezni a jelszó használatával, amíg nem hajtják végre a 2FA-t.", "Type": "Típus", + "typing": "ír", + "Types": "Típusok", "Type_your_email": "Írja be az e-mail", "Type_your_job_title": "Írja be a munkakört", "Type_your_message": "Írja be az üzenetet", @@ -3082,15 +3532,19 @@ "unarchive-room": "Unarchive szoba", "unarchive-room_description": "A csatornák unarchiválásának engedélyezése", "Unblock_User": "Felhasználó feloldása", + "Uncheck_All": "Összes kijelölés eltávolítása", "Unfavorite": "Eltávolítás a kedvencekből", "Unfollow_message": "Üzenet követés leállítása", "Unignore": "mellőzésének", "Uninstall": "Eltávolítás", + "Unknown_Import_State": "Ismeretlen importálási állapot", + "Unlimited": "Korlátlan", + "Unmute": "Némítás feloldása", "Unmute_someone_in_room": "Unmute valaki a szobában", "Unmute_user": "Felhasználó némításának visszavonása", "Unnamed": "Névtelen", "Unpin": "Rögzítés feloldása", - "Unpin_Message": "Unpin Message", + "Unpin_Message": "Kitűzött üzenet eltávolítása", "Unread": "Nem olvasott", "Unread_Count": "Olvasatlan gróf", "Unread_Count_DM": "Olvasatlan számlálás a közvetlen üzenetekhez", @@ -3100,11 +3554,16 @@ "Unread_Rooms_Mode": "Olvasatlan szobák Mode", "Unread_Tray_Icon_Alert": "Nem olvasott tálca ikon figyelmeztetés", "Unstar_Message": "Csillag eltávolítása", + "Unmute_microphone": "Mikrofon némítás feloldása", "Update": "Frissítés", + "Update_EnableChecker": "A Frissítésellenőrző engedélyezése", + "Update_every": "Frissítés minden", "Update_LatestAvailableVersion": "Frissítés a legújabb elérhető verzióra", "Update_to_version": "Frissítés __version__ verzióra", "Update_your_RocketChat": "Frissítsd a Rocket.Chat-et", "Updated_at": "Frissítve:", + "Upload": "Feltöltés", + "Uploads": "Feltöltések", "Upload_app": "Alkalmazás feltöltése", "Upload_file_description": "Fájl leírása", "Upload_file_name": "Fájl név", @@ -3115,13 +3574,19 @@ "Uploading_file": "Fájl feltöltése ...", "Uptime": "Indítás óta eltelt idő", "URL": "URL", + "URL_room_hash": "Szobanév hash engedélyezése", "URL_room_prefix": "URL szoba előtag", + "Usage": "Használat", + "Use": "Használ", "Use_account_preference": "Használja a fiók beállításokat", "Use_Emojis": "Hangulatjelek használata", "Use_Global_Settings": "Használja a globális beállításokat", "Use_initials_avatar": "Használd a felhasználónév kezdőbetűi", "Use_minor_colors": "Használjon kisebb színpalettát (az alapértelmezések örökölnek a nagyobb színeket)", + "Use_Server_configuration": "Szerver konfiguráció használata", "Use_service_avatar": "%s profilkép használata", + "Use_this_response": "Használja ezt a választ", + "Use_response": "Válasz használata", "Use_this_username": "Használd ezt a felhasználónevet", "Use_uploaded_avatar": "Feltöltött profilkép használata", "Use_url_for_avatar": "Avatar URL megadása", @@ -3132,6 +3597,7 @@ "User__username__is_now_a_leader_of__room_name_": "__username__ mostantól a __room_name__ csatorna vezetője", "User__username__is_now_a_moderator_of__room_name_": "__username__ mostantól moderátor a __room_name__ csatornán", "User__username__is_now_a_owner_of__room_name_": "__username__ mostantól tulajdonosa a __room_name__ csatornának", + "User__username__muted_in_room__roomName__": "__username__ felhasználó elnémítva a __roomName__ szobában", "User__username__removed_from__room_name__leaders": "__username__ már nem vezetője a __room_name__ csatornának", "User__username__removed_from__room_name__moderators": "__username__ már nem moderátora a __room_name__ csatornának", "User__username__removed_from__room_name__owners": "__username__ már nem tulajdonosa a __room_name__ csatornának", @@ -3177,6 +3643,7 @@ "User_sent_a_message_to_you": "__username__üzenetet küldött neked", "user_sent_an_attachment": "__user__ csatoltat küldött", "User_Settings": "felhasználói beállítások", + "User_started_a_new_conversation": "__username__ új beszélgetést indított", "User_unmuted_by": "Felhasználói __user_unmuted__ hangjának újbóli által __user_by__.", "User_unmuted_in_room": "A felhasználó elnémítása megszüntetve a szobában", "User_updated_successfully": "Felhasználó sikeresen frissítve", @@ -3218,8 +3685,13 @@ "Users must use Two Factor Authentication": "A felhasználóknak kétlépcsős azonosítást kell használniuk", "Users_added": "A felhasználók hozzáadásra kerültek", "Users_in_role": "Felhasználók szerepe", + "Uses": "Felhasználások", + "Uses_left": "Maradék felhasználások", "UTF8_Names_Slugify": "UTF8 nevek Slugify", + "Videocall_enabled": "Videohívás engedélyezve", "Validate_email_address": "Érvényesítse az e-mail címet", + "Value_messages": "__value__ üzenetek", + "Value_users": "__value__ felhasználók", "Verification": "Igazolás", "Verification_Description": "A következő helyőrzőket használhatja:
      • [Verification_Url] az ellenőrző URL-hez.
      • [név], [fname], [lname] a felhasználó teljes neve, utóneve vagy vezetékneve számára.
      • [e-mail] a felhasználó e-mailje számára.
      • [Site_Name] és [Site_URL] az Alkalmazás neve és URL címekhez.
      ", "Verification_Email": "Kattintson a ittfiókjának ellenőrzésére.", @@ -3230,12 +3702,13 @@ "Verify": "Ellenőrizze", "Verify_your_email": "Ellenőrizd az e-mail címet", "Version": "Változat", + "Version_version": "Verzió __version__", "Video Conference": "Videó konferencia", "Video_Chat_Window": "Videó Chat", "Video_Conference": "Videó konferencia", "Video_message": "Videóüzenet", "Videocall_declined": "Videóhívás visszautasítva.", - "Videocall_enabled": "Videohívás engedélyezve", + "Video_and_Audio_Call": "Videó- és hanghívás", "Videos": "Videók", "View_All": "Összes megtekintése", "View_Logs": "Naplók megtekintése", @@ -3245,8 +3718,10 @@ "view-broadcast-member-list": "View Member List a Broadcast Room-ben", "view-c-room": "Nyilvános csatorna megtekintése", "view-c-room_description": "Engedély a nyilvános csatornák megtekintéséhez", + "view-canned-responses": "Sablon válaszok megtekintése", "view-d-room": "Közvetlen üzenetek megtekintése", "view-d-room_description": "Engedély a közvetlen üzenetek megtekintéséhez", + "View_full_conversation": "Teljes beszélgetés megtekintése", "view-full-other-user-info": "Teljes többi felhasználói információ megtekintése", "view-full-other-user-info_description": "Engedély a többi felhasználó teljes profiljának megtekintésére, beleértve a fiók létrehozásának dátumát, az utolsó bejelentkezést stb.", "view-history": "Nézetelőzmények", @@ -3264,6 +3739,8 @@ "view-livechat-queue": "Livechat várólista megtekintése", "view-livechat-rooms": "Tekintse meg a Livechat szobákat", "view-livechat-rooms_description": "Engedély az egyéb élő csatornák megtekintéséhez", + "view-livechat-webhooks_description": "Az livechat webhook-ok megtekintésének engedélyezése", + "view-livechat-unit": "Livechat egységek megtekintése", "view-logs": "Naplók megtekintése", "view-logs_description": "Engedély a kiszolgáló naplóinak megtekintéséhez", "view-other-user-channels": "Egyéb felhasználói csatornák megtekintése", @@ -3285,7 +3762,11 @@ "Visible": "Látható", "Visit_Site_Url_and_try_the_best_open_source_chat_solution_available_today": "Látogasson el a __Site_URL__ oldalra, és próbálja ki a ma elérhető legjobb nyílt forráskódú chat megoldást! ", "Visitor": "Látogató", + "Visitor_Email": "Látogató E-mail", "Visitor_Info": "Látogatói információ", + "Visitor_message": "Látogatói üzenetek", + "Visitor_Name": "Látogató neve", + "Visitor_Name_Placeholder": "Kérjük, adja meg a látogató nevét...", "Visitor_Navigation": "visitor Navigation", "Visitor_page_URL": "Látogatói oldal URL", "Visitor_time_on_site": "Látogató az oldalon eltöltött idő", @@ -3303,6 +3784,7 @@ "Webdav_Server_URL": "WebDAV-kiszolgáló elérési URL-je", "Webdav_Username": "WebDAV felhasználónév", "webdav-account-saved": "WebDAV felhasználói fiók mentve", + "Webhook_Details": "WebHook részletek", "Webhook_URL": "Webhook URL", "Webhooks": "Webhooks", "WebRTC_direct_audio_call_from_%s": "Közvetlen hanghívás%s-ról", @@ -3317,16 +3799,22 @@ "WebRTC_Servers_Description": "A listát a STUN és TURN szerverek vesszővel elválasztva.
      Felhasználónév, jelszó és a port engedélyezett a formában `felhasználónév: jelszó @ stun: host: port` vagy` felhasználónév: jelszó @ fordulat: host: port`.", "Website": "Weboldal", "Wednesday": "szerda", + "Weekly_Active_Users": "Heti aktív felhasználók", "Welcome": "Üdvözöllek %s", "Welcome_to": " Üdvözöljük a(z) __Site_Name__ oldalon", "Welcome_to_the": "Üdvözöllek a", + "Where_are_the_messages_being_sent?": "Hová küldik az üzeneteket?", "Why_do_you_want_to_report_question_mark": "Miért akar jelenteni?", "will_be_able_to": "képes lesz", + "Will_be_available_here_after_saving": "A mentés után itt lesz elérhető.", + "Without_priority": "Prioritás nélkül", "Worldwide": "Világszerte", "Would_you_like_to_return_the_inquiry": "Szeretné visszaadni a vizsgálatot?", + "Would_you_like_to_place_chat_on_hold": "Szeretné várakoztatni ezt a csevegést?", "Yes": "Igen", "Yes_archive_it": "Igen, archiváld!", "Yes_clear_all": "Igen, tiszta minden!", + "Yes_deactivate_it": "Igen, kapcsolja ki!", "Yes_delete_it": "Igen, töröld!", "Yes_hide_it": "Igen, rejtsd el!", "Yes_leave_it": "Igen, hagyjuk!", @@ -3347,10 +3835,12 @@ "You_can_use_an_emoji_as_avatar": "Ön is használja a hangulatjel, mint egy avatar.", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Használhatja webhooks könnyen integrálható LiveChat a CRM.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Nem hagyhatja a LiveChat szobában. Kérjük, használja a bezárás gombot.", + "You_followed_this_message": "Követted ezt az üzenetet.", + "You_have_a_new_message": "Új üzeneted van.", "You_have_been_muted": "Akkor került lezárásra, és nem tud beszélni ebben a szobában", "You_have_n_codes_remaining": "Nincsenek __number__ kódok.", "You_have_not_verified_your_email": "Ön még nem igazolta az e-mail.", - "You_have_successfully_unsubscribed": "Sikeresen leiratkozott a Mailling List.", + "You_have_successfully_unsubscribed": "Sikeresen leiratkozott a levelező listáról.", "You_have_to_set_an_API_token_first_in_order_to_use_the_integration": "Először be kell állítania egy API tokenet az integráció használatához.", "You_must_join_to_view_messages_in_this_channel": "Csatlakozni kell ahhoz, hogy az ebben a csatornában megjelenő üzeneteket megnézhesse", "You_need_confirm_email": "Kérlek hitelesítsd az email címed a bejelntkezéshez", @@ -3362,18 +3852,28 @@ "You_need_to_write_something": "Be kell írni valamit!", "You_should_inform_one_url_at_least": "Meg kell határozni legalább egy URL-t.", "You_should_name_it_to_easily_manage_your_integrations": "Meg kell neveznie azt könnyedén kezelheti a integrációk.", + "You_unfollowed_this_message": "Már nem követed ezt az üzenetet.", "You_will_not_be_able_to_recover": "Ön nem lesz képes visszaállítani ezt az üzenetet!", "You_will_not_be_able_to_recover_file": "Ön nem lesz képes visszaállítani a fájlt!", "You_wont_receive_email_notifications_because_you_have_not_verified_your_email": "Nem fog kapni e-mailben értesítést, mert nem ellenőrizte az e-mail.", + "Your_e2e_key_has_been_reset": "Az e2e kulcsod visszaállításra került.", + "Your_email_address_has_changed": "Az e-mail címed megváltozott.", "Your_email_has_been_queued_for_sending": "Az e-mail várólistára került küldő", - "Your_entry_has_been_deleted": "A bejegyzés törölve lett.", + "Your_entry_has_been_deleted": "A bejegyzésed törölve lett.", "Your_file_has_been_deleted": "A fájl törölve lett.", + "Your_invite_link_will_expire_after__usesLeft__uses": "A meghívó linkje __usesLeft__ használat után lejár.", + "Your_invite_link_will_expire_on__date__": "A meghívó linkje lejár: __date__.", + "Your_invite_link_will_never_expire": "A meghívó linkje soha nem fog lejárni.", "Your_mail_was_sent_to_s": "A mail-ben küldött %s", "your_message": "az üzeneted", "your_message_optional": "az üzeneted (opcionális)", + "Your_new_email_is_email": "Az új e-mail címed: [email]", "Your_password_is_wrong": "A jelszó rossz!", + "Your_password_was_changed_by_an_admin": "Egy adminisztrátor megváltoztatta a jelszavad.", "Your_push_was_sent_to_s_devices": "Push küldték %s eszközök", "Your_question": "Kérdésed", "Your_server_link": "A szerver linkje", + "Your_temporary_password_is_password": "Az ideiglenes jelszavad: [password]", + "Your_TOTP_has_been_reset": "A kétfaktoros TOTP-d visszaállításra került.", "Your_workspace_is_ready": "A munkaterület készen áll a 🎉 használatára" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/id.i18n.json b/packages/rocketchat-i18n/i18n/id.i18n.json index e7cb8ccb9315b..a6187ba783e0e 100644 --- a/packages/rocketchat-i18n/i18n/id.i18n.json +++ b/packages/rocketchat-i18n/i18n/id.i18n.json @@ -555,6 +555,7 @@ "Continuous_sound_notifications_for_new_livechat_room": "Pemberitahuan suara berkelanjutan untuk ruang livechat baru", "Conversation": "Percakapan", "Conversation_closed": "Percakapan ditutup: __comment__.", + "Conversation_finished": "percakapan selesai", "Conversation_finished_message": "Pesan Selesai Percakapan", "conversation_with_s": "percakapan dengan %s", "Convert_Ascii_Emojis": "Ubah ASCII ke Emoji", @@ -2310,6 +2311,7 @@ "Show_Setup_Wizard": "Tampilkan Wizard Pengaturan", "Show_the_keyboard_shortcut_list": "Tampilkan daftar jalan pintas keyboard", "Showing_archived_results": "

      Menampilkan%s hasil diarsipkan

      ", + "Showing_online_users": null, "Showing_results": "

      Menampilkan %s hasil

      ", "Sidebar": "Sidebar", "Sidebar_list_mode": "Mode Daftar Saluran Sidebar", @@ -2689,6 +2691,7 @@ "Users_added": "Pengguna telah ditambahkan", "Users_in_role": "Pengguna dalam peran", "UTF8_Names_Slugify": "UTF8 Nama slugify", + "Videocall_enabled": "Panggilan Video Diaktifkan", "Validate_email_address": "Validasi alamat email", "Verification": "Verifikasi", "Verification_Description": "Anda dapat menggunakan placeholder berikut:
      • [Verification_Url] untuk URL verifikasi.
      • [nama], [fname], [lname] untuk nama lengkap pengguna, nama depan, atau nama belakang masing-masing.
      • [email] untuk email pengguna.
      • [Site_Name] dan [Site_URL] untuk Nama Aplikasi dan URL masing-masing.
      ", @@ -2703,7 +2706,6 @@ "Video_Conference": "Konferensi video", "Video_message": "Pesan video", "Videocall_declined": "Panggilan Video Ditolak.", - "Videocall_enabled": "Panggilan Video Diaktifkan", "View_All": "Tampilkan Semua", "View_Logs": "Lihat Log", "View_mode": "Lihat modus", @@ -2825,4 +2827,4 @@ "Your_push_was_sent_to_s_devices": "push dikirim ke%s perangkat", "Your_server_link": "Tautan server Anda", "Your_workspace_is_ready": "Ruang kerja Anda siap digunakan 🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/it.i18n.json b/packages/rocketchat-i18n/i18n/it.i18n.json index 03ce86cb6d4d9..1fb3c11791cde 100644 --- a/packages/rocketchat-i18n/i18n/it.i18n.json +++ b/packages/rocketchat-i18n/i18n/it.i18n.json @@ -299,7 +299,7 @@ "API_Upper_Count_Limit": "Numero massimo del registro", "API_Upper_Count_Limit_Description": "Qual è il numero massimo di record della REST API che può ritornare (quando non è illimitata)?", "API_User_Limit": "Limite utente per aggiungere tutti gli utenti al canale", - "API_Wordpress_URL": "URL WordPress", + "API_Wordpress_URL": "WordPress URL", "Apiai_Key": "Chiave Api.ai", "Apiai_Language": "Lingua Api.ai", "App_author_homepage": "homepage dell'autore", @@ -570,6 +570,7 @@ "Continuous_sound_notifications_for_new_livechat_room": "Notifiche continue del suono per la nuova stanza livechat", "Conversation": "Conversazione", "Conversation_closed": "Conversazione chiusa: __comment__.", + "Conversation_finished": "Conversazione terminata", "Conversation_finished_message": "Messaggio di conversazione terminato", "conversation_with_s": "la conversazione con %s", "Convert_Ascii_Emojis": "Converti gli ASCII in Emoji", @@ -918,7 +919,7 @@ "Desktop_Notifications_Enabled": "Le notifiche desktop sono abilitate", "Different_Style_For_User_Mentions": "Stile diverso per le menzioni dell'utente", "Direct_message_someone": "Invia un messaggio diretto", - "Direct_Messages": "Messaggi diretti", + "Direct_Messages": "Messaggi privati", "Direct_Reply": "Risposta diretta", "Direct_Reply_Debug": "Debug Direct Reply", "Direct_Reply_Debug_Description": "[Attenzione] L'attivazione della modalità di debug mostrerebbe la tua 'password di testo normale' nella Console di amministrazione.", @@ -2237,7 +2238,7 @@ "Room_unarchived": "Canale disarchiviato", "Room_uploaded_file_list": "Elenco dei file", "Room_uploaded_file_list_empty": "Nessun file disponibile.", - "Rooms": "Canali", + "Rooms": "Stanze", "run-import": "Esegui importazione", "run-import_description": "Autorizzazione a gestire gli importatori", "run-migration": "Esegui migrazione", @@ -2291,7 +2292,7 @@ "seconds": "secondi", "Secret_token": "Token segreto", "Security": "Sicurezza", - "Select_a_department": "Seleziona un reparto", + "Select_a_department": "Seleziona un dipartimento", "Select_a_user": "Seleziona un utente", "Select_an_avatar": "Seleziona l'avatar", "Select_an_option": "Seleziona un'opzione", @@ -2366,6 +2367,7 @@ "Show_Setup_Wizard": "Mostra procedura guidata di installazione", "Show_the_keyboard_shortcut_list": "Mostra l'elenco delle scorciatoie per la tastiera", "Showing_archived_results": "

      Mostra %s risultati archiviati

      ", + "Showing_online_users": null, "Showing_results": "

      Visualizzati %s risultati

      ", "Sidebar": "Sidebar", "Sidebar_list_mode": "Modalità Elenco canali laterale", @@ -2504,7 +2506,7 @@ "The_emails_are_being_sent": "Le email verranno inviate.", "The_field_is_required": "Il campo %s è richiesto.", "The_image_resize_will_not_work_because_we_can_not_detect_ImageMagick_or_GraphicsMagick_installed_in_your_server": "Il ridimensionamento dell'immagine non funzionerà se non rileva ImageMagick or GraphicsMagick installato sul tuo server.", - "The_redirectUri_is_required": "Il redirectUri è richiesto", + "The_redirectUri_is_required": "Il redirectUri é richiesto", "The_server_will_restart_in_s_seconds": "Il server si riavvierà in %s secondi", "The_setting_s_is_configured_to_s_and_you_are_accessing_from_s": "L'impostazione%s è configurata su %s e stai accedendo da%s!", "The_user_will_be_removed_from_s": "L'utente sarà rimosso da %s", @@ -2620,7 +2622,7 @@ "Type_your_email": "Inserisci la tua email", "Type_your_job_title": "Digita il titolo del tuo lavoro", "Type_your_message": "Inserisci la tua messaggio", - "Type_your_name": "Inserisci il tuo nome", + "Type_your_name": "Inserire il proprio nome", "Type_your_new_password": "Inserisci la nuova password", "Type_your_password": "Digita la tua password", "Type_your_username": "Inserisci il tuo nome utente", @@ -2651,7 +2653,7 @@ "Unread_Messages": "Messaggi non letti", "Unread_on_top": "Non letti sopra", "Unread_Rooms": "Canali non letti", - "Unread_Rooms_Mode": "Modalità canali non letti", + "Unread_Rooms_Mode": "Modalità Stanze Non Letta", "Unread_Tray_Icon_Alert": "Avviso icona vassoio non letto", "Unstar_Message": "Non evidenziare messaggio", "Update_your_RocketChat": "Aggiorna il tuo Rocket.Chat", @@ -2761,6 +2763,7 @@ "Users_added": "L'utente è stato aggiunto", "Users_in_role": "Utenti nel ruolo", "UTF8_Names_Slugify": "UTF8 Names Slugify", + "Videocall_enabled": "Chiamata video abilitata", "Validate_email_address": "Verifica indirizzo email", "Verification": "Verifica", "Verification_Description": "Puoi usare i seguenti segnaposti:
      • [Forgot_Password_Url] per la URL del recupero password.
      • [name], [fname], [lname] rispettivamente per il nome completo dell'utente, nome or cognome.
      • [email] per la email dell'utente.
      • [Site_Name] e [Site_URL] rispettivamente per il nome della applicazione e la URL.
      ", @@ -2778,7 +2781,6 @@ "Video_Conference": "Video conferenza", "Video_message": "Messaggio video", "Videocall_declined": "Chiamata video rifiutata", - "Videocall_enabled": "Chiamata video abilitata", "View_All": "Vedi tutto", "View_Logs": "Visualizza log", "View_mode": "Aspetto", @@ -2903,4 +2905,4 @@ "Your_push_was_sent_to_s_devices": "La tua richiesta è stata inviata ai %s dispositivi.", "Your_server_link": "Il tuo collegamento al server", "Your_workspace_is_ready": "Il tuo spazio di lavoro è pronto per l'uso 🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/ja.i18n.json b/packages/rocketchat-i18n/i18n/ja.i18n.json index 62cc14bf3154a..15ee9cec1c16a 100644 --- a/packages/rocketchat-i18n/i18n/ja.i18n.json +++ b/packages/rocketchat-i18n/i18n/ja.i18n.json @@ -892,7 +892,7 @@ "Conversation_closed": "会話が閉じました: __comment__", "Conversation_closing_tags": "会話終了タグ", "Conversation_closing_tags_description": "終了タグは、終了時に会話に自動的に割り当てられます。", - "Conversation_finished": "会話終了", + "Conversation_finished": "チャット終了", "Conversation_finished_message": "会話終了時のメッセージ", "Conversation_finished_text": "会話終了時のメッセージ", "conversation_with_s": "%sとの会話", @@ -3601,7 +3601,7 @@ "Sunday": "日曜日", "Support": "サポート", "Survey": "アンケート", - "Survey_instructions": "それぞれの設問にご満足度を、全く不満 1 〜 大変満足5 で評価してください。", + "Survey_instructions": "それぞれの設問について満足度を、不満 1 〜 満足 5 で評価してください。", "Symbols": "シンボル", "Sync": "同期する", "Sync / Import": "同期とインポート", @@ -3816,7 +3816,7 @@ "Type_your_email": "あなたのメールアドレスを入力してください", "Type_your_job_title": "職種を入力してください", "Type_your_message": "メッセージを入力", - "Type_your_name": "あなたの名前を入力します", + "Type_your_name": "名前を入力してください", "Type_your_new_password": "新しいパスワードを入力", "Type_your_password": "パスワードを入力してください", "Type_your_username": "あなたのユーザー名を入力", @@ -3999,6 +3999,7 @@ "Uses": "使用", "Uses_left": "残り", "UTF8_Names_Slugify": "UTF8形式名をスラグ化する", + "Videocall_enabled": "ビデオ通話が有効", "Validate_email_address": "電子メールアドレスの検証", "Validation": "検証", "Value_messages": "__value__ メッセージ", @@ -4020,7 +4021,6 @@ "Video_Conference": "ビデオ会議", "Video_message": "ビデオメッセージ", "Videocall_declined": "ビデオ通話が辞退しました。", - "Videocall_enabled": "ビデオ通話が有効", "Videos": "ビデオ", "View_All": "すべて表示", "View_Logs": "ログ表示", diff --git a/packages/rocketchat-i18n/i18n/ka-GE.i18n.json b/packages/rocketchat-i18n/i18n/ka-GE.i18n.json index 4c3105cdd5ea9..403028e1f582d 100644 --- a/packages/rocketchat-i18n/i18n/ka-GE.i18n.json +++ b/packages/rocketchat-i18n/i18n/ka-GE.i18n.json @@ -2888,6 +2888,7 @@ "Room_archivation_state_false": "აქტიური", "Room_archivation_state_true": "დაარქივებულია", "Room_archived": "ოთახი დაარქივებულია", + "room_changed_privacy": null, "room_changed_topic": "ოთახის თემა შეიცვალა: __room_topic__ __user_by__", "Room_default_change_to_private_will_be_default_no_more": "ეს არის დეფაულტ არხი და პირად ჯგუფად გადაკეთების შემთხვევაში აღარ იქნება დეფაულტ არხი.გსურთ გაგრძელება?", "Room_description_changed_successfully": "ოთახის აღწერა წარმატებით შეიცვალა", @@ -3578,6 +3579,7 @@ "Uses": "იყენებს", "Uses_left": "იყენებს მარცხნივ", "UTF8_Names_Slugify": "UTF8 სახელების Slugify", + "Videocall_enabled": "ვიდეო ზარი ჩართულია", "Validate_email_address": "ელ.ფოსტის მისამართების დამოწმება", "Validation": "დამოწმება", "Value_messages": "__value__ შეტყობინებები", @@ -3599,7 +3601,6 @@ "Video_Conference": "ვიდეო კონფერენცია", "Video_message": "ვიდეო შეტყობინება", "Videocall_declined": "ვიდეო ზარი უარყოფილია", - "Videocall_enabled": "ვიდეო ზარი ჩართულია", "Videos": "ვიდეოები", "View_All": "იხილეთ ყველა წევრი", "View_Logs": "ლოგების ნახვა", @@ -3747,4 +3748,4 @@ "Your_server_link": "თქვენი სერვერის მისამართი", "Your_temporary_password_is_password": "თქვენი დროებითი პაროლია არის [password]", "Your_workspace_is_ready": "თქვენი სამუშაო გარემო მზად არის სამუშაოდ 🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/km.i18n.json b/packages/rocketchat-i18n/i18n/km.i18n.json index 2a136459f14e0..8435fc2afe794 100644 --- a/packages/rocketchat-i18n/i18n/km.i18n.json +++ b/packages/rocketchat-i18n/i18n/km.i18n.json @@ -1530,7 +1530,7 @@ "hours": "ម៉ោង", "Hours": "ម៉ោង", "How_friendly_was_the_chat_agent": "តើធ្វើដូចម្តេចមិត្តភាពភ្នាក់ងារជជែកកំសាន្តនោះ?", - "How_knowledgeable_was_the_chat_agent": "តើចំណេះភ្នាក់ងារជជែកកំសាន្តនោះ?", + "How_knowledgeable_was_the_chat_agent": "តើចំណេះដឹងដែលភ្នាក់ងារជជែកកំសាន្តផ្តល់ឲ្យគ្រប់គ្រាន់ឬទេ?", "How_long_to_wait_after_agent_goes_offline": "តើត្រូវរង់ចាំប៉ុន្មានពេលដែលភ្នាក់ងារដើរនៅក្រៅអ៊ីនធឺណិត", "How_responsive_was_the_chat_agent": "តើធ្វើដូចម្តេចឆ្លើយតបភ្នាក់ងារជជែកកំសាន្តនោះ?", "How_satisfied_were_you_with_this_chat": "តើអ្នកពេញចិត្តនាក់អ្នកជាមួយនឹងការជជែកនេះ?", @@ -1985,6 +1985,7 @@ "Me": "ខ្ញុំ", "Media": "ប្រព័ន្ធផ្សព្វផ្សាយ", "Medium": "មធ្យម", + "Members": "សាមាជិក", "Members_List": "បញ្ជី​សមាជិក", "mention-all": "និយាយទាំងអស់", "mention-all_description": "ការអនុញ្ញាតប្រើប្រាស់ @all mention", @@ -2625,6 +2626,7 @@ "Show_Setup_Wizard": "បង្ហាញអ្នកជំនួយការដំឡើង", "Show_the_keyboard_shortcut_list": "បង្ហាញបញ្ជីផ្លូវកាត់ក្តារចុច", "Showing_archived_results": "

      បង្ហាញពីលទ្ធផលទុកក្នុងប័ណ្ណសារ %s បាន

      ", + "Showing_online_users": null, "Showing_results": "

      កំពុង​បង្ហាញ %s លទ្ធផល

      ", "Sidebar": "របារចំហៀង", "Sidebar_list_mode": "របៀបបញ្ជីឆានែលរបារចំហៀង", @@ -3031,6 +3033,7 @@ "Users_added": "អ្នកប្រើត្រូវបានបន្ថែម", "Users_in_role": "អ្នកប្រើនៅក្នុងតួនាទី", "UTF8_Names_Slugify": "ឈ្មោះក្រៅ UTF8 Slugify", + "Videocall_enabled": "បានបើកការហៅជាវីដេអូ", "Validate_email_address": "ធ្វើឱ្យមានអាសយដ្ឋានអ៊ីមែលមានសុពលភាព", "Verification": "ការផ្ទៀងផ្ទាត់", "Verification_Description": "អ្នកអាចប្រើកន្លែងដាក់ខាងក្រោម:
      • [Verification_Url] សម្រាប់ URL ផ្ទៀងផ្ទាត់។
      • [ឈ្មោះ], [fname], [lname] សម្រាប់ឈ្មោះអ្នកប្រើនាមត្រកូលឬនាមត្រកូលរៀងៗខ្លួន។
      • [អ៊ីមែល] សម្រាប់អ៊ីម៉ែលរបស់អ្នកប្រើ។
      • [Site_Name] និង [Site_URL] សម្រាប់ឈ្មោះកម្មវិធីនិង URL រៀងៗខ្លួន។
      ", @@ -3047,7 +3050,6 @@ "Video_Conference": "ស​ន្និ​សិ​ទ​វីដេអូ", "Video_message": "សារវីដេអូ", "Videocall_declined": "ការហៅវីដេអូបានបដិសេធ។", - "Videocall_enabled": "បានបើកការហៅជាវីដេអូ", "View_All": "មើល​ទាំង​អស់", "View_Logs": "មើលកំណត់ហេតុ", "View_mode": "របៀបមើល", @@ -3177,4 +3179,4 @@ "Your_push_was_sent_to_s_devices": "ការជំរុញរបស់អ្នកត្រូវបានបញ្ជូនទៅកាន់ឧបករណ៍ %s បាន", "Your_server_link": "តំណភ្ជាប់ម៉ាស៊ីនមេរបស់អ្នក", "Your_workspace_is_ready": "កន្លែងធ្វើការរបស់អ្នករួចរាល់ដើម្បីប្រើ🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/ko.i18n.json b/packages/rocketchat-i18n/i18n/ko.i18n.json index 361509ac5949a..0f9342ef89183 100644 --- a/packages/rocketchat-i18n/i18n/ko.i18n.json +++ b/packages/rocketchat-i18n/i18n/ko.i18n.json @@ -3927,6 +3927,7 @@ "Uses": "용도", "Uses_left": "남은 사용량", "UTF8_Names_Slugify": "UTF8 이름 Slugify", + "Videocall_enabled": "화상 통화 사용", "Validate_email_address": "이메일 주소 검증", "Validation": "검증", "Value_messages": "__value__ 메시지", @@ -3948,7 +3949,6 @@ "Video_Conference": "화상 회의", "Video_message": "화상 메시지", "Videocall_declined": "화상 통화가 거부되었습니다.", - "Videocall_enabled": "화상 통화 사용", "Videos": "동영상", "View_All": "모든 사용자 보기", "View_Logs": "로그 보기", diff --git a/packages/rocketchat-i18n/i18n/ku.i18n.json b/packages/rocketchat-i18n/i18n/ku.i18n.json index 679dfe2e0f6df..663f9dd27c72c 100644 --- a/packages/rocketchat-i18n/i18n/ku.i18n.json +++ b/packages/rocketchat-i18n/i18n/ku.i18n.json @@ -554,6 +554,7 @@ "Continuous_sound_notifications_for_new_livechat_room": "Agahiyên deng ên berdewam ji bo odeya zindî ya nû ya nû", "Conversation": "گفتوگۆ", "Conversation_closed": "Axaftina: __comment__.", + "Conversation_finished": "conversation qedand", "Conversation_finished_message": "Gotûbêja Peyama Dawîn", "conversation_with_s": "axaftina bi %s", "Convert_Ascii_Emojis": "Convert ASCII ji bo Emoji", @@ -1680,6 +1681,7 @@ "Max_length_is": "Mezinahiya%s e", "Media": "Medya", "Medium": "Medya", + "Members": "ئەندامان", "Members_List": "لیستی ئەندامان", "mention-all": "Mention All", "mention-all_description": "Destûra karûbarê @allê bikar bînin", @@ -2294,6 +2296,7 @@ "Show_Setup_Wizard": "Vebijêrk Setup", "Show_the_keyboard_shortcut_list": "Lîsteya kurteya klavyeyê nîşan bide", "Showing_archived_results": "

      Rûpela results trendê %s

      ", + "Showing_online_users": null, "Showing_results": "

      نیشاندانی %s ئەنجام

      ", "Sidebar": "Sidebar", "Sidebar_list_mode": "Mîhengên Channel Lîsteya Sidebar", @@ -2673,6 +2676,7 @@ "Users_added": "Bikarhênerên nû hatine zêdekirin", "Users_in_role": "Bikarhêner li rola", "UTF8_Names_Slugify": "نازناو بۆ ناوی UTF8", + "Videocall_enabled": "Call Call Enabled", "Validate_email_address": "Navnîşana Navnîşa Navnîşankirî", "Verification": "Tesdîq", "Verification_Description": "Hûn dikarin liverên jêrîn bikar bînin:
      • [Verification_Url] ji bo pejirandinê URL.
      • navê [[name], [fname], [lname] ji bo navê bikarhênerê, first name an navê paşîn, paşê.
      • [email] bo e-nameya bikarhêner.
      • [Site_Name] û [Site_URL] ji bo navê navekî û navnîşê ya serî.
      ", @@ -2687,7 +2691,6 @@ "Video_Conference": "Konferansa Video", "Video_message": "Peyamê Video", "Videocall_declined": "Call Call Declined.", - "Videocall_enabled": "Call Call Enabled", "View_All": "هەموو ببینە", "View_Logs": "View Têketin", "View_mode": "mode View", @@ -2809,4 +2812,4 @@ "Your_push_was_sent_to_s_devices": "push xwe ji bo cîhazên %s hate şandin", "Your_server_link": "Girêdana serverê", "Your_workspace_is_ready": "Karên te yên amadekar e amade ye" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/lo.i18n.json b/packages/rocketchat-i18n/i18n/lo.i18n.json index 93254d103888e..a12e2ad656845 100644 --- a/packages/rocketchat-i18n/i18n/lo.i18n.json +++ b/packages/rocketchat-i18n/i18n/lo.i18n.json @@ -572,6 +572,7 @@ "Continuous_sound_notifications_for_new_livechat_room": "ການແຈ້ງເຕືອນກ່ຽວກັບສຽງຕໍ່ເນື່ອງສໍາລັບຫ້ອງດໍາລົງຊີວິດໃຫມ່", "Conversation": "ການສົນທະນາ", "Conversation_closed": "ການສົນທະນາປິດ: __comment__.", + "Conversation_finished": "ການສົນທະນາໄດ້ສໍາເລັດ", "Conversation_finished_message": "ການສົນທະນາສິ້ນສຸດຂໍ້ຄວາມ", "conversation_with_s": "ການສົນທະນາທີ່ມີ %s", "Convert_Ascii_Emojis": "ແປງ ASCII ກັບ Emoji", @@ -994,6 +995,7 @@ "Editing_room": "ຫ້ອງການແກ້ໄຂ", "Editing_user": "ຜູ້ໃຊ້ການແກ້ໄຂ", "Education": "ການສຶກສາ", + "Email": "Email", "Email_address_to_send_offline_messages": "ທີ່ຢູ່ອີເມວການສົ່ງຂໍ້ຄວາມອອຟໄລ", "Email_already_exists": "Email ຢູ່ແລ້ວ", "Email_body": "ຮ່າງກາຍອີເມລ໌", @@ -2335,6 +2337,7 @@ "Show_Setup_Wizard": "ສະແດງຕົວຊ່ວຍສ້າງການຕັ້ງຄ່າ", "Show_the_keyboard_shortcut_list": "ສະແດງລາຍະການທາງລັດແປ້ນພິມ", "Showing_archived_results": "

      ສະແດງໃຫ້ເຫັນຜົນໄດ້ຮັບທີ່ເກັບ %s

      ", + "Showing_online_users": null, "Showing_results": "

      ສະແດງໃຫ້ເຫັນຜົນໄດ້ຮັບ %s

      ", "Sidebar": "Sidebar", "Sidebar_list_mode": "ໂຫມດບັນທັດຂອງແຖບ Sidebar", @@ -2714,6 +2717,7 @@ "Users_added": "ຜູ້ໃຊ້ໄດ້ຖືກເພີ່ມ", "Users_in_role": "ຜູ້ຊົມໃຊ້ໃນພາລະບົດບາດ", "UTF8_Names_Slugify": "UTF8 Names Slugify", + "Videocall_enabled": null, "Validate_email_address": "ຢືນຢັນທີ່ຢູ່ອີເມວ", "Verification": "ການຢັ້ງຢືນ", "Verification_Description": "ທ່ານອາດຈະນໍາໃຊ້ບ່ອນວາງສະຖານດັ່ງຕໍ່ໄປນີ້:
      • [Verification_Url] ສໍາລັບ URL ການຢືນຢັນ.
      • [ຊື່], [fname], [lname] ສໍາລັບຊື່ເຕັມ, ຊື່ຫຼືນາມສະກຸນຂອງຜູ້ໃຊ້.
      • [ອີເມວ] ສໍາລັບອີເມວຂອງຜູ້ໃຊ້.
      • [Site_Name] ແລະ [Site_URL] ສໍາລັບຊື່ແອັບຯແລະ URL ຕາມລໍາດັບ.
      ", @@ -2728,7 +2732,6 @@ "Video_Conference": "Video Conference", "Video_message": "ຂໍ້ຄວາມວິດີໂອ", "Videocall_declined": "ການໂທວິດີໂອໄດ້ຫຼຸດລົງ.", - "Videocall_enabled": "Video Call Enabled", "View_All": "ເບິ່ງ​ທັງ​ຫມົດ", "View_Logs": "ເບິ່ງຂໍ້ມູນບັນທຶກ", "View_mode": "ຮູບແບບການເບິ່ງ", @@ -2850,4 +2853,4 @@ "Your_push_was_sent_to_s_devices": "ການຊຸກຍູ້ຂອງທ່ານໄດ້ຖືກສົ່ງໄປອຸປະກອນ %s", "Your_server_link": "ເຊື່ອມຕໍ່ເຊີຟເວີຂອງທ່ານ", "Your_workspace_is_ready": "ພື້ນທີ່ເຮັດວຽກຂອງທ່ານແມ່ນພ້ອມທີ່ຈະໃຊ້🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/lt.i18n.json b/packages/rocketchat-i18n/i18n/lt.i18n.json index 707daa69125ba..b202e2a98e4c2 100644 --- a/packages/rocketchat-i18n/i18n/lt.i18n.json +++ b/packages/rocketchat-i18n/i18n/lt.i18n.json @@ -201,9 +201,12 @@ "Accounts_RequireNameForSignUp": "Reikalauti registracijos vardo", "Accounts_RequirePasswordConfirmation": "Reikalauti slaptažodžio patvirtinimo", "Accounts_SearchFields": "Laukai, kuriuos reikia apsvarstyti paieškoje", + "Accounts_Send_Email_When_Activating": "Siųsti naudotojui el. laišką, kai naudotojas aktyvuojamas", + "Accounts_Send_Email_When_Deactivating": "Siųsti naudotojui el. laišką, kai naudotojas deaktyvuojamas", "Accounts_SetDefaultAvatar": "Nustatyti numatytąjį įvaizdį", "Accounts_SetDefaultAvatar_Description": "Bando nustatyti numatytąją piktogramą, pagrįstą \"OAuth\" sąskaita arba \"Gravatar\"", "Accounts_ShowFormLogin": "Rodyti prisijungimą pagal formą", + "Accounts_TwoFactorAuthentication_By_Email_Enabled": "Įgalinti dviejų veiksnių autentiškumo patvirtinimą el. paštu", "Accounts_TwoFactorAuthentication_Enabled": "Įgalinti dviejų veiksnių autentifikavimą", "Accounts_TwoFactorAuthentication_MaxDelta": "Didžiausia deltė", "Accounts_TwoFactorAuthentication_MaxDelta_Description": "Maksimalus deltas nustato, kiek žetonų galioja bet kuriuo metu. Žetonai generuojami kas 30 sekundžių ir galioja (30 * Maksimalus deltos) sekundes.
      Pavyzdys: kai maksimali deltos vertė yra 10, kiekvienas raktas gali būti naudojamas iki 300 sekundžių iki arba po laiko žymos. Tai naudinga, kai kliento laikrodis netinkamai sinchronizuojamas su serveriu.", @@ -212,8 +215,11 @@ "Accounts_UserAddedEmail_Default": "

      Sveiki atvykę į [Site_Name]

      Eikite į [Site_URL] ir išbandykite geriausią atviro kodo pokalbių sprendimą šiandien!

      Prisijungti galite naudodami savo el. pašto adresą: [email] Ir slaptažodį: [password]. Po pirmojo prisijungimo gali tekti jį pakeisti.", "Accounts_UserAddedEmail_Description": "Galite atitinkamai nurodyti naudotojo vardą, pavardę ar vardą, naudodami šiuos užpildytojus:

      • [name], [fname], [lname].
      • [el. Paštas] naudotojo el. Laiškui.
      • [slaptažodis] vartotojo slaptažodžiui.
      • > [Site_Name] ir [Site_URL] atitinkamai programos pavadinimui ir URL.
      ", "Accounts_UserAddedEmailSubject_Default": "Jūs įtraukėte į [Site_Name]", + "Action": "Veiksmas", + "Action_required": "Reikalingas veiksmas", "Activate": "aktyvinti", "Active": "Aktyvus", + "Active_users": "Aktyvūs naudotojai", "Activity": "Veikla", "Add": "Pridurti", "Add_agent": "Pridėti agentą", @@ -225,6 +231,7 @@ "Add_user": "Pridėti naudotoją", "Add_User": "Pridėti naudotoją", "Add_users": "Pridėti vartotojus", + "Add_members": "Pridėti narius", "add-oauth-service": "Pridėti \"Oauth\" paslaugą", "add-oauth-service_description": "Leidimas pridėti naują \"Oauth\" paslaugą", "add-user": "Pridėti naudotoją", @@ -343,9 +350,12 @@ "Apps_Game_Center_Back": "Grįžti į žaidimų centrą", "Apps_Game_Center_Invite_Friends": "Pakvieskite draugus prisijungti", "Apps_Game_Center_Play_Game_Together": "@here Žaiskime __name__ kartu!", + "Apps_License_Message_renewal": "Licencijos galiojimo laikas baigėsi ir ją reikia atnaujinti", "Apps_Logs_TTL_7days": "7 dienos", "Apps_Logs_TTL_14days": "14 dienų", "Apps_Logs_TTL_30days": "30 dienų", + "Apps_Marketplace_Deactivate_App_Prompt": "Ar tikrai norite išjungti šią programą?", + "Apps_Marketplace_Login_Required_Title": "Reikalingas prisijungimas prie Marketplace", "Apps_Marketplace_pricingPlan_monthly": "__price__ per mėnesį", "Apps_Marketplace_pricingPlan_monthly_perUser": "__price__ per mėnesį vienam naudotojui", "Apps_Marketplace_pricingPlan_startingAt_monthly": "nuo __price__ per mėnesį", @@ -356,6 +366,7 @@ "Apps_Marketplace_pricingPlan_yearly_perUser": "__price__ per metus vienam naudotojui", "Apps_Marketplace_Uninstall_App_Prompt": "Ar tikrai norite pašalinti šią programą?", "Apps_Marketplace_Uninstall_Subscribed_App_Anyway": "Vis tiek pašalinti", + "Apps_Permissions_Review_Modal_Title": "Reikalingi leidimai", "Apps_Settings": "Programos nustatymai", "Apps_WhatIsIt": "\"Apps\": kokie jie?", "Apps_WhatIsIt_paragraph1": "Nauja piktograma administravimo srityje! Ką tai reiškia ir kas yra \"Apps\"?", @@ -479,7 +490,7 @@ "CAS_Sync_User_Data_Enabled": "Visada sinchronizuoti naudotojo duomenis", "CAS_Sync_User_Data_Enabled_Description": "Prisijungę, visada sinchronizuokite išorinius CAS vartotojo duomenis prie prieinamų atributų. Pastaba: bet kuriuo atveju atributai visada sinchronizuojami paskyros sukūrimo metu.", "CAS_Sync_User_Data_FieldMap": "Atributo žemėlapis", - "CAS_Sync_User_Data_FieldMap_Description": "Naudokite šį JSON įvestį, kad sukurtumėte vidinius atributus (raktą) iš išorinių atributų (vertė).
      Pavyzdžiui, `{\" el. \":\"% Email% \",\" name \":\"% firstname%,% lastname% \"}`

      Atributų žemėlapis visada interpoliuojamas. CAS 1.0 tik atributas `username` yra prieinamas. Galimi vidiniai atributai: vartotojo vardas, vardas, el. Paštas, kambariai; kambariai yra kableliais atskirtų kambarių sąrašas, skirtas prisijungti prie vartotojo sukūrimo, pvz .: (\"kambariai\": \"% team%,% department%\") prisijungs prie CAS vartotojų kūrimo savo komandos ir departamento kanale.", + "CAS_Sync_User_Data_FieldMap_Description": "Naudokite šį JSON įvestį, kad sukurtumėte vidinius atributus (raktą) iš išorinių atributų (vertė).
      Pavyzdžiui, `{\"email\":\"%email%\", \"name\":\"%firstname%, %lastname%\"}`

      Atributų žemėlapis visada interpoliuojamas. CAS 1.0 tik atributas `username` yra prieinamas. Galimi vidiniai atributai: vartotojo vardas, vardas, el. Paštas, kambariai; kambariai yra kableliais atskirtų kambarių sąrašas, skirtas prisijungti prie vartotojo sukūrimo, pvz .:{\"rooms\": \"%team%,%department%\"} prisijungs prie CAS vartotojų kūrimo savo komandos ir departamento kanale.", "CAS_version": "CAS versija", "CAS_version_Description": "Naudokite tik palaikomą CAS versiją, kurią palaiko jūsų CAS SSO paslauga.", "CDN_PREFIX": "CDN prefiksas", @@ -605,6 +616,7 @@ "Continuous_sound_notifications_for_new_livechat_room": "Nuolatiniai pranešimai apie garsą naujam \"Livechat\" kambariui", "Conversation": "Pokalbis", "Conversation_closed": "Pokalbis uždarytas: __comment__.", + "Conversation_finished": "Pokalbis baigtas", "Conversation_finished_message": "Pokalbis baigtas pranešimas", "conversation_with_s": "pokalbis su %s", "Convert_Ascii_Emojis": "Konvertuoti ASCII į Emoji", @@ -1617,7 +1629,7 @@ "LDAP_User_Search_Filter_Description": "Jei nurodyta, bus leista prisijungti tik šį filtrą atitinkantiems vartotojams. Jei filtro nenurodyta, visi naudotojai, kuriems taikoma konkretaus domeno bazė, galės prisijungti.
      Pvz. Active Directory \"memberOf = cn = ROCKET_CHAT, ou = Bendrosios grupės\".
      Pvz. OpenLDAP (extensible match search) \"ou: dn: = ROCKET_CHAT\".", "LDAP_User_Search_Scope": "Taikymo sritis", "LDAP_Username_Field": "Vartotojo vardas laukas", - "LDAP_Username_Field_Description": "Kuris laukas bus naudojamas kaip * vartotojo vardas * naujiems vartotojams. Palikite tuščią, kad naudotojo vardas būtų nurodytas prisijungimo puslapyje.
      Galite naudoti šablono žymes, pvz., \"# {GivenName}. # {Sn}\".
      Numatytoji reikšmė yra \"sAMAccountName\".", + "LDAP_Username_Field_Description": "Kuris laukas bus naudojamas kaip * vartotojo vardas * naujiems vartotojams. Palikite tuščią, kad naudotojo vardas būtų nurodytas prisijungimo puslapyje.
      Galite naudoti šablono žymes, pvz., \"#{givenName}. #{sn}\".
      Numatytoji reikšmė yra \"sAMAccountName\".", "Lead_capture_email_regex": "Švinas surenkite el. Pašto regex", "Lead_capture_phone_regex": "\"Lead\" užfiksuok telefoną regex", "Leave": "Palikite kambarį", @@ -2086,7 +2098,7 @@ "Push_show_username_room": "Rodyti kanalą / grupę / vartotojo vardą pranešime", "Push_test_push": "Testas", "Query": "Užklausa", - "Query_description": "Papildomos sąlygos nustatyti, kurie naudotojai turi siųsti el. Laišką. Neatsakytus naudotojus automatiškai pašalina iš užklausos. Tai turi galioti JSON. Pavyzdys: \"{\" createdAt \": {\" $ gt \": {\" $ date \":\" 2015-01-01T00: 00: 00.000Z \"}}}\"", + "Query_description": "Papildomos sąlygos nustatyti, kurie naudotojai turi siųsti el. laišką. Neatsakytus naudotojus automatiškai pašalina iš užklausos. Tai turi galioti JSON. Pavyzdys: \"{\"createdAt\":{\"$gt\":{\"$date\": \"2015-01-01T00:00:00.000Z\"}}}\"", "Queue": "Eilė", "quote": "citata", "Quote": "Citata", @@ -2732,6 +2744,7 @@ "Users_added": "Naudotojams buvo pridėta", "Users_in_role": "Vartotojai vaidmenyje", "UTF8_Names_Slugify": "UTF8 vardai Slugify", + "Videocall_enabled": "Vaizdo skambutis įjungtas", "Validate_email_address": "Patvirtinkite el. Pašto adresą", "Verification": "Patvirtinimas", "Verification_Description": "Galite naudoti šiuos užpildytojus:
      • > [Verification_Url], jei norite patvirtinimo URL.
      • [vardas], [fname], [lname] atitinkamai vartotojo vardas, pavardė arba vardas.
      • [el. Paštas] naudotojo el. Laiškui.
      • > [Site_Name] ir [Site_URL] atitinkamai programos pavadinimui ir URL.
      ", @@ -2746,7 +2759,6 @@ "Video_Conference": "Video konferencija", "Video_message": "Vaizdo pranešimas", "Videocall_declined": "Vaizdo skambutis atmesti.", - "Videocall_enabled": "Vaizdo skambutis įjungtas", "View_All": "Peržiūrėti visus narius", "View_Logs": "Žiūrėti žurnalus", "View_mode": "Peržiūrėti režimą", diff --git a/packages/rocketchat-i18n/i18n/lv.i18n.json b/packages/rocketchat-i18n/i18n/lv.i18n.json index 3e71499a0761d..f01b99b21e3e3 100644 --- a/packages/rocketchat-i18n/i18n/lv.i18n.json +++ b/packages/rocketchat-i18n/i18n/lv.i18n.json @@ -2159,6 +2159,7 @@ "Room_archivation_state_false": "Aktīvs", "Room_archivation_state_true": "Arhivēts", "Room_archived": "Istaba arhivēta", + "room_changed_privacy": null, "Room_default_change_to_private_will_be_default_no_more": "Šis ir noklusējuma kanāls, mainot to uz privātu grupu, tas vairs nebūs noklusējuma kanāls. Vai vēlaties turpināt?", "Room_description_changed_successfully": "Istabas apraksts ir veiksmīgi mainīts", "Room_has_been_archived": "Istaba ir arhivēta", @@ -2597,6 +2598,7 @@ "Use_url_for_avatar": "Izmantot URL kā avataru", "Use_User_Preferences_or_Global_Settings": "Izmantot lietotāja preferences vai globālos iestatījumus", "User": "Lietotājs", + "User__username__removed_from__room_name__leaders": null, "User_added": "Lietotājs pievienots", "User_added_successfully": "Lietotājs pievienots veiksmīgi", "User_and_group_mentions_only": "Lietotājs un grupa tikai pieminējumi", @@ -2663,6 +2665,7 @@ "Users_added": "Lietotāji ir pievienoti", "Users_in_role": "Lietotāji lomā", "UTF8_Names_Slugify": "UTF8 vārdi Slugify", + "Videocall_enabled": "Video zvans ir iespējots", "Validate_email_address": "Apstiprināt e-pasta adresi", "Verification": "Pārbaude", "Verification_Description": "Jūs varat izmantot šādus vietturus:
      • [Verification_Url] kā apstiprinājuma URL.
      • [vārds], [fname], [lname] lietotāja pilnam vārdam, attiecīgi vārds vai uzvārds.
      • [e-pasts] lietotāja e-pastam.
      • [Vietnes nosaukums] un [Site_URL] attiecīgi lietotnes nosaukums un URL.
      ", @@ -2677,7 +2680,6 @@ "Video_Conference": "Video konference", "Video_message": "Video ziņojums", "Videocall_declined": "Video zvans noraidīts.", - "Videocall_enabled": "Video zvans ir iespējots", "View_All": "Skatīt visus dalībniekus", "View_Logs": "Skatīt žurnālus", "View_mode": "Skatīt režīmu", @@ -2771,6 +2773,7 @@ "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Jūs varat izmantot webhoaks, lai viegli integrētu livechat ar savu CRM.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Jūs nevarat atstāt livechat istabu. Lūdzu, izmantojiet aizvēršanas pogu.", "You_have_been_muted": "Jums ir liegts rakstīt un nevar runāt šajā istabā", + "You_have_n_codes_remaining": null, "You_have_not_verified_your_email": "Jūs neesat apstiprinājis savu e-pastu.", "You_have_successfully_unsubscribed": "Jūs esat veiksmīgi anulējis abonomentu no mūsu Sūtšanas saraksta.", "You_have_to_set_an_API_token_first_in_order_to_use_the_integration": "Vispirms ir jāiestata API žetons, lai izmantotu integrāciju.", @@ -2797,4 +2800,4 @@ "Your_push_was_sent_to_s_devices": "Jūsu push tika nosūtīts uz %s ierīcēm", "Your_server_link": "Jūsu servera saite", "Your_workspace_is_ready": "Jūsu darbastacija ir gatava lietošanai 🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/mn.i18n.json b/packages/rocketchat-i18n/i18n/mn.i18n.json index cb7c6fe548690..c53483bee2373 100644 --- a/packages/rocketchat-i18n/i18n/mn.i18n.json +++ b/packages/rocketchat-i18n/i18n/mn.i18n.json @@ -553,6 +553,7 @@ "Continuous_sound_notifications_for_new_livechat_room": "Шинэ livechat өрөөний тасралтгүй дууны мэдэгдэл", "Conversation": "Харилцаа холбоо", "Conversation_closed": "Хэлэлцүүлэг хаалттай: __comment__.", + "Conversation_finished": "Харилцаа дууссан", "Conversation_finished_message": "Хэлэлцүүлэг Дууссан зурвас", "conversation_with_s": "%s-тэй харилцсан", "Convert_Ascii_Emojis": "ASCII-г Emoji руу хөрвүүлэх", @@ -2666,6 +2667,7 @@ "Users_added": "Хэрэглэгчид нэмэгдсэн байна", "Users_in_role": "Үүрэг дэх хэрэглэгчид", "UTF8_Names_Slugify": "UTF8 нэрс Slugify", + "Videocall_enabled": "Видео дуудлага идэвхжсэн", "Validate_email_address": "Баталгаажуулсан имэйл хаяг", "Verification": "Баталгаажуулалт", "Verification_Description": "Та дараах байршлыг ашиглаж болно:
      • [Verification_Url] баталгаажуулах URL-д зориулж болно.
      • [нэр], [fname], [lname] хэрэглэгчийн бүтэн нэр, эхний нэр эсвэл овог нэр.
      • [имэйл] хэрэглэгчийн имэйлийн хувьд.
      • [Site_Name] болон [Site_URL] нь Програмын Нэр болон URL-ыг тус тусад нь зааж өгсөн.
      ", @@ -2680,7 +2682,6 @@ "Video_Conference": "Видео хурал", "Video_message": "Видео мэдээ", "Videocall_declined": "Видео дуудлага татгалзсан.", - "Videocall_enabled": "Видео дуудлага идэвхжсэн", "View_All": "Бүх гишүүдийг харах", "View_Logs": "Бүртгэлийг харах", "View_mode": "Горимыг харах", @@ -2775,6 +2776,7 @@ "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Та вэбсайтаа CRech-тай livechat-тай амархан холбох боломжтой.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Та livechat өрөө орхиж болохгүй. Ойрхон товчлуурыг ашиглана уу.", "You_have_been_muted": "Та чимээгүй болж, энэ өрөөнд ярьж чадахгүй байна", + "You_have_n_codes_remaining": null, "You_have_not_verified_your_email": "Та өөрийн имэйлийг баталгаажуулаагүй байна.", "You_have_successfully_unsubscribed": "Та манай Майдансын жагсаалтаас амжилттай цуцалсан.", "You_have_to_set_an_API_token_first_in_order_to_use_the_integration": "Та интеграцыг ашиглахын тулд эхлээд API жетоныг тохируулах хэрэгтэй.", @@ -2801,4 +2803,4 @@ "Your_push_was_sent_to_s_devices": "Таны түлхэлт %s төхөөрөмж рүү илгээгдсэн", "Your_server_link": "Таны серверийн холбоос", "Your_workspace_is_ready": "Таны ажлын талбарыг ашиглахад бэлэн байна" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/ms-MY.i18n.json b/packages/rocketchat-i18n/i18n/ms-MY.i18n.json index 23b73b185a1e4..746bd620fb56c 100644 --- a/packages/rocketchat-i18n/i18n/ms-MY.i18n.json +++ b/packages/rocketchat-i18n/i18n/ms-MY.i18n.json @@ -1,6 +1,7 @@ { "403": "Larangan", "500": "Ralat Pelayan Dalaman", + "__username__was_set__role__by__user_by_": null, "@username": "@pengguna", "@username_message": "@username ", "#channel": "#channel", @@ -553,6 +554,7 @@ "Continuous_sound_notifications_for_new_livechat_room": "Pemberitahuan bunyi yang berterusan untuk bilik livechat baru", "Conversation": "Perbualan", "Conversation_closed": "Perbualan ditutup: __comment__.", + "Conversation_finished": "perbualan selesai", "Conversation_finished_message": "Mesej Selesai Perbualan", "conversation_with_s": "perbualan dengan %s", "Convert_Ascii_Emojis": "Menukar ASCII ke Emoji", @@ -1020,6 +1022,7 @@ "error-application-not-found": "Permohonan tidak dijumpai", "error-archived-duplicate-name": "Ada satu saluran yang diarkibkan dengan nama '__room_name__'", "error-avatar-invalid-url": "avatar URL tidak sah: __url__", + "error-avatar-url-handling": null, "error-cant-invite-for-direct-room": "tidak boleh menjemput pengguna ke bilik terus", "error-channels-setdefault-is-same": "Tetapan lalai saluran adalah sama dengan apa yang akan ditukar kepada.", "error-channels-setdefault-missing-default-param": "Diperlukan 'default' bodyParam", @@ -1681,6 +1684,7 @@ "Max_length_is": "Panjang maksimum ialah%s", "Media": "Media", "Medium": "Sederhana", + "Members": "Ahli", "Members_List": "Senarai Ahli", "mention-all": "Sebut semua", "mention-all_description": "Kebenaran untuk menggunakan sebutan @all", @@ -2305,6 +2309,7 @@ "Show_Setup_Wizard": "Tunjukkan Penyihir Persediaan", "Show_the_keyboard_shortcut_list": "Tunjukkan senarai pintasan papan kekunci", "Showing_archived_results": "

      Menunjukkan hasil yang diarkibkan %s

      ", + "Showing_online_users": null, "Showing_results": "

      Menunjukan %s keputusan

      ", "Sidebar": "Sidebar", "Sidebar_list_mode": "Mod Senarai Saluran Sidebar", @@ -2496,6 +2501,8 @@ "This_email_has_already_been_used_and_has_not_been_verified__Please_change_your_password": "E-mel ini telah digunakan dan tidak disahkan. Sila tukar kata laluan anda.", "This_is_a_desktop_notification": "Ini adalah pemberitahuan desktop", "This_is_a_push_test_messsage": "Ini adalah mesej supaya ujian push", + "This_room_has_been_archived_by__username_": "Bilik ini telah diarkibkan oleh __username__", + "This_room_has_been_unarchived_by__username_": "Bilik ini telah dinyaharkibkan oleh __username__", "Thursday": "Khamis", "Time_in_seconds": "Masa dalam saat", "Title": "Title", @@ -2599,7 +2606,10 @@ "Use_User_Preferences_or_Global_Settings": "Gunakan Keutamaan Pengguna atau Tetapan Global", "User": "Pengguna", "User__username__is_now_a_leader_of__room_name_": "Pengguna __username__ kini menjadi pemimpin __room_name__", + "User__username__is_now_a_moderator_of__room_name_": "__username__ pengguna kini merupakan moderator __room_name__", + "User__username__is_now_a_owner_of__room_name_": "__username__ pengguna kini merupakan pemilik __room_name__", "User__username__removed_from__room_name__leaders": "Pengguna __username__ dikeluarkan daripada pemimpin __room_name__", + "User__username__removed_from__room_name__moderators": "__username__ Pengguna dikeluarkan dari moderator __room_name__", "User_added": "Pengguna ditambah.", "User_added_by": "Pengguna __user_added__ ditambah oleh __user_by__.", "User_added_successfully": "Pengguna berjaya diletakkan", @@ -2666,6 +2676,7 @@ "Username_Change_Disabled": "pentadbir Rocket.Chat anda telah melumpuhkan perubahan nama pengguna", "Username_description": "Nama Pengguna adalah digunakan untuk membolehkan pengguna lain menyebut anda di mesej.", "Username_doesnt_exist": "Nama pengguna ' %s` tidak wujud.", + "Username_ended_the_OTR_session": "__username__ mengakhiri sesi OTR yang", "Username_invalid": "%s bukan nama pengguna yang sah,
      guna hanya huruf, nombor, titik dan pemisah", "Username_is_already_in_here": "`@%s` sudah berada di sini.", "Username_is_not_in_this_room": "Pengguna `#%s` tidak ada di dalam bilik ini.", @@ -2675,6 +2686,7 @@ "Users_added": "Pengguna telah ditambah", "Users_in_role": "Pengguna dalam peranan", "UTF8_Names_Slugify": "UTF8 Nama Slugify", + "Videocall_enabled": "Panggilan Video Dihidupkan", "Validate_email_address": "Mengesahkan Alamat E-mel", "Verification": "Pengesahan", "Verification_Description": "Anda boleh menggunakan pemegang tempat berikut:
      • [Verification_Url] untuk URL pengesahan.
      • [nama], [fname], [lname] untuk nama penuh pengguna, nama pertama atau nama belakangnya masing-masing.
      • [email] untuk e-mel pengguna.
      • [Site_Name] dan [Site_URL] untuk Nama Aplikasi dan URL masing-masing.
      ", @@ -2689,7 +2701,6 @@ "Video_Conference": "Persidangan Video", "Video_message": "Mesej video", "Videocall_declined": "Panggilan Video Ditolak.", - "Videocall_enabled": "Panggilan Video Dihidupkan", "View_All": "Lihat Semua", "View_Logs": "Lihat Balak", "View_mode": "mod lihat", @@ -2811,4 +2822,4 @@ "Your_push_was_sent_to_s_devices": "push anda telah dihantar ke peranti %s", "Your_server_link": "Pautan pelayan anda", "Your_workspace_is_ready": "Ruang kerja anda sedia untuk menggunakan 🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/nl.i18n.json b/packages/rocketchat-i18n/i18n/nl.i18n.json index 695149dcabc74..19acdbcb70c60 100644 --- a/packages/rocketchat-i18n/i18n/nl.i18n.json +++ b/packages/rocketchat-i18n/i18n/nl.i18n.json @@ -4,7 +4,7 @@ "__count__empty_rooms_will_be_removed_automatically": "__count__ lege kamers worden automatisch verwijderd.", "__count__empty_rooms_will_be_removed_automatically__rooms__": "__count__ lege kamers worden automatisch verwijderd:
      __rooms__", "__username__is_no_longer__role__defined_by__user_by_": "__username__ is niet langer __role__ door __user_by__", - "__username__was_set__role__by__user_by_": "__username__ is __role__ ingesteld door __user_by__", + "__username__was_set__role__by__user_by_": "__username__ werd ingesteld op __role__ door __user_by__", "This_room_encryption_has_been_enabled_by__username_": "De versleuteling van deze kamer werd ingeschakeld door __username__", "This_room_encryption_has_been_disabled_by__username_": "De versleuteling van deze kamer werd uitgeschakeld door __username__", "@username": "@gebruikersnaam", @@ -25,8 +25,8 @@ "Accept_with_no_online_agents": "Accepteer zonder online agenten", "Access_not_authorized": "Toegang niet toegestaan", "Access_Token_URL": "Toegangstoken URL", - "access-mailer": "Open het Mailer-scherm", - "access-mailer_description": "Toestemming voor het verzenden van massa-e-mail naar alle gebruikers.", + "access-mailer": "Toegang tot het Mailer-scherm", + "access-mailer_description": "Toestemming om massa-e-mail naar alle gebruikers te verzenden.", "access-permissions": "Open het toegangsrechten scherm", "access-permissions_description": "Wijzig de rechten voor verschillende rollen.", "access-setting-permissions": "Wijzig machtigingen op basis van instellingen", @@ -114,6 +114,8 @@ "Accounts_OAuth_Custom_Merge_Users": "Gebruikers samenvoegen", "Accounts_OAuth_Custom_Name_Field": "Naam veld", "Accounts_OAuth_Custom_Roles_Claim": "Rol / Groepen veldnaam", + "Accounts_OAuth_Custom_Roles_To_Sync": "Rollen die moeten worden gesynchroniseerd", + "Accounts_OAuth_Custom_Roles_To_Sync_Description": "Te synchroniseren OAuth rollen bij het inloggen en aanmaken van gebruikers (gescheiden door komma's).", "Accounts_OAuth_Custom_Scope": "Reikwijdte", "Accounts_OAuth_Custom_Secret": "Geheim", "Accounts_OAuth_Custom_Show_Button_On_Login_Page": "Knop weergeven op inlogpagina", @@ -370,6 +372,8 @@ "API_Enable_Rate_Limiter_Dev": "Schakel Rate Limiter in ontwikkeling in", "API_Enable_Rate_Limiter_Dev_Description": "Moet het aantal oproepen naar de eindpunten in de ontwikkelomgeving worden beperkt?", "API_Enable_Rate_Limiter_Limit_Calls_Default": "Standaardnummeroproepen naar de snelheidsbegrenzer", + "Rate_Limiter_Limit_RegisterUser": "Standaard aantal oproepen naar de snelheidsbegrenzer (rate limiter) voor het registreren van een gebruiker", + "Rate_Limiter_Limit_RegisterUser_Description": "Aantaal standaardoproepen voor gebruikersregistratie-eindpunten (REST en realtime API's), toegestaan binnen het tijdbereik dat is gedefinieerd in de sectie API Rate Limiter.", "API_Enable_Rate_Limiter_Limit_Calls_Default_Description": "Aantal standaardoproepen voor elk eindpunt van de REST API, toegestaan binnen het hieronder gedefinieerde tijdsbereik", "API_Enable_Rate_Limiter_Limit_Time_Default": "Standaard tijdslimiet voor de snelheidsbegrenzer (in ms)", "API_Enable_Rate_Limiter_Limit_Time_Default_Description": "Standaard time-out om het aantal oproepen op elk eindpunt van de REST API te beperken (in ms)", @@ -385,13 +389,14 @@ "API_Personal_Access_Tokens_Regenerate_Modal": "Als je de token kwijt bent of vergeet, dan kan je deze opnieuw genereren, maar vergeet niet dat alle applicaties die deze token gebruiken, moeten worden bijgewerkt", "API_Personal_Access_Tokens_Remove_Modal": "Weet u zeker dat u dit persoonlijke toegangstoken wilt verwijderen?", "API_Personal_Access_Tokens_To_REST_API": "Persoonlijke toegangstoken tot REST API", + "API_Rate_Limiter": "API Rate Limiter", "API_Shield_Types": "Schildtypen", "API_Shield_Types_Description": "Typen schilden die kunnen worden ingeschakeld als een door komma's gescheiden lijst, kies `online`, `kanaal` of `*` voor iedereen", "API_Shield_user_require_auth": "Verificatie vereisen voor gebruikersschilden", "API_Token": "API Token", "API_Tokenpass_URL": "Tokenpass-server-URL", "API_Tokenpass_URL_Description": "Voorbeeld: https://domain.com (exclusief trailing slash)", - "API_Upper_Count_Limit": "Max. Recordbedrag", + "API_Upper_Count_Limit": "Max. recordbedrag", "API_Upper_Count_Limit_Description": "Wat is het maximale aantal records dat de REST-API moet teruggeven (wanneer niet onbeperkt)?", "API_Use_REST_For_DDP_Calls": "Gebruik REST in plaats van websocket voor Meteor-oproepen", "API_User_Limit": "Gebruikerslimiet voor het toevoegen van alle gebruikers aan een kaneel", @@ -401,21 +406,21 @@ "Apiai_Key": "Api.ai Key", "Apiai_Language": "Api.ai Taal", "APIs": "API's", - "App_author_homepage": "startpagina van de auteur", + "App_author_homepage": "auteur homepage", "App_Details": "Applicatie details", "App_Information": "App-informatie", "App_Installation": "App-installatie", "App_status_auto_enabled": "Ingeschakeld", "App_status_constructed": "Gebouwd", "App_status_disabled": "Uitgeschakeld", - "App_status_error_disabled": "Uitgeschakeld: niet-afgevangen fout", + "App_status_error_disabled": "Uitgeschakeld: niet opgevangen fout", "App_status_initialized": "Geïnitialiseerd", "App_status_invalid_license_disabled": "Uitgeschakeld: ongeldige licentie", "App_status_invalid_settings_disabled": "Uitgeschakeld: configuratie vereist", "App_status_manually_disabled": "Uitgeschakeld: handmatig", "App_status_manually_enabled": "Ingeschakeld", "App_status_unknown": "Onbekend", - "App_support_url": "ondersteuning voor URL's", + "App_support_url": "ondersteunings-URL", "App_Url_to_Install_From": "Installeer vanaf URL", "App_Url_to_Install_From_File": "Installeer vanuit bestand", "App_user_not_allowed_to_login": "App-gebruikers mogen niet rechtstreeks inloggen.", @@ -425,7 +430,7 @@ "Application_Name": "Naam van de toepassing", "Application_updated": "Applicatie bijgewerkt", "Apply": "Toepassen", - "Apply_and_refresh_all_clients": "Toepassen en alle cliënten opnieuw laden", + "Apply_and_refresh_all_clients": "Toepassen en alle klanten opnieuw laden", "Apps": "Apps", "Apps_Engine_Version": "Apps Engine-versie", "Apps_Essential_Alert": "Deze app is essentieel voor de volgende evenementen:", @@ -550,10 +555,10 @@ "assign-roles": "Rollen toewijzen", "assign-roles_description": "Toestemming om rollen toe te wijzen aan andere gebruikers", "at": "op", - "At_least_one_added_token_is_required_by_the_user": "Ten minste één toegevoegd token is vereist door de gebruiker", + "At_least_one_added_token_is_required_by_the_user": "De gebruiker heeft ten minste één toegevoegde token nodig", "AtlassianCrowd": "Atlassian Crowd", "Attachment_File_Uploaded": "Bestand geüpload", - "Attribute_handling": "Behandeling van attributen", + "Attribute_handling": "Attribuut behandeling", "Audio": "Audio", "Audio_message": "Audiobericht", "Audio_Notification_Value_Description": "Kan elk aangepast geluid zijn of de standaardgeluiden: piep, chelle, ding, droplet, highbell, seizoenen", @@ -701,6 +706,9 @@ "By_author": "Door __author__", "cache_cleared": "Cache gewist", "Call": "Bel", + "Call_declined": "Oproep geweigerd!", + "Call_provider": "Oproepprovider", + "Call_Already_Ended": "Oproep al beëindigd", "call-management": "Oproepbeheer", "call-management_description": "Toestemming om een vergadering te starten", "Caller": "Beller", @@ -734,10 +742,10 @@ "CAS_login_url_Description": "De inlog-URL van uw externe SSO-service, bijvoorbeeld: https://sso.example.undef/sso/login", "CAS_popup_height": "Hoogte aanmeldingspop-up", "CAS_popup_width": "Breedte aanmeldingspop-up", - "CAS_Sync_User_Data_Enabled": "Synchroniseer altijd gebruikersgegevens", + "CAS_Sync_User_Data_Enabled": "Altijd gebruikersgegevens synchroniseren", "CAS_Sync_User_Data_Enabled_Description": "Synchroniseer altijd externe CAS-gebruikersgegevens naar beschikbare attributen bij het inloggen. Opmerking: Kenmerken worden sowieso altijd gesynchroniseerd bij het maken van een account.", "CAS_Sync_User_Data_FieldMap": "Kenmerkkaart", - "CAS_Sync_User_Data_FieldMap_Description": "Gebruik deze JSON-invoer om interne attributen (sleutel) te bouwen op basis van externe attributen (waarde). Externe attribuutnamen ingesloten met '%' worden geïnterpoleerd in waardestrings.
      Voorbeeld, `{\" email \":\"% email% \",\" name \":\"% firstname%,% lastname% \"}`

      De kenmerkkaart wordt altijd geïnterpoleerd. In CAS 1.0 is alleen het kenmerk `gebruikersnaam 'beschikbaar. Beschikbare interne kenmerken zijn: gebruikersnaam, naam, e-mail, kamers; rooms is een door komma's gescheiden lijst van kamers om deel te nemen na het maken van de gebruiker, bijvoorbeeld: {\"rooms\": \"% team%,% department%\"} zou CAS-gebruikers bij de creatie vergezellen naar hun team- en afdelingskanaal.", + "CAS_Sync_User_Data_FieldMap_Description": "Gebruik deze JSON-invoer om interne attributen (sleutel) te bouwen op basis van externe attributen (waarde). Externe attribuutnamen ingesloten met '%' worden geïnterpoleerd in waardestrings.
      Voorbeeld, `{\"email\":\"%email%\", \"name\":\"%firstname%, %lastname%\"}`

      De kenmerkkaart wordt altijd geïnterpoleerd. In CAS 1.0 is alleen het `username` attribuut beschikbaar. Beschikbare interne attributen zijn: username, name, email, rooms; rooms is een door komma's gescheiden lijst van kamers om lid van te worden bij het aanmaken van een gebruiker, bijvoorbeeld: {\"rooms\": \"%team%,%department%\"} zou CAS-gebruikers bij het aanmaken toevoegen aan hun team- en afdelingskanaal.", "CAS_trust_username": "Vertrouw op CAS-gebruikersnaam", "CAS_trust_username_description": "Indien ingeschakeld, vertrouwt Rocket.Chat erop dat elke gebruikersnaam van CAS toebehoort aan dezelfde gebruiker op Rocket.Chat.
      Dit kan nodig zijn als de naam van een gebruiker wordt gewijzigd op CAS, maar het kan mensen ook toestaan de controle over Rocket.Chat-accounts te nemen door hun eigen CAS-gebruikers te hernoemen.", "CAS_version": "CAS-versie", @@ -760,8 +768,8 @@ "Channel_created": "Kanaal `#%s` aangemaakt.", "Channel_doesnt_exist": "Het kanaal `#%s` bestaat niet.", "Channel_Export": "Kanaal exporteren", - "Channel_name": "Kanaal naam", - "Channel_Name_Placeholder": "Voer de kanaalnaam in...", + "Channel_name": "Kanaalnaam", + "Channel_Name_Placeholder": "Voer kanaalnaam in...", "Channel_to_listen_on": "Kanaal om op te luisteren", "Channel_Unarchived": "Kanaal met de naam `#%s` is succesvol uit het archief gehaald", "Channels": "Kanalen", @@ -802,7 +810,7 @@ "Chatpal_Batch_Size_Description": "De batchgrootte van indexdocumenten (bij bootstrapping)", "Chatpal_channel_not_joined_yet": "Kanaal is nog geen lid", "Chatpal_create_key": "Sleutel aanmaken", - "Chatpal_created_key_successfully": "API-sleutel is succesvol aangemaakt", + "Chatpal_created_key_successfully": "API-sleutel succesvol aangemaakt", "Chatpal_Current_Room_Only": "Zelfde kamer", "Chatpal_Default_Result_Type": "Standaard resultaattype", "Chatpal_Default_Result_Type_Description": "Bepaalt welk resultaattype wordt weergegeven per resultaat. Alles betekent dat een overzicht voor alle typen wordt gegeven.", @@ -815,7 +823,7 @@ "Chatpal_Get_more_information_about_chatpal_on_our_website": "Meer informatie over Chatpal op http://chatpal.io!", "Chatpal_go_to_message": "Springen", "Chatpal_go_to_room": "Springen", - "Chatpal_go_to_user": "Direct bericht sturen", + "Chatpal_go_to_user": "Direct bericht verzenden", "Chatpal_HTTP_Headers": "HTTP-headers", "Chatpal_HTTP_Headers_Description": "Lijst met HTTP-headers, één header per regel. Formaat: naam: waarde", "Chatpal_Include_All_Public_Channels": "Inclusief alle openbare kanalen", @@ -851,7 +859,7 @@ "Choose_the_username_that_this_integration_will_post_as": "Kies de gebruikersnaam die deze integratie zal posten.", "Choose_users": "Kies gebruikers", "Clean_Usernames": "Gebruikersnamen wissen", - "clean-channel-history": "Maak kanaalgeschiedenis schoon", + "clean-channel-history": "Kanaalgeschiedenis wissen", "clean-channel-history_description": "Toestemming om de geschiedenis van kanalen te wissen", "clear": "Wissen", "Clear_all_unreads_question": "Alle ongelezen berichten wissen?", @@ -1340,6 +1348,7 @@ "Days": "Dagen", "DB_Migration": "Database Migratie", "DB_Migration_Date": "Database Migratie Datum", + "DDP_Rate_Limit": "DDP Rate Limit", "DDP_Rate_Limit_Connection_By_Method_Enabled": "Beperking per verbinding per methode: ingeschakeld", "DDP_Rate_Limit_Connection_By_Method_Interval_Time": "Beperking door verbinding per methode: intervaltijd", "DDP_Rate_Limit_Connection_By_Method_Requests_Allowed": "Beperking door verbinding per methode: verzoeken toegestaan", @@ -1403,7 +1412,7 @@ "Desktop_Notifications_Enabled": "Desktopmeldingen zijn ingeschakeld", "Desktop_Notifications_Not_Enabled": "Bureaubladmeldingen zijn niet ingeschakeld", "Details": "Details", - "Different_Style_For_User_Mentions": "Verschillende stijl voor gebruikersvermeldingen", + "Different_Style_For_User_Mentions": "Andere stijl voor gebruikersvermeldingen", "Direct_Message": "Privébericht", "Direct_message_creation_description": "U staat op het punt een chat te starten met meerdere gebruikers. Voeg degene toe met wie u wilt praten, iedereen op dezelfde plaats, via directe berichten.", "Direct_message_someone": "Stuur iemand een privébericht", @@ -1427,7 +1436,7 @@ "Direct_Reply_Separator": "Scheidingsteken", "Direct_Reply_Separator_Description": "[Alleen wijzigen als u precies weet wat u aan het doen bent, raadpleeg de documentatie]
      Separator tussen basis- en taggedeelte van e-mail", "Direct_Reply_Username": "Gebruikersnaam", - "Direct_Reply_Username_Description": "Gebruik alstublieft absolute e-mail, tagging is niet toegestaan, het zou overschreven worden", + "Direct_Reply_Username_Description": "Gebruik absolute e-mail, tagging is niet toegestaan, het zou worden overschreven", "Directory": "Directory", "Disable": "Uitschakelen", "Disable_Facebook_integration": "Schakel Facebook-integratie uit", @@ -1609,6 +1618,8 @@ "Encryption_key_saved_successfully": "Uw coderingssleutel is succesvol opgeslagen.", "EncryptionKey_Change_Disabled": "U kunt geen wachtwoord instellen voor uw coderingssleutel omdat uw privésleutel niet aanwezig is op de client. Om een nieuw wachtwoord in te stellen, moet u uw privésleutel laden met uw bestaande wachtwoord of een client gebruiken waarop de sleutel al is geladen.", "End": "Einde", + "End_call": "Oproep beëindigen", + "Expand_view": "Weergave uitvouwen", "End_OTR": "Stop OTR", "Engagement_Dashboard": "Betrokkenheidsdashboard", "Enter": "Enter", @@ -1836,7 +1847,9 @@ "Favorite": "Favoriete", "Favorite_Rooms": "Schakel favoriete kamers in", "Favorites": "Favorieten", + "Feature_depends_on_selected_call_provider_to_be_enabled_from_administration_settings": "Deze functie is afhankelijk van de hierboven geselecteerde oproepprovider die moet worden ingeschakeld via de beheerinstellingen.", "Feature_Depends_on_Livechat_Visitor_navigation_as_a_message_to_be_enabled": "Deze functie is afhankelijk van het feit of 'Navigatiegeschiedenis van bezoeker als bericht verzenden' is ingeschakeld.", + "Feature_Limiting": "Functiebeperking", "Features": "Functies", "Features_Enabled": "Ingeschakelde functies", "Feature_Disabled": "Functie uitgeschakeld", @@ -2012,7 +2025,7 @@ "force-delete-message_description": "Toestemming om een bericht te verwijderen waarbij alle beperkingen worden omzeild", "Forgot_password": "Wachtwoord vergeten?", "Forgot_Password_Description": "U kunt de volgende placeholders gebruiken:
      • [Forgot_Password_Url] voor de URL voor wachtwoordherstel.
      • [naam], [fname], [lname] voor de volledige naam, voornaam of achternaam van de gebruiker, respectievelijk.
      • [email] voor het e-mailadres van de gebruiker.
      • [Site_Name] en [Site_URL] voor respectievelijk de applicatienaam en URL.
      ", - "Forgot_Password_Email": "Klik hierom uw wachtwoord opnieuw in te stellen.", + "Forgot_Password_Email": "Klik hier om uw wachtwoord opnieuw in te stellen.", "Forgot_Password_Email_Subject": "[Site_Name] - Wachtwoordherstel", "Forgot_password_section": "Wachtwoord vergeten", "Forward": "Doorsturen", @@ -2101,7 +2114,7 @@ "Hours": "Uren", "How_friendly_was_the_chat_agent": "Hoe vriendelijk was de chatagent?", "How_knowledgeable_was_the_chat_agent": "Hoe deskundig was de chatagent?", - "How_long_to_wait_after_agent_goes_offline": "Hoelang gewacht moet worden nadat agent offline is gegaan", + "How_long_to_wait_after_agent_goes_offline": "Hoe lang te wachten nadat de agent offline gaat", "How_long_to_wait_to_consider_visitor_abandonment": "Hoe lang moet je wachten voordat je overweegt dat de bezoeker het heeft verlaten?", "How_long_to_wait_to_consider_visitor_abandonment_in_seconds": "Hoe lang moet je wachten voordat je overweegt dat de bezoeker het heeft verlaten?", "How_responsive_was_the_chat_agent": "Hoe responsief was de chatagent?", @@ -2149,16 +2162,16 @@ "Importer_finishing": "Afwerking van de import.", "Importer_From_Description": "Impoort __from__-gegevens in Rocket.Chat.", "Importer_HipChatEnterprise_BetaWarning": "Houd er rekening mee dat deze import nog steeds in uitvoering is, meld eventuele fouten die optreden op GitHub:", - "Importer_HipChatEnterprise_Information": "Het geüploade bestand moet een ontsleutelde tar.gz zijn, lees de documentatie voor meer informatie:", + "Importer_HipChatEnterprise_Information": "Het geüploade bestand moet een gedecodeerde tar.gz zijn, lees de documentatie voor meer informatie:", "Importer_import_cancelled": "Import geannuleerd.", "Importer_import_failed": "Er is een fout opgetreden tijdens het importeren.", - "Importer_importing_channels": "De kanalen importeren.", + "Importer_importing_channels": "Kanalen aan het importeren.", "Importer_importing_files": "Importeren van de bestanden.", "Importer_importing_messages": "De berichten importeren.", - "Importer_importing_started": "Starten van de import.", + "Importer_importing_started": "Het importeren starten.", "Importer_importing_users": "De gebruikers importeren.", "Importer_not_in_progress": "De importeur is momenteel niet actief.", - "Importer_not_setup": "De importeur is niet correct ingesteld, omdat er geen gegevens zijn geretourneerd.", + "Importer_not_setup": "De importeur is niet correct ingesteld, omdat hij geen gegevens heeft geretourneerd.", "Importer_Prepare_Restart_Import": "Start de import opnieuw", "Importer_Prepare_Start_Import": "Begin met importeren", "Importer_Prepare_Uncheck_Archived_Channels": "Haal het vinkje weg van gearchiveerde kanalen", @@ -2205,7 +2218,7 @@ "Install": "Installeren", "Install_Extension": "Installeer extensie", "Install_FxOs": "Installeer Rocket.Chat op uw Firefox", - "Install_FxOs_done": "Super! Je kunt Rocket.Chat nu gebruiken via het icoontje op je homescherm. Veel plezier met Rocket.Chat!", + "Install_FxOs_done": "Super! Je kunt nu Rocket.Chat gebruiken via het icoontje op je homescherm. Veel plezier met Rocket.Chat!", "Install_FxOs_error": "Sorry, dat werkte niet zoals de bedoeling! De volgende fout is opgetreden:", "Install_FxOs_follow_instructions": "Bevestig de installatie van de app op uw apparaat (druk op \"Installeren\" wanneer daar om gevraagd wordt).", "Install_package": "Installeer pakket", @@ -2215,7 +2228,7 @@ "Instance": "Instantie", "Instances": "Instanties", "Instances_health": "Gezondheid van instanties", - "Instance_Record": "Record instantie", + "Instance_Record": "Instantierecord", "Instructions": "Instructies", "Instructions_to_your_visitor_fill_the_form_to_send_a_message": "Instructies voor uw bezoeker vul het formulier in om een bericht te verzenden", "Insert_Contact_Name": "Voer de naam van het contact in", @@ -2237,12 +2250,12 @@ "Integration_Outgoing_WebHook_History_Http_Response_Error": "HTTP-antwoordfout", "Integration_Outgoing_WebHook_History_Messages_Sent_From_Prepare_Script": "Berichten verzonden vanuit de voorbereidingsstap", "Integration_Outgoing_WebHook_History_Messages_Sent_From_Process_Script": "Berichten verzonden vanuit procesantwoordstap", - "Integration_Outgoing_WebHook_History_Time_Ended_Or_Error": "Tijd dat het eindigde of een fout maakte", + "Integration_Outgoing_WebHook_History_Time_Ended_Or_Error": "Tijd dat het eindigde of fout ging", "Integration_Outgoing_WebHook_History_Time_Triggered": "Tijdintegratie geactiveerd", "Integration_Outgoing_WebHook_History_Trigger_Step": "Laatste activeringsstap", "Integration_Outgoing_WebHook_No_History": "Deze uitgaande webhook-integratie heeft nog geen geschiedenis geregistreerd.", "Integration_Retry_Count": "Aantal pogingen", - "Integration_Retry_Count_Description": "Hoe vaak moet de integratie worden geprobeerd als de aanroep naar de url mislukt?", + "Integration_Retry_Count_Description": "Hoe vaak moet de integratie worden geprobeerd als de oproep naar de url mislukt?", "Integration_Retry_Delay": "Wachttijd nieuwe poging", "Integration_Retry_Delay_Description": "Welk vertragingsalgoritme moet de nieuwe poging gebruiken? 10^xof 2^x of x*2", "Integration_Retry_Failed_Url_Calls": "Probeer mislukte URL-oproepen opnieuw", @@ -2292,7 +2305,7 @@ "Invitation": "Uitnodiging", "Invitation_Email_Description": "U kunt de volgende variabels gebruiken:
      • [email] voor het e-mailadres van de ontvanger.
      • [Site_Name] en [Site_URL] voor respectievelijk de applicatienaam en URL.
      ", "Invitation_HTML": "Uitnodiging HTML", - "Invitation_HTML_Default": "

      Je bent uitgenodigd voor [Site_Name]

      Ga naar [Site_URL] en probeer de beste open source chat-oplossing die vandaag beschikbaar is!

      ", + "Invitation_HTML_Default": "

      Je bent uitgenodigd voor [Site_Name]

      Ga naar [Site_URL] en probeer de beste open source chatoplossing die vandaag beschikbaar is!

      ", "Invitation_Subject": "Uitnodiging onderwerp", "Invitation_Subject_Default": "Je bent uitgenodigd voor [Site_Name]", "Invite": "Nodig uit", @@ -2339,12 +2352,14 @@ "Jitsi_Limit_Token_To_Room": "Beperk het token tot Jitsi Room", "Job_Title": "Functietitel", "join": "Toetreden", + "Join_call": "Deelnemen aan gesprek", "Join_audio_call": "Deelnemen aan audiogesprek", "Join_Chat": "Met chat meedoen", "Join_default_channels": "Word lid van standaardkanalen", "Join_the_Community": "Word lid van de community", "Join_the_given_channel": "Word lid van het gegeven kanaal", "Join_video_call": "Deelnemen aan videogesprek", + "Join_my_room_to_start_the_video_call": "Word lid van mijn kamer om het videogesprek te starten", "join-without-join-code": "Word lid zonder deelnamecode", "join-without-join-code_description": "Toestemming om de deelnamecode te omzeilen in kanalen waarvoor join-code is ingeschakeld", "Joined": "Toegetreden", @@ -2353,7 +2368,7 @@ "Jump_to_first_unread": "Ga naar eerste ongelezen", "Jump_to_message": "Ga naar bericht", "Jump_to_recent_messages": "Ga naar recente berichten", - "Just_invited_people_can_access_this_channel": "Alleen uitgenodige mensen hebben toegang tot dit kanaal.", + "Just_invited_people_can_access_this_channel": "Alleen uitgenodigde mensen hebben toegang tot dit kanaal.", "Katex_Dollar_Syntax": "Dollar-syntaxis toestaan", "Katex_Dollar_Syntax_Description": "Sta het gebruik van $$katex block$$ en $inline katex$ syntaxis toe", "Katex_Enabled": "Katex ingeschakeld", @@ -2373,7 +2388,7 @@ "Keyboard_Shortcuts_Mark_all_as_read": "Markeer alle berichten (in alle kanalen) als gelezen", "Keyboard_Shortcuts_Move_To_Beginning_Of_Message": "Ga naar het begin van het bericht", "Keyboard_Shortcuts_Move_To_End_Of_Message": "Ga naar het einde van het bericht", - "Keyboard_Shortcuts_New_Line_In_Message": "Nieuwe regel in bericht opstellen", + "Keyboard_Shortcuts_New_Line_In_Message": "Nieuwe regel in invoer bericht opstellen", "Keyboard_Shortcuts_Open_Channel_Slash_User_Search": "Open kanaal / gebruiker zoeken", "Keyboard_Shortcuts_Title": "Toetsenbord sneltoetsen", "Knowledge_Base": "Kennis basis", @@ -2552,6 +2567,10 @@ "LDAP_Sync_User_Data_Roles_Filter_Description": "Het LDAP-zoekfilter dat wordt gebruikt om te controleren of een gebruiker deel uitmaakt van een groep.", "LDAP_Sync_User_Data_RolesMap": "User Data Group Map", "LDAP_Sync_User_Data_RolesMap_Description": "Wijs LDAP-groepen toe aan Rocket.Chat-gebruikersrollen
      Als voorbeeld zal `{\"rocket-admin\":\"admin\", \"tech-support\":\"support\"}` de rocket-admin LDAP-groep toewijzen aan Rocket's rol \"admin\".", + "LDAP_Teams_BaseDN": "LDAP-teams BaseDN", + "LDAP_Teams_BaseDN_Description": "De LDAP BaseDN gebruikt om gebruikersteams op te zoeken.", + "LDAP_Teams_Name_Field": "LDAP-teamnaam attribuut", + "LDAP_Teams_Name_Field_Description": "Het LDAP-attribuut dat Rocket.Chat moet gebruiken om de teamnaam te laden. Je kunt meer dan één mogelijke attribuutnaam opgeven als je ze scheidt met een komma.", "LDAP_Timeout": "Time-out (ms)", "LDAP_Timeout_Description": "Hoeveel milliseconden wachten op een zoekresultaat voordat een fout wordt geretourneerd", "LDAP_Unique_Identifier_Field": "Uniek identificatieveld", @@ -2580,7 +2599,7 @@ "Leave_Room_Warning": "Weet je zeker dat je het kanaal \"%s\" wilt verlaten?", "Leave_the_current_channel": "Verlaat het huidige kanaal", "Leave_the_description_field_blank_if_you_dont_want_to_show_the_role": "Laat het beschrijvingsveld leeg als u de rol niet wilt weergeven", - "leave-c": "Verlaat kanalen", + "leave-c": "Kanalen verlaten", "leave-c_description": "Toestemming om kanalen te verlaten", "leave-p": "Verlaat privégroepen", "leave-p_description": "Toestemming om privégroepen te verlaten", @@ -2665,6 +2684,7 @@ "Livechat_Triggers": "Livechat-triggers", "Livechat_user_sent_chat_transcript_to_visitor": "__agent__ heeft het chattranscript naar __guest__ gestuurd", "Livechat_Users": "Omnichannel-gebruikers", + "Livechat_Calls": "Livechat-oproepen", "Livechat_visitor_email_and_transcript_email_do_not_match": "E-mailadres van bezoeker en transcriptie-e-mailadres komen niet overeen", "Livechat_visitor_transcript_request": "__guest__ heeft het chattranscript aangevraagd", "LiveStream & Broadcasting": "LiveStream & Broadcasting", @@ -2975,6 +2995,8 @@ "Mobex_sms_gateway_restful_address_desc": "IP of Host van uw Mobex REST API, bijv. `http://192.168.1.1:8080` of `https://www.example.com:8080`", "Mobex_sms_gateway_username": "Gebruikersnaam", "Mobile": "Mobiel", + "mobile-download-file": "Downloaden van bestanden op mobiele apparaten toestaan", + "mobile-upload-file": "Uploaden van bestanden op mobiele apparaten toestaan", "Mobile_Push_Notifications_Default_Alert": "Standaardwaarschuwing pushmeldingen", "Monday": "Maandag", "Mongo_storageEngine": "Mongo Storage Engine", @@ -3005,6 +3027,7 @@ "Mute_Group_Mentions": "Mute @all en @hier vermeldingen", "Mute_someone_in_room": "Demp iemand in de kamer", "Mute_user": "Gebruiker dempen", + "Mute_microphone": "Microfoon dempen", "mute-user": "Gebruiker dempen", "mute-user_description": "Toestemming om andere gebruikers in hetzelfde kanaal te dempen", "Muted": "Gedempt", @@ -3172,6 +3195,8 @@ "Omnichannel": "Omnichannel", "Omnichannel_Directory": "Omnichannel-directory", "Omnichannel_appearance": "Omnichannel-uiterlijk", + "Omnichannel_calculate_dispatch_service_queue_statistics": "Omnichannel-wachtrijstatistieken berekenen en verzenden", + "Omnichannel_calculate_dispatch_service_queue_statistics_Description": "Verwerken en verzenden van wachtrijstatistieken zoals positie en geschatte wachttijd. Als *Livechat-kanaal* niet in gebruik is, is het aan te raden om deze instelling uit te schakelen en te voorkomen dat de server onnodige processen uitvoert.", "Omnichannel_Contact_Center": "Omnichannel-contactcentrum", "Omnichannel_contact_manager_routing": "Wijs nieuwe gesprekken toe aan de contactmanager", "Omnichannel_contact_manager_routing_Description": "Deze instelling wijst een chat toe aan de toegewezen Contact Manager, zolang de Contact Manager online is wanneer de chat start", @@ -3182,6 +3207,7 @@ "Omnichannel_External_Frame_URL": "URL van externe frame", "On": "Aan", "On_Hold_Chats": "On-hold", + "On_Hold_conversations": "Gesprekken in de wacht", "online": "online", "Online": "Online", "Only_authorized_users_can_write_new_messages": "Alleen geautoriseerde gebruikers kunnen nieuwe berichten schrijven", @@ -3398,6 +3424,7 @@ "Query_description": "Aanvullende voorwaarden om te bepalen naar welke gebruikers de e-mail moet worden verzonden. Niet-geabonneerde gebruikers worden automatisch uit de zoekopdracht verwijderd. Het moet een geldige JSON zijn. Voorbeeld: \"{\"createdAt\":{\"$gt\":{\"$date\": \"2015-01-01T00:00:00.000Z\"}}}\"", "Query_is_not_valid_JSON": "Query is geen geldige JSON", "Queue": "Wachtrij", + "Queue_delay_timeout": "Wachtrij verwerking vertraging timeout", "Queue_Time": "Wachttijd", "Queue_management": "Wachtrijbeheer", "quote": "citaat", @@ -3903,7 +3930,7 @@ "Smileys_and_People": "Smileys & Mensen", "SMS": "sms", "SMS_Default_Omnichannel_Department": "Omnichannel-afdeling (standaard)", - "SMS_Default_Omnichannel_Department_Description": "Indien ingesteld, worden alle nieuwe inkomende chats die door deze integratie worden geïnitieerd, naar deze afdeling gestuurd.", + "SMS_Default_Omnichannel_Department_Description": "Indien ingesteld, worden alle nieuwe inkomende chats die door deze integratie worden gestart, naar deze afdeling gerouteerd.\nDeze instelling kan worden overschreven door de department query param in het verzoek door te geven.\nBijv. https:///api/v1/livechat/sms-incoming/twilio?department=.\nOpmerking: indien je afdelingsnaam gebruikt, moet de URL veilig zijn.", "SMS_Enabled": "SMS ingeschakeld", "SMTP": "SMTP", "SMTP_Host": "SMTP-host", @@ -4277,6 +4304,8 @@ "Tuesday": "Dinsdag", "Turn_OFF": "Uitschakelen", "Turn_ON": "Aanzetten", + "Turn_on_video": "Video aanzetten", + "Turn_off_video": "Video uitschakelen", "Two Factor Authentication": "Twee-factorenauthenticatie", "Two-factor_authentication": "Tweefactorauthenticatie via TOTP", "Two-factor_authentication_disabled": "Tweefactorauthenticatie uitgeschakeld", @@ -4338,6 +4367,7 @@ "Unread_Rooms_Mode": "Ongelezen kamers-modus", "Unread_Tray_Icon_Alert": "Waarschuwing in systeemvak voor ongelezen berichten", "Unstar_Message": "Verwijder markering", + "Unmute_microphone": "Microfoon inschakelen", "Update": "Bijwerken", "Update_EnableChecker": "Update Checker inschakelen", "Update_EnableChecker_Description": "Controleert automatisch op nieuwe updates / belangrijke berichten van de Rocket.Chat-ontwikkelaars en ontvangt meldingen indien beschikbaar. De melding verschijnt één keer per nieuwe versie als een klikbare banner en als bericht van de Rocket.Cat-bot, beide alleen zichtbaar voor beheerders.", @@ -4498,6 +4528,7 @@ "UTF8_User_Names_Validation_Description": "RegExp dat zal worden gebruikt om gebruikersnamen te valideren", "UTF8_Channel_Names_Validation": "Validatie van UTF8-kanaalnamen", "UTF8_Channel_Names_Validation_Description": "Validatie van UTF8-kanaalnamen", + "Videocall_enabled": "Videogesprek ingeschakeld", "Validate_email_address": "E-mailadres valideren", "Validation": "Validatie", "Value_messages": "__value__ berichten", @@ -4519,10 +4550,12 @@ "Video_Conference": "Videoconferentie", "Video_message": "Videoboodschap", "Videocall_declined": "Videogesprek geweigerd.", - "Videocall_enabled": "Videogesprek ingeschakeld", + "Video_and_Audio_Call": "Video- en audiogesprek", "Videos": "Videos", "View_All": "Bekijk alle leden", "View_channels": "Bekijk kanalen", + "view-omnichannel-contact-center": "Omnichannel-contactcentrum bekijken", + "view-omnichannel-contact-center_description": "Toestemming om het Omnichannel-contactcentrum te bekijken en ermee te werken", "View_Logs": "Logboeken bekijken", "View_mode": "Weergavemodus", "View_original": "Bekijk origineel", @@ -4596,6 +4629,7 @@ "Visitor_message": "Bezoekersberichten", "Visitor_Name": "Bezoekersnaam", "Visitor_Name_Placeholder": "Voer een bezoekersnaam in...", + "Visitor_does_not_exist": "Bezoeker bestaat niet!", "Visitor_Navigation": "Bezoekersnavigatie", "Visitor_page_URL": "URL van bezoekerspagina", "Visitor_time_on_site": "Bezoeker tijd op de site", @@ -4623,6 +4657,7 @@ "Webhook_Details": "WebHook details", "Webhook_URL": "Webhook-URL", "Webhooks": "Webhooks", + "WebRTC_Call": "WebRTC-oproep", "WebRTC_direct_audio_call_from_%s": "Directe audiogesprek van %s", "WebRTC_direct_video_call_from_%s": "Direct videogesprek van %s", "WebRTC_Enable_Channel": "Inschakelen voor openbare kanalen", @@ -4633,6 +4668,8 @@ "WebRTC_monitor_call_from_%s": "Monitor oproep van %s", "WebRTC_Servers": "STUN / TURN Servers", "WebRTC_Servers_Description": "Een lijst met STUN- en TURN-servers gescheiden door komma's.
      Gebruikersnaam, wachtwoord en poort zijn toegestaan in de indeling `gebruikersnaam: wachtwoord@stun:host:poort` of `gebruikersnaam:wachtwoord@turn:host:poort`.", + "WebRTC_call_ended_message": " Gesprek beëindigd om __endTime__ - Duurde __callDuration__", + "WebRTC_call_declined_message": " Gesprek geweigerd door contact.", "Website": "Website", "Wednesday": "Woensdag", "Weekly_Active_Users": "Wekelijks actieve gebruikers", diff --git a/packages/rocketchat-i18n/i18n/no.i18n.json b/packages/rocketchat-i18n/i18n/no.i18n.json index 308f89c11c2df..9b48b55c553ea 100644 --- a/packages/rocketchat-i18n/i18n/no.i18n.json +++ b/packages/rocketchat-i18n/i18n/no.i18n.json @@ -618,6 +618,7 @@ "Continuous_sound_notifications_for_new_livechat_room": "Kontinuerlige lydvarsler for nytt livechat-rom", "Conversation": "Samtale", "Conversation_closed": "Samtalen avsluttet: __comment__.", + "Conversation_finished": "Samtalen er avsluttet", "Conversation_finished_message": "Samtalen avsluttet melding", "conversation_with_s": "samtalen med %s", "Convert_Ascii_Emojis": "Konverter ASCII til Emoji", @@ -2784,6 +2785,7 @@ "Users_added": "Brukerne har blitt lagt til", "Users_in_role": "Brukere i rollen", "UTF8_Names_Slugify": "UTF8 Navn Slugify", + "Videocall_enabled": "Videoanrop aktivert", "Validate_email_address": "Bekreft e-postadresse", "Verification": "Bekreftelse", "Verification_Description": "Du kan bruke følgende plassholdere:
      • [Verification_Url] for verifikasjonsadressen.
      • [navn], [fname], [lname] for brukerens fulle navn, fornavn eller etternavn.
      • [email] for brukerens e-postadresse.
      • [Site_Name] og [Site_URL] for henholdsvis programnavnet og nettadressen.
      ", @@ -2798,7 +2800,6 @@ "Video_Conference": "Video konferanse", "Video_message": "Videomelding", "Videocall_declined": "Videoanrop avslått.", - "Videocall_enabled": "Videoanrop aktivert", "View_All": "Se alle medlemmer", "View_Logs": "Se logger", "View_mode": "Visningsmodus", diff --git a/packages/rocketchat-i18n/i18n/pl.i18n.json b/packages/rocketchat-i18n/i18n/pl.i18n.json index 9b6a1b8dbfbad..8c8174f777396 100644 --- a/packages/rocketchat-i18n/i18n/pl.i18n.json +++ b/packages/rocketchat-i18n/i18n/pl.i18n.json @@ -345,7 +345,7 @@ "API_Add_Personal_Access_Token": "Dodaj nowy osobisty token dostępowy", "API_Allow_Infinite_Count": "Pozwól uzyskać wszystko", "API_Allow_Infinite_Count_Description": "Czy połączenia z interfejsem REST API powinny zwracać wszystko w jednym wywołaniu?", - "API_Analytics": "Analityka", + "API_Analytics": "Analytics", "API_CORS_Origin": "CORS Origin", "API_Default_Count": "Domyślny licznik", "API_Default_Count_Description": "Domyślna liczba dla REST API wynika, jeśli konsument nie podał żadnych.", @@ -4392,6 +4392,7 @@ "UTF8_User_Names_Validation_Description": "RegExp, który będzie używany do sprawdzania poprawności nazw użytkowników", "UTF8_Channel_Names_Validation": "Walidacja nazw kanałów UTF8", "UTF8_Channel_Names_Validation_Description": "RegExp, który będzie używany do sprawdzania poprawności nazw kanałów", + "Videocall_enabled": "Rozmowa video uruchomiona", "Validate_email_address": "Sprawdź poprawność adresu e-mail", "Validation": "Walidacja", "Value_messages": "__value__ wiadomości", @@ -4413,7 +4414,6 @@ "Video_Conference": "Konferencja wideo", "Video_message": "Wiadomość wideo", "Videocall_declined": "Rozmowa video odrzucona.", - "Videocall_enabled": "Rozmowa video uruchomiona", "Videos": "Wideo", "View_All": "Pokaż wszystko", "View_channels": "Wyświetl kanały", diff --git a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json index fd7d9fadd791a..6f2e9780080e5 100644 --- a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json +++ b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json @@ -32,7 +32,7 @@ "access-setting-permissions": "Modifique as permissões baseadas em configuração", "access-setting-permissions_description": "Permissão para modificar permissões baseadas em configuração", "Accessing_permissions": "Acessando permissões", - "Account_SID": "Conta SID", + "Account_SID": "SID da Conta", "Accounts": "Contas", "Accounts_Admin_Email_Approval_Needed_Default": "

      O usuário [nome] ([email]) foi registrado.

      Verifique \"Administração ->Usuários\" para ativá-lo ou excluí-lo.

      ", "Accounts_Admin_Email_Approval_Needed_Subject_Default": "Um novo usuário se registrou e precisa de aprovação", @@ -43,14 +43,14 @@ "Accounts_AllowedDomainsList": "Lista de Domínios Permitidos", "Accounts_AllowedDomainsList_Description": "Lista de domínios permitidos, separados por vírgula", "Accounts_AllowInvisibleStatusOption": "Permitir opção de status Invisível", - "Accounts_AllowEmailChange": "Permitir alterar e-mail", + "Accounts_AllowEmailChange": "Permitir Alteração de E-mail", "Accounts_AllowEmailNotifications": "Permitir notificações por e-mail", "Accounts_AllowPasswordChange": "Permitir Alteração de Senha", "Accounts_AllowPasswordChangeForOAuthUsers": "Permitir alteração de senha para usuários OAuth", "Accounts_AllowRealNameChange": "Permitir Mudança de Nome", - "Accounts_AllowUserAvatarChange": "Permitir que O Usuário Troque O Avatar", + "Accounts_AllowUserAvatarChange": "Permitir que o Usuário Troque o Avatar", "Accounts_AllowUsernameChange": "Permitir Alterar Nome de Usuário", - "Accounts_AllowUserProfileChange": "Permitir Mudança no Perfil de Usuário", + "Accounts_AllowUserProfileChange": "Permitir Alteração no Perfil de Usuário", "Accounts_AllowUserStatusMessageChange": "Permitir Mensagem de Estado Personalizada", "Accounts_AvatarBlockUnauthenticatedAccess": "Bloquear acesso não autenticado aos avatares", "Accounts_AvatarCacheTime": "Tempo de cache do avatar", @@ -65,7 +65,7 @@ "Accounts_BlockedUsernameList_Description": "Lista de nomes de usuários bloqueados, separada por vírgulas (não diferencia maiúsculas)", "Accounts_CustomFields_Description": "Deve ser um JSON válido onde as chaves são os nomes de campos contendo um dicionário de configuração de campos. Exemplo:
      {\n \"role\": {\n \"type\": \"select\",\n \"defaultValue\": \"student\",\n \"options\": [\"teacher\", \"student\"],\n \"required\": true,\n \"modifyRecordField\": {\n \"array\": true,\n \"field\": \"roles\"\n }\n },\n \"twitter\": {\n \"type\": \"text\",\n \"required\": true,\n \"minLength\": 2,\n \"maxLength\": 10\n }\n}", "Accounts_CustomFieldsToShowInUserInfo": "Campos Personalizados para Exibir em Informação do Usuário", - "Accounts_Default_User_Preferences": "Preferências de Usuário Padrões", + "Accounts_Default_User_Preferences": "Preferências Padrão de Usuário", "Accounts_Default_User_Preferences_audioNotifications": "Áudio do Alerta de Notificação Padrão", "Accounts_Default_User_Preferences_desktopNotifications": "Alerta de Notificação para Desktop Padrão", "Accounts_Default_User_Preferences_pushNotifications": "Alerta de Notificação Push Padrão", @@ -79,7 +79,7 @@ "Accounts_Email_Approved_Subject": "Conta aprovada", "Accounts_Email_Deactivated": "[name]

      Sua conta foi desativada.

      ", "Accounts_Email_Deactivated_Subject": "Conta desativada", - "Accounts_EmailVerification": "Verificação de E-mail", + "Accounts_EmailVerification": "Permitir login apenas de usuários verificados", "Accounts_EmailVerification_Description": "Certifique-se de que possui definições SMTP corretas para usar este recurso", "Accounts_Enrollment_Email": "E-mail de Inscrição", "Accounts_Enrollment_Email_Default": "

      Bem-vindo ao [Site_Name]

      Vá para [Site_URL] e teste a melhor solução de bate-papo open source disponível!

      ", @@ -114,6 +114,8 @@ "Accounts_OAuth_Custom_Merge_Users": "Mesclar usuários", "Accounts_OAuth_Custom_Name_Field": "Campo de nome", "Accounts_OAuth_Custom_Roles_Claim": "Nome do campo de funções / grupos", + "Accounts_OAuth_Custom_Roles_To_Sync": "Papéis a Sincronizar", + "Accounts_OAuth_Custom_Roles_To_Sync_Description": "Papéis OAuth a sincronizar na autenticação e criação do usuário (separado por vírgulas)", "Accounts_OAuth_Custom_Scope": "Escopo", "Accounts_OAuth_Custom_Secret": "Secreto", "Accounts_OAuth_Custom_Show_Button_On_Login_Page": "Mostrar Botão na Página de Autenticação", @@ -704,6 +706,9 @@ "By_author": "Por __author__", "cache_cleared": "Cache limpo", "Call": "Ligação", + "Call_declined": "Chamada Recusada!", + "Call_provider": "Provedor de Chamada", + "Call_Already_Ended": "Chamada já Encerrada", "call-management": "Gestão de Chamadas", "call-management_description": "Permissão para iniciar reunião", "Caller": "Caller", @@ -885,7 +890,7 @@ "close-others-livechat-room_description": "Permissão para fechar outras salas de Omnichannel", "Closed": "Fechado", "Closed_At": "Fechado em", - "Closed_automatically": "Fechado automaticamente pelo sistema", + "Closed_automatically": "Encerrado automaticamente pelo sistema", "Closed_automatically_chat_queued_too_long": "Encerrado automaticamente pelo sistema (tempo máximo de fila excedido)", "Closed_by_visitor": "Encerrado pelo visitante", "Closing_chat": "Encerrando chat", @@ -1442,7 +1447,7 @@ "Disallow_reacting": "Não permitir reagir", "Disallow_reacting_Description": "Não permite reagir", "Discard": "Descartar", - "Disconnect": "Desconectado", + "Disconnect": "Desconectar", "Discussion": "Discussão", "Discussion_description": "Ajude a manter uma visão geral sobre o que está acontecendo! Ao criar uma discussão, um sub-canal do que selecionou é criado e ambos são ligados.", "Discussion_first_message_disabled_due_to_e2e": "Você pode começar a enviar mensagens criptografadas End-to-End nessa discussão após sua criação.", @@ -1613,6 +1618,8 @@ "Encryption_key_saved_successfully": "Sua chave de criptografia foi salva com sucesso.", "EncryptionKey_Change_Disabled": "Você não pode definir uma senha para sua chave de criptografia porque sua chave privada não está presente neste cliente. Para definir uma nova senha, você precisa inserir sua chave privada usando sua senha existente ou usar um cliente onde a chave já esteja em uso.", "End": "Fim", + "End_call": "Encerrar Chamada", + "Expand_view": "Expandir visão", "End_OTR": "Finalizar OTR", "Engagement_Dashboard": "Dashboard de Engajamento", "Enter": "Enter", @@ -1840,9 +1847,10 @@ "Favorite": "Adicionar aos Favoritos", "Favorite_Rooms": "Ativar Salas Favoritas", "Favorites": "Favoritos", + "Feature_depends_on_selected_call_provider_to_be_enabled_from_administration_settings": "Essa funcionalidade depende do provedor de chamada selecionado acima para ser habilitado nas configurações administrativas", "Feature_Depends_on_Livechat_Visitor_navigation_as_a_message_to_be_enabled": "Esse recurso depende de \"Enviar histórico de navegação do visitante como uma mensagem\" para ser ativado.", - "Features": "Funcionalidades", "Feature_Limiting": "Limitação de funcionalidades", + "Features": "Funcionalidades", "Features_Enabled": "Funcionalidades habilitadas", "Feature_Disabled": "Funcionalidade desabilitada", "Federation": "Federação", @@ -2068,7 +2076,7 @@ "Group_discussions": "Discussões em grupo", "Group_favorites": "Grupos favoritos", "Group_mentions_disabled_x_members": "As menções de grupo `@all` e` @here` foram desativadas para salas com mais de __total__ membros.", - "Group_mentions_only": "Grupo menciona apenas", + "Group_mentions_only": "Apenas menções a Grupo", "Grouping": "Agrupamento", "Guest": "Convidado", "Hash": "Hash", @@ -2344,12 +2352,14 @@ "Jitsi_Limit_Token_To_Room": "Limitar token a Sala Jitsi", "Job_Title": "Cargo", "join": "Entrar", + "Join_call": "Participar da Chamada", "Join_audio_call": "Entrar na chamada de áudio", "Join_Chat": "Junte-se ao Chat", "Join_default_channels": "Entrar em canais predefinidos", "Join_the_Community": "Junte-se à Comunidade", "Join_the_given_channel": "Entrar no canal informado", "Join_video_call": "Entrar na chamada de vídeo", + "Join_my_room_to_start_the_video_call": "Participar da minha sala para iniciar chamada de vídeo", "join-without-join-code": "Cadastre-se sem se juntar ao código", "join-without-join-code_description": "Permissão para ignorar o código de associação em canais com o código de associação ativado", "Joined": "Entrou", @@ -2557,6 +2567,10 @@ "LDAP_Sync_User_Data_Roles_Filter_Description": "O filtro de busca LDAP usado para verificar se um usuário está em um grupo.", "LDAP_Sync_User_Data_RolesMap": "Mapeamento de Grupo de Dados de Usuário", "LDAP_Sync_User_Data_RolesMap_Description": "Mapeia grupos LDAP para papeis de usuário Rocket.Chat
      Por exemplo, `{\"rocket-admin\":\"admin\", \"suporte-tecnico\":\"suporte\"}` irá mapear o grupo LDAP rocket-admin para o papel \"admin\" do Rocket.Chat.", + "LDAP_Teams_BaseDN": "BaseDN LDAP de Equipes", + "LDAP_Teams_BaseDN_Description": "O BaseDN LDAP usado para procurar equipes do usuário", + "LDAP_Teams_Name_Field": "Atributo de Nome do Time LDAP", + "LDAP_Teams_Name_Field_Description": "O atributo LDAP que o Rocket.Chat deve usar para carregar o nome da equipe. Voce pode especificar mais de um nome de atributo possível se voce separá-los com uma vírgula.", "LDAP_Timeout": "Tempo limite (ms)", "LDAP_Timeout_Description": "Qual tempo limite esperar por um resultado de pesquisa antes de retornar um erro", "LDAP_Unique_Identifier_Field": "Campo Identificador Único", @@ -2670,6 +2684,7 @@ "Livechat_Triggers": "Gatilhos de Livechat", "Livechat_user_sent_chat_transcript_to_visitor": "__agent__ enviou a transcrição do bate-papo para __guest__", "Livechat_Users": "Usuários Omnichannel", + "Livechat_Calls": "Chamadas Livechat", "Livechat_visitor_email_and_transcript_email_do_not_match": "O email do visitante e o email da transcrição não correspondem", "Livechat_visitor_transcript_request": "__guest__ solicitou a transcrição do bate-papo", "LiveStream & Broadcasting": "LiveStream e Broadcasting", @@ -2806,6 +2821,7 @@ "Markdown_Parser": "Parser Markdown", "Markdown_SupportSchemesForLink": "Protocolos Suportados para Markdown de Links", "Markdown_SupportSchemesForLink_Description": "Lista de protocolos separados por vírgulas", + "Marketplace": "Marketplace", "Marketplace_view_marketplace": "Ver Marketplace", "MAU_value": "MAU __value__", "Max_length_is": "Tamanho máximo é %s", @@ -2920,7 +2936,7 @@ "Message_MaxAllowedSize": "Tamanho máximo de mensagem permitido", "Message_pinning": "Fixação de mensagem", "message_pruned": "mensagem removida", - "Message_QuoteChainLimit": "Número máximo de citações acorrentadas", + "Message_QuoteChainLimit": "Número máximo de citações encadeadas", "Message_Read_Receipt_Enabled": "Mostrar Recibos de Leitura", "Message_Read_Receipt_Store_Users": "Recibos de leitura detalhados", "Message_Read_Receipt_Store_Users_Description": "Mostra os recibos de leitura de cada usuário", @@ -3011,6 +3027,7 @@ "Mute_Group_Mentions": "Silenciar menções @all e @here", "Mute_someone_in_room": "Silenciar alguém na sala", "Mute_user": "Silenciar usuário", + "Mute_microphone": "Silenciar Microfone", "mute-user": "Silenciar usuário", "mute-user_description": "Permissão para silenciar outros usuários no mesmo canal", "Muted": "Silenciado", @@ -3190,6 +3207,7 @@ "Omnichannel_External_Frame_URL": "URL do Frame Externo", "On": "Em", "On_Hold_Chats": "Em Espera", + "On_Hold_conversations": "Conversas em espera", "online": "online", "Online": "Online", "Only_authorized_users_can_write_new_messages": "Somente usuários autorizados podem escrever novas mensagens", @@ -4286,6 +4304,8 @@ "Tuesday": "Terça-feira", "Turn_OFF": "Desligar", "Turn_ON": "Ligar", + "Turn_on_video": "Habilitar video", + "Turn_off_video": "Desabilitar video", "Two Factor Authentication": "Autenticação de dois fatores", "Two-factor_authentication": "Autenticação de dois fatores por TOTP", "Two-factor_authentication_disabled": "Autenticação de dois fatores desativada", @@ -4347,6 +4367,7 @@ "Unread_Rooms_Mode": "Agrupar Salas Não Lidas", "Unread_Tray_Icon_Alert": "Alerta do ícone da bandeja não lida", "Unstar_Message": "Remover Favorito", + "Unmute_microphone": "Tirar Microfone do Mudo", "Update": "Atualizar", "Update_EnableChecker": "Habilitar Verificador de Atualização", "Update_EnableChecker_Description": "Verifica automaticamente por novas atualizações / mensagens importantes do desenvolvedores do Rocket.Chat e receber notificações quando disponíveis. A notificação aparece uma vez por nova versão como um banner clicável e como uma mensagem do bot Rocket.Cat, ambos visíveis apenas para administradores.", @@ -4401,7 +4422,7 @@ "User_added": "Usuário adicionado", "User_added_by": "Usuário __user_added__ adicionado à conversa por __user_by__.", "User_added_successfully": "Usuário adicionado com sucesso", - "User_and_group_mentions_only": "O usuário e o grupo mencionam apenas", + "User_and_group_mentions_only": "Apenas menções a usuário e grupo", "User_cant_be_empty": "O usuário não pode estar vazio", "User_created_successfully!": "Usuário criado com sucesso!", "User_default": "Padrão do usuário", @@ -4436,7 +4457,7 @@ "User_left_team_male": "Saiu da equpe.", "User_logged_out": "Usuário não logado", "User_management": "Gerenciamento de usuários", - "User_mentions_only": "O usuário menciona apenas", + "User_mentions_only": "Apenas menções a usuário", "User_muted": "Usuário silenciado", "User_muted_by": "Usuário __user_muted__ silenciado por __user_by__.", "User_not_found": "Usuário não encontrado", @@ -4507,6 +4528,7 @@ "UTF8_User_Names_Validation_Description": "RegExp que será usado para validar nomes de usuários", "UTF8_Channel_Names_Validation": "Validação de Nomes de Canal UTF8", "UTF8_Channel_Names_Validation_Description": "RegExp que será usada para validar nomes de canais", + "Videocall_enabled": "Vídeoconferência habilitada", "Validate_email_address": "Validar endereço de e-mail", "Validation": "Validação", "Value_messages": "__value__ mensagens", @@ -4528,7 +4550,7 @@ "Video_Conference": "Vídeo Conferência", "Video_message": "Mensagem de vídeo", "Videocall_declined": "Chamada de vídeo negada.", - "Videocall_enabled": "Vídeoconferência habilitada", + "Video_and_Audio_Call": "Chamadas de Video e Áudio", "Videos": "Vídeos", "View_All": "Ver Todos Membros", "View_channels": "Ver Canais", @@ -4607,6 +4629,7 @@ "Visitor_message": "Mensagens de Visitantes", "Visitor_Name": "Nome do Visitante", "Visitor_Name_Placeholder": "Digite o nome do visitante ...", + "Visitor_does_not_exist": "Visitante não existe!", "Visitor_Navigation": "Navegação do Visitante", "Visitor_page_URL": "URL da página de visitante", "Visitor_time_on_site": "Tempo do visitante no site", @@ -4634,6 +4657,7 @@ "Webhook_Details": "Detalhes do WebHook", "Webhook_URL": "URL do webhook", "Webhooks": "Webhooks", + "WebRTC_Call": "Chamada WebRTC", "WebRTC_direct_audio_call_from_%s": "Chamada de áudio direta de %s", "WebRTC_direct_video_call_from_%s": "Videochamada direta de %s", "WebRTC_Enable_Channel": "Habilitar para Canais Públicos", @@ -4644,6 +4668,8 @@ "WebRTC_monitor_call_from_%s": "Monitore a chamada de %s", "WebRTC_Servers": "Servidores STUN/TURN", "WebRTC_Servers_Description": "Uma lista de servidores STUN e TURN separados por vírgula.
      Nome de usuário, senha e porta são permitidos no formato `username:password @stun:host:port` ou `username:password@turn:host:port`.", + "WebRTC_call_ended_message": " Chamada encerrada em __endTime__ - Duração __callDuration__", + "WebRTC_call_declined_message": " Chamada Recusada pelo Contato.", "Website": "Site", "Wednesday": "Quarta-feira", "Weekly_Active_Users": "Usuários Ativos Semanalmente", @@ -4734,4 +4760,4 @@ "Your_temporary_password_is_password": "Sua senha temporária é [password].", "Your_TOTP_has_been_reset": "Seu TOTP de dois fatores foi redefinido.", "Your_workspace_is_ready": "O seu espaço de trabalho está pronto para usar 🎉" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/pt.i18n.json b/packages/rocketchat-i18n/i18n/pt.i18n.json index f623c63a1b075..5961977f1d27e 100644 --- a/packages/rocketchat-i18n/i18n/pt.i18n.json +++ b/packages/rocketchat-i18n/i18n/pt.i18n.json @@ -692,6 +692,7 @@ "Continuous_sound_notifications_for_new_livechat_room": "Notificações sonoras contínuas, para nova sala de livechat", "Conversation": "Conversa", "Conversation_closed": "Chat encerrado: __comment__.", + "Conversation_finished": "Chat encerrado", "Conversation_finished_message": "Mensagem ao encerrar chat", "conversation_with_s": "a conversa com %s", "Conversations": "Conversas", @@ -1977,6 +1978,7 @@ "Max_length_is": "O comprimento máximo é %s", "Media": "Média", "Medium": "Médio", + "Members": "Membros", "Members_List": "Lista de Membros", "mention-all": "Mencionar todos", "mention-all_description": "Permissão para usar a menção @todos", @@ -2771,7 +2773,7 @@ "Sunday": "Domingo", "Support": "Apoio", "Survey": "Inquérito", - "Survey_instructions": "Classifique cada questão de acordo com a sua satisfação, 1 significa que você está completamente insatisfeito e 5 significa que você está completamente satisfeito.", + "Survey_instructions": "Classifique cada questão de acordo com a sua satisfação, 1 significa que está completamente insatisfeito e 5 significa que está completamente satisfeito.", "Symbols": "Símbolos", "Sync": "Sincronizar", "Sync / Import": "Sincronizar / Importar", @@ -3093,6 +3095,7 @@ "Users_added": "Os utilizadores foram adicionados", "Users_in_role": "Utilizadores na função", "UTF8_Names_Slugify": "Slugify Nomes UTF8 ", + "Videocall_enabled": "Vídeoconferência habilitada", "Validate_email_address": "Validar endereço de e-mail", "Verification": "Validação", "Verification_Description": "Pode usar os seguintes espaços reservados:
      • [Verification_Url] para o URL de verificação.
      • [name], [fname], [lname] para o nome completo, primeiro nome ou sobrenome do utilizador, respetivamente.
      • [email] para o email do usuário.
      • [Site_Name] e [Site_URL] para o Nome da aplicação e o URL, respetivamente.
      ", @@ -3109,7 +3112,6 @@ "Video_Conference": "Vídeo Conferência", "Video_message": "Mensagem de vídeo", "Videocall_declined": "Chamada de vídeo rejeitada.", - "Videocall_enabled": "Vídeoconferência habilitada", "View_All": "Ver todos os utilizadores", "View_Logs": "Ver Registo", "View_mode": "Modo de visualização", diff --git a/packages/rocketchat-i18n/i18n/ro.i18n.json b/packages/rocketchat-i18n/i18n/ro.i18n.json index 9c222c8979b88..b62744fc559ff 100644 --- a/packages/rocketchat-i18n/i18n/ro.i18n.json +++ b/packages/rocketchat-i18n/i18n/ro.i18n.json @@ -555,6 +555,7 @@ "Continuous_sound_notifications_for_new_livechat_room": "Notificări de sunet continue pentru o cameră livechat nouă", "Conversation": "Conversaţie", "Conversation_closed": "Conversație închisă: __comment__.", + "Conversation_finished": "conversație terminat", "Conversation_finished_message": "Conversație Mesaj terminat", "conversation_with_s": "conversația cu %s", "Convert_Ascii_Emojis": "Conversie ASCII în Emoji", @@ -1685,6 +1686,7 @@ "Max_length_is": "Lungimea maximă este%s", "Media": "Mass-media", "Medium": "Mediu", + "Members": "Membri", "Members_List": "Lista de membri", "mention-all": "Menționați pe toate", "mention-all_description": "Permisiunea de a utiliza mențiunea @all", @@ -2300,6 +2302,7 @@ "Show_Setup_Wizard": "Afișați asistentul de configurare", "Show_the_keyboard_shortcut_list": "Afișați lista de comenzi rapide de la tastatură", "Showing_archived_results": "

      Se arată %s rezultate arhivate

      ", + "Showing_online_users": null, "Showing_results": "

      Se afișează %s rezultate

      ", "Sidebar": "Bara laterală", "Sidebar_list_mode": "Modul listei de canale din bara laterală", @@ -2678,6 +2681,7 @@ "Users_added": "Utilizatorii au fost adăugați", "Users_in_role": "Utilizatorii în rol", "UTF8_Names_Slugify": "UTF8 Names Slugify", + "Videocall_enabled": "Apel video este activat", "Validate_email_address": "Validați adresa de e-mail", "Verification": "Verificare", "Verification_Description": "Puteți utiliza următorii substituenți:
      • [Verification_Url] pentru adresa URL de verificare.
      • [name], [fname], [lname] pentru numele complet al utilizatorului, prenumele sau numele de familie.
      • [e-mail] pentru e-mailul utilizatorului.
      • [Site_Name] și [Site_URL] pentru numele aplicației și respectiv adresa URL.
      ", @@ -2692,7 +2696,6 @@ "Video_Conference": "Conferințe video", "Video_message": "Mesaj video", "Videocall_declined": "Videoclipul a fost refuzat.", - "Videocall_enabled": "Apel video este activat", "View_All": "Vezi toți", "View_Logs": "Vezi log-uri", "View_mode": "mod de vizualizare", @@ -2778,6 +2781,7 @@ "Yes_unarchive_it": "Da, dezarhivați-o!", "yesterday": "ieri", "You": "Tu", + "you_are_in_preview_mode_of": null, "You_are_logged_in_as": "Sunteți autentificat ca ", "You_are_not_authorized_to_view_this_page": "Nu sunteți autorizat pentru a vizualiza această pagină.", "You_can_change_a_different_avatar_too": "Puteți înlocui avatarul folosit pentru a posta din această integrare.", @@ -2813,4 +2817,4 @@ "Your_push_was_sent_to_s_devices": "Mesajul Push a fost trimis la %s dispozitive", "Your_server_link": "Linkul dvs. de server", "Your_workspace_is_ready": "Spațiul dvs. de lucru este gata de utilizare 🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/ru.i18n.json b/packages/rocketchat-i18n/i18n/ru.i18n.json index dbe9514dcea66..e707a4230856b 100644 --- a/packages/rocketchat-i18n/i18n/ru.i18n.json +++ b/packages/rocketchat-i18n/i18n/ru.i18n.json @@ -949,6 +949,7 @@ "Confirm_password": "Подтвердить пароль", "Confirmation": "Подтверждение", "Connect": "Подключение", + "Connected": "Подключено", "Connect_SSL_TLS": "Подключение с помощью SSL/TLS", "Connection_Closed": "Соединение закрыто", "Connection_Reset": "Сброс соединения", @@ -4385,6 +4386,7 @@ "UTF8_User_Names_Validation_Description": "Регулярное выражение для валидации имени пользователя", "UTF8_Channel_Names_Validation": "UTF8 валидация имени чата", "UTF8_Channel_Names_Validation_Description": "Регулярное выражение для валидации имени чата", + "Videocall_enabled": "Видеозвонок включен", "Validate_email_address": "Подтвердите адрес электронной почты", "Validation": "Валидация", "Value_messages": "__value__ сообщений", @@ -4406,7 +4408,6 @@ "Video_Conference": "Видеоконференция", "Video_message": "Видеосообщение", "Videocall_declined": "Видеозвонок отклонён.", - "Videocall_enabled": "Видеозвонок включен", "Videos": "Видеозаписи", "View_All": "Смотреть всех участников", "View_channels": "Просмотр каналов", @@ -4607,7 +4608,7 @@ "Your_push_was_sent_to_s_devices": "Оповещение было отправлено на %s устройств.", "Your_question": "Ваш вопрос", "Your_server_link": "Ссылка на ваш сервер", - "Your_temporary_password_is_password": "Ваш временный пароль [password].", + "Your_temporary_password_is_password": "Ваш временный пароль [password]", "Your_TOTP_has_been_reset": "Ваш двухфакторный TOTP был сброшен.", "Your_workspace_is_ready": "Ваше рабочее пространство готово к работе 🎉" } \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/sk-SK.i18n.json b/packages/rocketchat-i18n/i18n/sk-SK.i18n.json index f44ce1eb3c012..4d8f4b11dbd63 100644 --- a/packages/rocketchat-i18n/i18n/sk-SK.i18n.json +++ b/packages/rocketchat-i18n/i18n/sk-SK.i18n.json @@ -561,6 +561,7 @@ "Continuous_sound_notifications_for_new_livechat_room": "Nepretržité zvukové upozornenia pre novú miestnosť livechat", "Conversation": "Konverzácia", "Conversation_closed": "Konverzácia bola uzavretá: __comment__.", + "Conversation_finished": "Konverzácia bola dokončená", "Conversation_finished_message": "Oznam o ukončení konverzácie", "conversation_with_s": "Konverzácia s %s", "Convert_Ascii_Emojis": "Previesť ASCII na Emoji", @@ -1284,7 +1285,7 @@ "hours": "hodiny", "Hours": "hodiny", "How_friendly_was_the_chat_agent": "Ako priateľský bol chatový agent?", - "How_knowledgeable_was_the_chat_agent": "Ako bol znalý chatový agent?", + "How_knowledgeable_was_the_chat_agent": "Ako informovaný bol diskusný agent?", "How_long_to_wait_after_agent_goes_offline": "Ako dlho čakať po tom, ako sa agent stane offline", "How_responsive_was_the_chat_agent": "Ako reagoval chatový agent?", "How_satisfied_were_you_with_this_chat": "Ako ste boli spokojní s týmto rozhovorom?", @@ -1975,7 +1976,7 @@ "Placeholder_for_password_login_field": "Zástupca pre pole Prihlasovacie heslo", "Please_add_a_comment": "Pridajte komentár", "Please_add_a_comment_to_close_the_room": "Pridajte komentár na zatvorenie miestnosti", - "Please_answer_survey": "Venujte chvíľu odpovede na rýchly prieskum o tomto rozhovore", + "Please_answer_survey": "Venujte prosím chvíľu odpovediam v rýchlom prieskume o tejto diskusii", "Please_enter_usernames": "Zadajte používateľské mená ...", "please_enter_valid_domain": "Zadajte platnú doménu", "Please_enter_value_for_url": "Zadajte hodnotu pre adresu URL vášho avatara.", @@ -2249,7 +2250,7 @@ "Select_user": "Vyberte používateľa", "Select_users": "Vyberte používateľov", "Selected_agents": "Vybraní zástupcovia", - "Send": "odoslať", + "Send": "Odoslať", "Send_a_message": "Poslať správu", "Send_a_test_mail_to_my_user": "Pošlite skúšobnú poštu môjmu používateľovi", "Send_a_test_push_to_my_user": "Pošlite skúšobný krok môjmu používateľovi", @@ -2322,7 +2323,7 @@ "Site_Url": "Adresa URL lokality", "Site_Url_Description": "Príklad: https://chat.domena.com/", "Size": "veľkosť", - "Skip": "preskočiť", + "Skip": "Preskočiť", "Slack_Users": "Slack používatelia CSV", "SlackBridge_error": "SlackBridge dostal chybu pri importovaní vašich správ na%s:%s", "SlackBridge_finish": "Služba SlackBridge dokončila importovanie správ na%s. Znova načítajte všetky správy.", @@ -2418,8 +2419,8 @@ "Success_message": "Správa o úspechu", "Sunday": "nedeľa", "Support": "podpora", - "Survey": "prehľad", - "Survey_instructions": "Hodnoť každú otázku podľa vašej spokojnosti, 1 znamená, že ste úplne nespokojní a 5 znamená, že ste úplne spokojní.", + "Survey": "Prieskum", + "Survey_instructions": "Ohodnoťte každú otázku na základe vašej spokojnosti, 1 znamená úplnú nespokojnosť, 5 znamená úplnú spokojnosť.", "Symbols": "symboly", "Sync_in_progress": "Prebieha synchronizácia", "Sync_success": "Synchronizácia úspechu", @@ -2440,7 +2441,7 @@ "Test_Connection": "Test pripojenia", "Test_Desktop_Notifications": "Testovanie upozornení na pracovnej ploche", "Thank_you_exclamation_mark": "Ďakujem!", - "Thank_you_for_your_feedback": "Ďakujeme vám za vašu reakciu", + "Thank_you_for_your_feedback": "Ďakujeme vám za vašu spätnú väzbu", "The_application_name_is_required": "Názov aplikácie je povinný", "The_channel_name_is_required": "Názov kanála je povinný", "The_emails_are_being_sent": "Posielajú sa e-maily.", @@ -2692,6 +2693,7 @@ "Users_added": "Používatelia boli pridaní", "Users_in_role": "Používatelia v úlohe", "UTF8_Names_Slugify": "Mená UTF8", + "Videocall_enabled": "Videohovor je zapnutý", "Validate_email_address": "Overiť e-mailovú adresu", "Verification": "overenie", "Verification_Description": "Môžete použiť nasledujúce zástupné symboly:
      • [Verification_Url] pre verifikačnú adresu URL.
      • [meno], [fname], [lname] pre celé meno používateľa, krstné meno alebo priezvisko.
      • [email] pre e-mail používateľa.
      • [Site_Name] a [Site_URL] pre názov aplikácie a adresu URL.
      ", @@ -2706,7 +2708,6 @@ "Video_Conference": "Video konferencia", "Video_message": "Video správy", "Videocall_declined": "Zamietnutý videohovor.", - "Videocall_enabled": "Videohovor je zapnutý", "View_All": "Zobraziť všetkých členov", "View_Logs": "Zobraziť denníky", "View_mode": "Režim zobrazenia", @@ -2780,7 +2781,7 @@ "will_be_able_to": "budú môcť", "Worldwide": "celosvetovo", "Would_you_like_to_return_the_inquiry": "Chcete vrátiť dotaz?", - "Yes": "Áno,", + "Yes": "Áno", "Yes_archive_it": "Áno, archivujte to!", "Yes_clear_all": "Áno, jasné všetko!", "Yes_delete_it": "Áno, odstráňte ju!", @@ -2791,7 +2792,7 @@ "Yes_remove_user": "Áno, odstráňte používateľa!", "Yes_unarchive_it": "Áno, dearchívujte to!", "yesterday": "včera", - "You": "vy", + "You": "Vy", "you_are_in_preview_mode_of": "Nachádzate sa v režime náhľadu kanálu # __room_name__", "You_are_logged_in_as": "Ste prihlásení ako", "You_are_not_authorized_to_view_this_page": "Nemáte oprávnenie na zobrazenie tejto stránky.", diff --git a/packages/rocketchat-i18n/i18n/sl-SI.i18n.json b/packages/rocketchat-i18n/i18n/sl-SI.i18n.json index 9732fa2b55917..f5a0f7306a320 100644 --- a/packages/rocketchat-i18n/i18n/sl-SI.i18n.json +++ b/packages/rocketchat-i18n/i18n/sl-SI.i18n.json @@ -552,6 +552,7 @@ "Continue": "Nadaljuj", "Continuous_sound_notifications_for_new_livechat_room": "Neprekinjena zvočna obvestila za novo sobo za življenje", "Conversation": "Pogovor", + "Conversation_finished": "Pogovor končan", "Conversation_finished_message": "Konverzirano sporočilo", "conversation_with_s": "pogovor z %s", "Convert_Ascii_Emojis": "Pretvori ASCII v čustveni simbol", @@ -2672,6 +2673,7 @@ "Users_added": "Uporabniki so bili dodani", "Users_in_role": "Uporabniki v vlogi", "UTF8_Names_Slugify": "Uporabi UTF8 Slugify za imena", + "Videocall_enabled": "Video klic je omogočen", "Validate_email_address": "Potrdite e-poštni naslov", "Verification": "Preverjanje", "Verification_Description": "Za URL za preverjanje lahko uporabite naslednje označbe:
      • [Verification_Url].
      • [name], [fname], [lname] za polno ime, ime ali priimek uporabnika.
      • [email] za uporabnikov e-poštni naslov.
      • [Site_Name] in [Site_URL] za ime aplikacije in URL.
      ", @@ -2686,7 +2688,6 @@ "Video_Conference": "Video konferenca", "Video_message": "Video sporočilo", "Videocall_declined": "Video klic zavrnjen.", - "Videocall_enabled": "Video klic je omogočen", "View_All": "Ogled vseh članov", "View_Logs": "Ogled dnevnikov", "View_mode": "Način pogleda", diff --git a/packages/rocketchat-i18n/i18n/sq.i18n.json b/packages/rocketchat-i18n/i18n/sq.i18n.json index edd5658a2ed39..1e5706a37ab5b 100644 --- a/packages/rocketchat-i18n/i18n/sq.i18n.json +++ b/packages/rocketchat-i18n/i18n/sq.i18n.json @@ -555,6 +555,7 @@ "Continuous_sound_notifications_for_new_livechat_room": "Njoftime të vazhdueshme të zërit për dhomën e re të jetesës", "Conversation": "Biseda", "Conversation_closed": "Biseda mbyllur: __comment__.", + "Conversation_finished": "biseda përfunduar", "Conversation_finished_message": "Biseda Përfundoi Mesazhi", "conversation_with_s": "biseda me %s", "Convert_Ascii_Emojis": "Convert ASCII të Emoji", @@ -1684,6 +1685,7 @@ "Max_length_is": "Gjatësia maksimale është %s", "Media": "Media", "Medium": "medium", + "Members": "Anëtarët", "Members_List": "Lista e Anëtarëve", "mention-all": "Përmend të gjitha", "mention-all_description": "Leja për të përdorur të gjitha përmendur", @@ -2679,6 +2681,7 @@ "Users_added": "Përdoruesit janë shtuar", "Users_in_role": "Përdoruesit në rolin", "UTF8_Names_Slugify": "UTF8 Emrat Slugify", + "Videocall_enabled": "Thirrje video të aktivizuara", "Validate_email_address": "Validoni adresën e emailit", "Verification": "verifikim", "Verification_Description": "Mund të përdorësh vendin e mëposhtëm:
      • [Verification_Url] për URL-në e verifikimit.
      • [emër], [fname], [lname] për emrin e plotë, emrin ose mbiemrin e përdoruesit.
      • [email] për emailin e përdoruesit.
      • [Site_Name] dhe [Site_URL] për emrin e aplikacionit dhe URL përkatësisht.
      ", @@ -2693,7 +2696,6 @@ "Video_Conference": "Konferenca Video", "Video_message": "Mesazh video", "Videocall_declined": "Thirrje video nuk pranohet.", - "Videocall_enabled": "Thirrje video të aktivizuara", "View_All": "Shiko të gjitha", "View_Logs": "Shiko Shkrime", "View_mode": "mënyra e shfaqjes", diff --git a/packages/rocketchat-i18n/i18n/sr.i18n.json b/packages/rocketchat-i18n/i18n/sr.i18n.json index b384b8e64926a..391f91bc4ffe9 100644 --- a/packages/rocketchat-i18n/i18n/sr.i18n.json +++ b/packages/rocketchat-i18n/i18n/sr.i18n.json @@ -1,6 +1,7 @@ { "403": "Забрањен", "500": "Интерна грешка сервера", + "__username__was_set__role__by__user_by_": null, "@username": "@корисничко име", "@username_message": "@ корисничко име ", "#channel": "#канал", @@ -48,6 +49,7 @@ "Accounts_EmailVerification": "Дозволи пријаву само потврђеним корисницима", "Accounts_EmailVerification_Description": "Потврдите да имате исправне SMTP параметре да би сте користили ову могућност", "Accounts_Enrollment_Email_Default": "

      Добродошли на[Site_Name]

      Иди на [Site_URL] и испробај најбоље решење за ћаскање отвореног кода које је тренутно доступно!

      ", + "Accounts_Enrollment_Email_Description": "Можете користити [name], [fname], [lname] за пуно име корисника, имену или презимену, респективно.
      Можете користити [email] е-поште корисника.", "Accounts_Enrollment_Email_Subject_Default": "Добродошли на [Site_Name]", "Accounts_ForgetUserSessionOnWindowClose": "Заборави корисничку сесију по затварању прозора", "Accounts_Iframe_api_method": "Api метода", @@ -71,13 +73,25 @@ "Accounts_OAuth_Custom_Token_Path": "Путања токена", "Accounts_OAuth_Custom_Token_Sent_Via": "Токен послат путем", "Accounts_OAuth_Custom_Username_Field": "Поље корисничког имена", + "Accounts_OAuth_Facebook_callback_url": "Фацебоок УРЛ за повратни позив", + "Accounts_OAuth_Facebook_id": "ИД фацебоок апликација", + "Accounts_OAuth_Github_callback_url": "Гитхуб УРЛ за повратни позив", + "Accounts_OAuth_GitHub_Enterprise": "ОАутх Омогућено", "Accounts_OAuth_GitHub_Enterprise_id": "Ид клијента", "Accounts_OAuth_GitHub_Enterprise_secret": "Тајна клијента", "Accounts_OAuth_Github_id": "Ид клијента", "Accounts_OAuth_Github_secret": "Тајна клијента", + "Accounts_OAuth_Gitlab": "ОАутх Омогућено", + "Accounts_OAuth_Gitlab_callback_url": "ГитЛаб УРЛ за повратни позив", + "Accounts_OAuth_Gitlab_id": "ГитЛаб ИД", "Accounts_OAuth_Gitlab_identity_path": "Путања до идентитета", "Accounts_OAuth_Gitlab_secret": "Тајна клијента", + "Accounts_OAuth_Google": "гоогле Пријава", + "Accounts_OAuth_Google_id": "гоогле ИД", + "Accounts_OAuth_Meteor": "метеор Пријава", + "Accounts_OAuth_Meteor_callback_url": "Метеор УРЛ за повратни позив", "Accounts_OAuth_Wordpress_authorize_path": "Путања до ауторизације", + "Accounts_OAuth_Wordpress_id": "ВордПресс ИД", "Accounts_OAuth_Wordpress_identity_path": "Путања до идентитета", "Accounts_OAuth_Wordpress_scope": "Опсег", "Accounts_OAuth_Wordpress_server_type_custom": "Прилагођено", @@ -110,6 +124,7 @@ "Accounts_RegistrationForm_Public": "Јавни", "Accounts_RegistrationForm_Secret_URL": "Тајна УРЛ адреса", "Accounts_RegistrationForm_SecretURL": "Тајна УРЛ адреса обрасца за регистрацију", + "Accounts_RegistrationForm_SecretURL_Description": "Морате обезбедити случајни низ који ће бити додат на ваш регистрације УРЛ. Пример: хттпс://демо.роцкет.цхат/регистер/[secret_hash]", "Accounts_RequireNameForSignUp": "Захтева име за регистрацију", "Accounts_RequirePasswordConfirmation": "Захтевај потврду лозинке", "Accounts_SearchFields": "Поља за размишљање у потрази", @@ -128,6 +143,7 @@ "Activity": "Активност", "Add": "Додај", "Add_agent": "Додај агента", + "Add_custom_oauth": "Додај прилагођени ОАутх", "Add_Domain": "Додај домен", "Add_files_from": "Додајте датотеке из", "Add_manager": "Додај менаџера", @@ -148,6 +164,7 @@ "Additional_emails": "Додатне Е-поште", "Administration": "Администрација", "Adult_images_are_not_allowed": "Слике за одрасле нису дозвољене", + "After_OAuth2_authentication_users_will_be_redirected_to_this_URL": "Након ОАутх2 аутентификације, корисници ће бити преусмерени на овај УРЛ", "Agent": "Агент", "Agent_added": "Агент додат", "Agent_removed": "Агент уклоњен", @@ -165,6 +182,7 @@ "Always_open_in_new_window": "Увек отвори у новом прозору", "Analytics": "Аналитика", "Analytics_features_enabled": "Активиране могућности", + "Analytics_features_users_Description": "Треки прилагођене догађаје који се односе на акције које се односе на кориснике (пассворд ресет пута, профил Промена слике, итд).", "Analytics_Google": "Гугл аналитика", "Analytics_Google_id": "ИД праћења", "and": "и", @@ -173,10 +191,13 @@ "Announcement": "Најава", "API": "АПИ", "API_Analytics": "Аналитика", + "API_CORS_Origin": "ЦОРС Оригин", "API_Drupal_URL_Description": "Пример: https://domain.com (искључујући крајњу косу црту)", "API_Embed": "Убаци преглед линкова", "API_Embed_Description": "Да ли су омогућени прегледи уграђене везе када корисник поставља линк на веб локацију.", + "API_EmbedDisabledFor": "Онемогући Додајте за кориснике", "API_EmbedDisabledFor_Description": "Зарезом одвојена листа корисничких имена којима је онемогућен преглед убачених линкова.", + "API_EmbedIgnoredHosts_Description": "Зарезом одвојена листа хостова или цидр адресе, нпр. лоцалхост 127.0.0.1, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16", "API_EmbedSafePorts": "Безбедни портови", "API_EmbedSafePorts_Description": "Зарезом одвојена листа портова дозвољених за преглед.", "API_Enable_Direct_Message_History_EndPoint": "Омогући крајњу тачку историје директних порука", @@ -188,6 +209,9 @@ "API_Token": "АПИ токен", "API_Tokenpass_URL_Description": "Пример: https://domain.com (искључујући крајњу косу црту)", "API_Upper_Count_Limit": "Максимално записа", + "API_User_Limit": "Корисник лимит за додавање Сви корисници на канал", + "API_Wordpress_URL": "УордПресс УРЛ адреса", + "Apiai_Language": "Апи.аи Језик", "App_author_homepage": "веб страница аутора", "App_Information": "Информације о апликацији", "App_Installation": "Инсталација апликације", @@ -229,6 +253,7 @@ "Audio_Notification_Value_Description": "Може бити сваки прилагођени звук или подразумевани: звучни сигнал, звук, динг, капљица, високи звук, годишња доба", "Audio_Notifications_Default_Alert": "Подразумевана обавештења о аудио обавештењима", "Audio_Notifications_Value": "Подразумевано обавештење о поруци за звук", + "Auth_Token": "аутх токен", "Author": "Аутор", "Author_Information": "Информације о аутору", "Authorization_URL": "УРЛ адреса за ауторизацију", @@ -237,7 +262,11 @@ "Auto_Translate": "Аутоматски преведи", "auto-translate": "Аутоматско превођење", "auto-translate_description": "Дозвола за коришћење алата за аутоматско превођење", + "AutoLinker_Phone": "АутоЛинкер телефона", + "AutoLinker_StripPrefix": "АутоЛинкер Стрип Префикс", "AutoLinker_StripPrefix_Description": "Кратак приказ. нпр https://rocket.chat => rocket.chat", + "AutoLinker_Urls_Scheme": "АутоЛинкер Шема: // адресе", + "AutoLinker_UrlsRegExp": "АутоЛинкер УРЛ-Регулар Екпрессион", "Automatic_Translation": "Аутоматско превођење", "AutoTranslate": "Аутоматски преведи", "AutoTranslate_APIKey": "АПИ кључ", @@ -249,6 +278,7 @@ "Avatar": "Аватар", "Avatar_changed_successfully": "Аватар успешно промењен", "Avatar_URL": "УРЛ адреса аватара", + "Avatar_url_invalid_or_error": "УРЛ адреса ако је неважећи или није доступна. Покушајте поново, али са различитим УРЛ.", "away": "одсутан(на)", "Away": "Одсутан(на)", "away_female": "недоступан", @@ -322,6 +352,8 @@ "Channels_are_where_your_team_communicate": "Канали су тамо где ваш тим комуницира", "Channels_list": "Листа јавних канала", "Chat_Now": "Ћаскај сада", + "Chatops_Enabled": "Омогући Цхатопс", + "Chatops_Title": "Цхатопс панел", "Chatpal_All_Results": "Све", "Chatpal_API_Key": "АПИ кључ", "Chatpal_Base_URL": "Основни УРЛ", @@ -402,6 +434,7 @@ "Continue": "наставити", "Continuous_sound_notifications_for_new_livechat_room": "Непрекидна звучна обавештења за нову собу за живот", "Conversation": "Разговор", + "Conversation_finished": "разговор завршио", "Conversation_finished_message": "Завршена порука конверзације", "conversation_with_s": "разговор са %s", "Convert_Ascii_Emojis": "Претворити ASCII у емотиконе", @@ -685,6 +718,7 @@ "Custom_Emoji_Info": "Информације о прилагођеном емотикону", "Custom_Emoji_Updated_Successfully": "Прилагођени емотикон је успешно ажуриран", "Custom_Fields": "Произвољна поља", + "Custom_oauth_helper": "Када подешавате ОАутх провајдера, мораћете да обавести УРЛ за повратни позив. употреба
       % с 
      .", "Custom_Script_Logged_In": "Произвољне скрипте за пријављене кориснике", "Custom_Script_Logged_Out": "Произвољне скрипте за одјављене кориснике", "Custom_Scripts": "Прилагођене скрипте", @@ -738,6 +772,7 @@ "Desktop_Notifications_Default_Alert": "Подразумевана обавештења на радној површини", "Desktop_Notifications_Disabled": "Обавештења на радној површини су искључена. Промените подешавања вашег прегледача ако желите да укључите обавештења.", "Desktop_Notifications_Duration": "Трајање обавештења на радној површини", + "Desktop_Notifications_Duration_Description": "Секунде да бисте приказали обавештење десктоп. Ово може утицати на ОС Кс Нотифицатион Центер. Унесите 0 за коришћење подразумевана подешавања претраживача и не утиче на ОС Кс Нотифицатион Центер.", "Desktop_Notifications_Enabled": "Обавештења на радној површини су омогућена", "Different_Style_For_User_Mentions": "Различит стил за помињање корисника", "Direct_message_someone": "Пошаљи директну поруку некоме", @@ -836,6 +871,7 @@ "Enable_two-factor_authentication": "Омогућите двоструку аутентификацију", "Enabled": "Оmogućeno", "Encrypted_message": "Шифрована порука", + "End_OTR": "Крај ОТР", "Enter_a_room_name": "Унесите име собе", "Enter_a_username": "Унесите корисничко име", "Enter_Alternative": "Алтернативни режим (послати с Ентер + Цтрл / Алт / Схифт / ЦМД)", @@ -854,6 +890,8 @@ "Error_RocketChat_requires_oplog_tailing_when_running_in_multiple_instances": "Грешка: Роцкет.Цхат захтева оплог таилинг када ради у више инстанци", "Error_RocketChat_requires_oplog_tailing_when_running_in_multiple_instances_details": "Проверите да ли је ваш МонгоДБ на режиму РеплицаСет, а варијабла окружења МОНГО_ОПЛОГ_УРЛ је исправно дефинирана на серверу апликација", "error-application-not-found": "Апликација није пронађена", + "error-avatar-invalid-url": "Инвалид Аватар УРЛ: __url__", + "error-avatar-url-handling": "Грешка при руковању подешавање аватар из УРЛ (__url__) за __username__", "error-cant-invite-for-direct-room": "Није могуће позивати кориснике у директне собе", "error-channels-setdefault-is-same": "Подразумевана поставка канала је иста као и оно на шта ће се променити.", "error-channels-setdefault-missing-default-param": "Захтева се бодиПарам 'дефаулт'", @@ -869,6 +907,8 @@ "error-field-unavailable": "__field__ је већ у употреби :(", "error-file-too-large": "Датотека је превелика", "error-importer-not-defined": "Увозник није правилно дефинисан, недостаје му класа за увоз.", + "error-input-is-not-a-valid-field": "__input__ није валидан __field__", + "error-invalid-actionlink": "Неважећи радња Линк", "error-invalid-arguments": "Неисправни аргументи", "error-invalid-channel": "Неисправан канал.", "error-invalid-channel-start-with-chars": "Неисправан канал. Почните са @ или #", @@ -888,6 +928,7 @@ "error-invalid-name": "Неисправан назив", "error-invalid-password": "Неисправна лозинка", "error-invalid-permission": "Неважећа дозвола", + "error-invalid-redirectUri": "неважећи редирецтУри", "error-invalid-role": "Неисправна улога", "error-invalid-room": "Неисправна соба", "error-invalid-room-name": "__room_name__ није исправан назив соба", @@ -895,12 +936,14 @@ "error-invalid-settings": "Унешена су неисправна подешавања", "error-invalid-subscription": "Неисправна претплата", "error-invalid-token": "Неисправан токен", + "error-invalid-triggerWords": "Неважећи триггерВордс", "error-invalid-urls": "Погрешна УРЛ адреса", "error-invalid-user": "Погрешан корисник", "error-invalid-username": "Погрешно корисничко име", "error-logged-user-not-in-room": "Нисте у соби `%s`", "error-message-deleting-blocked": "Брисање порука је блокирано", "error-message-editing-blocked": "Уређивање порука је блокирано", + "error-message-size-exceeded": "величина поруке већа од Мессаге_МакАлловедСизе", "error-missing-unsubscribe-link": "Морате навести [unsubscribe] линк.", "error-no-tokens-for-this-user": "Нема токена за овог корисника", "error-not-allowed": "Није дозвољено", @@ -913,10 +956,13 @@ "error-password-policy-not-met-oneSpecial": "Лозинка не испуњава политику сервера од најмање једног специјалног карактера", "error-password-policy-not-met-oneUppercase": "Лозинка не испуњава политику сервера од најмање једног великог слова", "error-password-policy-not-met-repeatingCharacters": "Лозинка не испуњава политику сервера забрањених понављања знакова (имате превише истих знакова поред једне друге)", + "error-push-disabled": "Пусх је онемогућен", "error-remove-last-owner": "Ово је последњи власник. Подесите новог власника пре него што овог уклоните.", "error-role-in-use": "Не можете обрисати улогу јер је у употреби", "error-role-name-required": "Име улоге је неопходно", "error-room-is-not-closed": "Соба није затворена", + "error-the-field-is-required": "је обавезно поље __field__.", + "error-too-many-requests": "Грешка, превише захтева. Молим те успори. Морате да сачекате __seconds__ секунди пре него што поново покушава.", "error-user-has-no-roles": "Корисник нема улоге", "error-user-is-not-activated": "Корисник није активиран", "error-user-limit-exceeded": "Број корисника које покушавате позвати у #цханнел_наме превазилази лимит који је поставио администратор", @@ -947,6 +993,7 @@ "Favorite": "Омиљено", "Favorite_Rooms": "Омогући омиљене собе", "Favorites": "Омиљене", + "Features_Enabled": "karakteristike Омогућено", "Federation_Domain": "Домен", "Federation_Discovery_method": "Начин откривања", "FEDERATION_Discovery_Method": "Начин откривања", @@ -972,6 +1019,7 @@ "FileUpload_Error": "Грешка при постављању датотеке", "FileUpload_File_Empty": "Датотека је празна", "FileUpload_FileSystemPath": "Системска путања", + "FileUpload_GoogleStorage_Bucket": "Гоогле Стораге Буцкет Наме", "FileUpload_GoogleStorage_Secret_Description": "Пратите ова упутства и залепите резултат овде.", "FileUpload_MaxFileSize": "Максимална величина уплоад сизе (ин битес)", "FileUpload_MaxFileSizeDescription": "Подесите на -1 да уклоните ограничење величине датотеке.", @@ -1010,6 +1058,7 @@ "Financial_Services": "Финансијске услуге", "First_Channel_After_Login": "Први канал након пријаве", "Flags": "Заставе", + "Follow_social_profiles": "Пратите наше друштвене профиле, форкујте нас на Гитхабу и поделите ваша мишљења о нашој rocket.chat апликацији на нашој Трело табли.", "Fonts": "Фонтови", "Food_and_Drink": "Храна и пиће", "Footer": "Подножје", @@ -1038,6 +1087,8 @@ "From_email_warning": "Упозорење: Поље Од подлеже поставкама твог севера е-поште.", "Gaming": "Гаминг", "General": "Општи", + "Give_a_unique_name_for_the_custom_oauth": "Дати јединствено име за прилагођени ОАутх", + "Give_the_application_a_name_This_will_be_seen_by_your_users": "Дајте Апликација име. Ово ће бити виђена од својих корисника.", "Global": "Глобално", "Global_purge_override_warning": "Успостављена је глобална политика задржавања. Ако оставите \"Оверриде глобал полици ретентион\" искључену, можете примијенити само политику која је строжија од глобалне политике.", "Global_Search": "Глобално претраживање", @@ -1069,6 +1120,7 @@ "Host": "Домаћин", "hours": "сати", "Hours": "Сати", + "How_knowledgeable_was_the_chat_agent": "Како знања је агент ћаскање?", "How_long_to_wait_after_agent_goes_offline": "Како дуго чекати након што агент престане да ради", "How_to_handle_open_sessions_when_agent_goes_offline": "Како поступати са отвореним сесијама када агент отпутује ван мреже", "Idle_Time_Limit": "Лимит Идле Тиме", @@ -1099,6 +1151,7 @@ "Importer_CSV_Information": "ЦСВ увознику је потребан одређени формат, молимо прочитајте документацију како структурирати своју зип датотеку:", "Importer_done": "Увоз завршен!", "Importer_finishing": "Завршавање увоза.", + "Importer_From_Description": "Увоз __from__ 'с подацима у Роцкет.Цхат.", "Importer_HipChatEnterprise_BetaWarning": "Имајте на уму да је овај увоз и даље посао у току, пријавите грешке које се јављају у ГитХуб-у:", "Importer_HipChatEnterprise_Information": "Датотека која је отпремљена мора бити дешифрована тар.гз, прочитајте документацију за додатне информације:", "Importer_import_cancelled": "Увоз отказан.", @@ -1111,6 +1164,8 @@ "Importer_not_setup": "Увозник није подешен правилно, пошто није вратио никакве податке.", "Importer_Prepare_Restart_Import": "Почни увоз поново", "Importer_Prepare_Start_Import": "Почни увожење", + "Importer_Prepare_Uncheck_Archived_Channels": "Поништите потврду Архивирани Канали", + "Importer_progress_error": "Није добио напредак за увоз.", "Importer_setup_error": "Дошло је до грешке приликом подешавања увозника.", "Importer_Slack_Users_CSV_Information": "Датотека која је отпремљена мора да буде датотека извоза корисника Слацк-а, која је ЦСВ датотека. Погледајте овде за више информација:", "Importer_Source_File": "Избор извора датотека", @@ -1120,6 +1175,7 @@ "Incoming_WebHook": "Долазни ВебХоок", "Industry": "Индустрија", "initials_avatar": "Инитиалс Аватар", + "Install_Extension": "Инсталл Ектенсион", "Install_package": "Инсталирајте пакет", "Installation": "Инсталација", "Installed_at": "инсталиран у", @@ -1169,6 +1225,7 @@ "InternalHubot_Username_Description": "Ово мора бити валидно корисничко име бота регистрованог на твом серверу.", "Invalid_confirm_pass": "Потврдна лозинка се не поклапа са лозинком", "Invalid_email": "Унета је неисправна адреса е-поште", + "Invalid_Import_File_Type": "Погрешна врста Увоз датотеке.", "Invalid_name": "Име не сме бити празно", "Invalid_notification_setting_s": "Неисправна поставка обавештења: %s", "Invalid_pass": "Лозинка не сме бити празна", @@ -1244,6 +1301,7 @@ "Last_seen": "Последњи пут виђен", "Launched_successfully": "Покренут успешно", "Layout": "Распоред", + "Layout_Home_Title": "хоме Наслов", "Layout_Login_Terms": "Услови пријаве", "Layout_Privacy_Policy": "Правила о приватности", "Layout_Sidenav_Footer_description": "Фоотер је величине 260 х 70 пиксела", @@ -1291,6 +1349,8 @@ "LDAP_Login_Fallback_Description": "Ако пријава на ЛДАП-у није успешна, покушајте да се пријавите у систему подразумеваног / локалног налога. Помаже када је ЛДАП из неког разлога пао.", "LDAP_Merge_Existing_Users": "Споји постојеће кориснике", "LDAP_Merge_Existing_Users_Description": "* Опрез! * Када увозите корисника из ЛДАП-а и корисник са истим корисничким именом већ постоји, ЛДАП инфо и лозинка ће бити постављени у постојећи корисник.", + "LDAP_Port": "лука", + "LDAP_Port_Description": "Порт за приступ ЛДАП. нпр: `389` или` 636` за ЛДАПС", "LDAP_Reconnect": "Поново повежите", "LDAP_Reconnect_Description": "Покушајте да се аутоматски повежете када је веза прекинута из неког разлога током извршавања операција", "LDAP_Reject_Unauthorized": "Одбиј неовлашћено", @@ -1339,11 +1399,16 @@ "Livechat_guest_count": "Гост Цоунтер-", "Livechat_Inquiry_Already_Taken": "Ливецхат упит већ преузет", "Livechat_managers": "ЛивеЦхат менаџери", + "Livechat_offline": "ЛивеЦхат онлине", "Livechat_online": "ЛивеЦхат на мрежи", "Livechat_Queue": "Ливецхат Куеуе", "Livechat_registration_form": "Образац за регистрацију", + "Livechat_room_count": "ЛивеЦхат соба датотека", "Livechat_Routing_Method": "Ливецхат метод рутирања", "Livechat_Take_Confirm": "Хоћеш ли узети овог клијента?", + "Livechat_title": "ЛивеЦхат Наслов", + "Livechat_title_color": "ЛивеЦхат Наслов Боја позадине", + "Livechat_Users": "ЛивеЦхат корисника", "Livestream_close": "Затвори Ливестреам", "Livestream_enable_audio_only": "Омогућите само аудио режим", "Livestream_not_found": "Ливестреам није доступан", @@ -1374,11 +1439,15 @@ "Logistics": "Логистика", "Logout": "Одјави се", "Logout_Others": "Одјави ме из других пријављених локација", + "Mail_Message_Invalid_emails": "Које сте дали један или више неважећих е-поште:% с", "Mail_Message_No_messages_selected_select_all": "Нисте изабрали ниједну поруку.", "Mail_Messages": "Поруке е-поште", "Mail_Messages_Instructions": "Изабрати које поруке желите да пошаљете путем е-маила тако што ћете кликнути поруке", + "Mail_Messages_Subject": "Овде је изабран део% с порука", "mail-messages": "Поруке на е-пошту", "mail-messages_description": "Дозвола за кориштење опције поштанских порука", + "Mailer_body_tags": "Морате користити [unsubscribe] за Унсубсцриптион линк.
      Можете користити [name], [fname], [lname] за пуно име корисника, имену или презимену, респективно.
      Можете користити [email] е-поште корисника.", + "Mailing": "маилинг", "Make_sure_you_have_a_copy_of_your_codes_1": "Проверите да ли имате копију својих кодова:", "Make_sure_you_have_a_copy_of_your_codes_2": "Ако изгубите приступ вашој апликацији за аутентификацију, можете да користите један од ових шифара за пријављивање.", "manage-apps": "Управљај апликацијама", @@ -1419,6 +1488,7 @@ "Max_length_is": "Максимална дужина је %s", "Media": "Медији", "Medium": "Средње", + "Members": "Чланови", "Members_List": "Списак чланова", "mention-all": "Ментион Алл", "mention-all_description": "Дозволите да користите @алл напомену", @@ -1437,7 +1507,9 @@ "Message_AllowEditing": "Дозволи уређивање порука", "Message_AllowEditing_BlockEditInMinutes": "Блокирај уређивање порука након (х) минута", "Message_AllowEditing_BlockEditInMinutesDescription": "Унесите 0 да искључите блокирање.", + "Message_AllowPinning": "Дозволи порука пиннинг", "Message_AllowSnippeting": "Дозволи Сниппинг порука", + "Message_AllowStarring": "Дозволи Порука Улоге", "Message_AllowUnrecognizedSlashCommand": "Дозволи непознате команде за сласх", "Message_Attachments": "Додаци за поруке", "Message_Attachments_GroupAttach": "Дугмад за додавање групе", @@ -1564,6 +1636,7 @@ "No_group_with_name_%s_was_found": "Приватна група са именом \"%s\" није пронађена!", "No_groups_yet": "Још увек немате приватне групе.", "No_integration_found": "Интеграција није пронађена од стране обезбеђеног ИД-а.", + "No_livechats": "Немате ливецхатс.", "No_mentions_found": "Помињања нису пронађена", "No_messages_yet": "Још нема порука", "No_pages_yet_Try_hitting_Reload_Pages_button": "Још нема страница. Покушајте да притиснете дугме \"Релоад Пагес\".", @@ -1571,6 +1644,7 @@ "No_results_found": "Нема резултата", "No_results_found_for": "Није пронађен ниједан резултат за:", "No_snippet_messages": "Нема сниппет", + "No_starred_messages": "Но звездицом порука", "No_such_command": "Нема такве команде: `/__command__`", "No_user_with_username_%s_was_found": "Корисник са корисничким именом \"%s\" није пронађен!", "Nobody_available": "Нико није доступан", @@ -1643,8 +1717,14 @@ "Organization_Name": "Назив организације", "Organization_Type": "Тип организације", "Original": "Оригинал", + "OS_Cpus": "ОС Процесор Точка", + "OS_Freemem": "ОС Слободна меморија", + "OS_Release": "ОС Издање", + "OS_Totalmem": "ОС Укупно меморије", + "OS_Type": "тури", "Other": "Друго", "others": "други", + "OTR_is_only_available_when_both_users_are_online": "ОТР је доступна само ако оба корисника су онлине", "Outgoing_WebHook": "Одлазни ВебХоок", "Outgoing_WebHook_Description": "Добијте податке из Роцкет.Цхат у реалном времену.", "Page_title": "Наслов странице", @@ -2007,6 +2087,7 @@ "Show_Setup_Wizard": "Прикажи чаробњак за подешавање", "Show_the_keyboard_shortcut_list": "Покажите листу пречица на тастатури", "Showing_archived_results": "

      Показујући %s архивиране резултате

      ", + "Showing_online_users": null, "Showing_results": "

      Приказујем %s резултата

      ", "Sidebar": "Сидебар", "Sidebar_list_mode": "Режим листе канала бочне траке", @@ -2195,6 +2276,8 @@ "This_email_has_already_been_used_and_has_not_been_verified__Please_change_your_password": "Ова адреса је већ коришћен и није потврђена. Молимо Вас да промените лозинку.", "This_is_a_desktop_notification": "Ово је обавештење десктоп", "This_is_a_push_test_messsage": "Ово је гурање теста Месссаге", + "This_room_has_been_archived_by__username_": "Ова соба је архивиран од __username__", + "This_room_has_been_unarchived_by__username_": "Ова соба је неархивирано од __username__", "Thursday": "Четвртак", "Time_in_seconds": "Време у секундама", "Title": "наслов", @@ -2298,6 +2381,9 @@ "Use_url_for_avatar": "Користи УРЛ за аватар", "Use_User_Preferences_or_Global_Settings": "Користите Усер Преференцес или Глобал Сеттингс", "User": "Корисник", + "User__username__is_now_a_moderator_of__room_name_": "Корисник __username__ је сада модератор за __room_name__", + "User__username__is_now_a_owner_of__room_name_": "Корисник __username__ је сада власник __room_name__", + "User__username__removed_from__room_name__moderators": "Корисник __username__ уклоњен из модератори __room_name__", "User_added": "Корисник/ца додат(а)", "User_added_by": "Корисник/ца __user_added__ је додат(а) од стране __user_by__.", "User_added_successfully": "Корисник је успешно додат", @@ -2356,6 +2442,7 @@ "Username_Change_Disabled": "Ваш Роцкет.Цхат Администратор је онемогућио промену корисничких имена", "Username_description": "Корисничко име се користи да би други могли да вас спомињу у порукама.", "Username_doesnt_exist": "Корисничко име `%s` не постоји.", + "Username_ended_the_OTR_session": "__username__ завршио ОТР сесију", "Username_invalid": "%s није исправно корисничко име,
      користите само слова, бројеве, тачке и повлаке", "Username_is_already_in_here": "`@%s` је већ овде.", "Username_is_not_in_this_room": "Корисник `#%s` није у овој соби.", @@ -2365,6 +2452,7 @@ "Users_added": "Корисници су додати", "Users_in_role": "Корисници у улози", "UTF8_Names_Slugify": "УТФ8 имена Слугифи", + "Videocall_enabled": "Видео позив је омогућен", "Validate_email_address": "Потврдите е-адресу", "Verification": "Верификација", "Verification_Description": "Можете користити следеће држаче:
      • [Верифицатион_Урл] за УРЛ за верификацију.
      • [име], [фнаме], [лнаме] за пуно име, презиме или презиме корисника.
      • [емаил] за е-пошту корисника.
      • [Сите_Наме] и [Сите_УРЛ] за име апликације и УРЛ адресу респективно.
      ", @@ -2379,7 +2467,6 @@ "Video_Conference": "Видео конференција", "Video_message": "Видео порука", "Videocall_declined": "Видео позив је одбачен.", - "Videocall_enabled": "Видео позив је омогућен", "View_All": "Погледај све", "View_Logs": "Погледај протоколе", "View_mode": "Опције приказа", @@ -2465,6 +2552,7 @@ "Yes_unarchive_it": "Да, одвојите га!", "yesterday": "јуче", "You": "ти", + "you_are_in_preview_mode_of": "Ви сте у режиму прегледа од канала # __room_name__", "You_are_logged_in_as": "Пријављени сте као", "You_are_not_authorized_to_view_this_page": "Нисте ауторизовани да видите ову страницу.", "You_can_change_a_different_avatar_too": "Можете премостити аватар користити за постављање из ове интеграције.", @@ -2498,4 +2586,4 @@ "Your_push_was_sent_to_s_devices": "Ваш притиском је послат на %s уређајима", "Your_server_link": "Веза са сервером", "Your_workspace_is_ready": "Ваш радни простор је спреман за кориштење 🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/sv.i18n.json b/packages/rocketchat-i18n/i18n/sv.i18n.json index 85668f1fde4c3..aaa696bfc544a 100644 --- a/packages/rocketchat-i18n/i18n/sv.i18n.json +++ b/packages/rocketchat-i18n/i18n/sv.i18n.json @@ -604,8 +604,9 @@ "Content": "Innehåll", "Continue": "Fortsätt", "Continuous_sound_notifications_for_new_livechat_room": "Kontinuerliga ljudmeddelanden för nytt livechat-rum", - "Conversation": "Konversation", + "Conversation": "Meddelande", "Conversation_closed": "Konversation stängd: __comment__.", + "Conversation_finished": "Konversation avslutad", "Conversation_finished_message": "Konversation Slutfört meddelande", "conversation_with_s": "konversationen med %s", "Conversations": "Konversationer", @@ -1391,6 +1392,7 @@ "Importer_not_setup": "Importören är inte korrekt inställd, eftersom det inte returnerade några data.", "Importer_Prepare_Restart_Import": "Starta om import", "Importer_Prepare_Start_Import": "Börja importera", + "Importer_Prepare_Uncheck_Archived_Channels": "Avmarkera Arkiverade Kanaler", "Importer_Prepare_Uncheck_Deleted_Users": "Avmarkera borttagna användare", "Importer_progress_error": "Det gick inte att få information importstatus.", "Importer_setup_error": "Ett fel uppstod när importeraren skulle skapas.", @@ -1758,6 +1760,7 @@ "Max_length_is": "Max längd är%s", "Media": "Media", "Medium": "Medium", + "Members": "Medlemmar", "Members_List": "Medlemslista", "mention-all": "Nämna alla", "mention-all_description": "Tillstånd att använda @all mention", @@ -2386,6 +2389,7 @@ "Show_Setup_Wizard": "Visa installationsguiden", "Show_the_keyboard_shortcut_list": "Visa genvägslistan för tangentbordet", "Showing_archived_results": "

      Visar %s arkiverade resultat

      ", + "Showing_online_users": null, "Showing_results": "

      Visar %s resultat

      ", "Sidebar": "Sidebar", "Sidebar_list_mode": "Sidpanel Kanallista läge", @@ -2777,6 +2781,7 @@ "Users_added": "Användarna har blivit tillagda", "Users_in_role": "Användare i rollen", "UTF8_Names_Slugify": "UTF8 Names Slugify", + "Videocall_enabled": "Videosamtal aktiverat", "Validate_email_address": "Validera e-postadress", "Verification": "Verifikation", "Verification_Description": "Du kan använda följande platsinnehavare:
      • [Verification_Url] för verifieringsadressen.
      • [namn], [fname], [lname] för användarens fullständiga namn, förnamn eller efternamn.
      • [email] för användarens email.
      • [Site_Name] och [Site_URL] för respektive programnamn och URL.
      ", @@ -2792,7 +2797,6 @@ "Video_Conference": "Videokonferens", "Video_message": "Videomeddelande", "Videocall_declined": "Videokall avvisad.", - "Videocall_enabled": "Videosamtal aktiverat", "View_All": "Visa Alla", "View_Logs": "Visa loggar", "View_mode": "Visningsläge", @@ -2916,4 +2920,4 @@ "Your_push_was_sent_to_s_devices": "Din push skickades till %s enheter", "Your_server_link": "Din serverlänk", "Your_workspace_is_ready": "Din arbetsyta är redo att använda 🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/ta-IN.i18n.json b/packages/rocketchat-i18n/i18n/ta-IN.i18n.json index 92007b250281a..293eb0975800a 100644 --- a/packages/rocketchat-i18n/i18n/ta-IN.i18n.json +++ b/packages/rocketchat-i18n/i18n/ta-IN.i18n.json @@ -555,6 +555,7 @@ "Continuous_sound_notifications_for_new_livechat_room": "புதிய livechat அறைக்கு தொடர்ச்சியான ஒலி அறிவிப்புகள்", "Conversation": "உரையாடல்", "Conversation_closed": "உரையாடல் மூடப்பட்டது: __comment__.", + "Conversation_finished": "உரையாடலை முடித்தேன்", "Conversation_finished_message": "உரையாடல் முடிந்தது செய்தி", "conversation_with_s": "%s உடன் உரையாடல்", "Convert_Ascii_Emojis": "ஈமோஜியில் ஆஸ்கி மாற்ற", @@ -1684,6 +1685,7 @@ "Max_length_is": "அதிகபட்சம்%s", "Media": "ஊடகம்", "Medium": "நடுத்தர", + "Members": "உறுப்பினர்கள்", "Members_List": "உறுப்பினர்கள் பட்டியல்", "mention-all": "எல்லாவற்றையும் குறிப்பிடுங்கள்", "mention-all_description": "@ எல்லாவற்றையும் பயன்படுத்த அனுமதி", @@ -2299,6 +2301,7 @@ "Show_Setup_Wizard": "அமைவு வழிகாட்டி காட்டு", "Show_the_keyboard_shortcut_list": "விசைப்பலகை குறுக்குவழி பட்டியலைக் காட்டு", "Showing_archived_results": "

      %s ஐக் காட்டுகிறது காப்பகப் முடிவுகள்

      ", + "Showing_online_users": null, "Showing_results": "

      %s ஐக் காட்டுகிறது முடிவுகள்

      ", "Sidebar": "பக்கப்பட்டி", "Sidebar_list_mode": "பக்கப்பட்டி சேனல் பட்டியல் முறை", @@ -2596,6 +2599,7 @@ "User": "பயனர்", "User__username__is_now_a_moderator_of__room_name_": "பயனர் __username__ இப்போது __room_name__ ஒரு மதிப்பீட்டாளர்", "User__username__is_now_a_owner_of__room_name_": "பயனர் __username__ இப்போது __room_name__ ஒரு உரிமையாளர் ஆவார்", + "User__username__removed_from__room_name__leaders": "__room_name__ தலைவர்களிடமிருந்து பயனர் __username__ நீக்கப்பட்டது", "User__username__removed_from__room_name__moderators": "பயனர் __username__ __room_name__ நடுவர்களின் நீக்கப்பட்டது", "User__username__removed_from__room_name__owners": "பயனர் __username__ __room_name__ உரிமையாளர்கள் இருந்து நீக்கப்பட்டது", "User_added": "பயனர் __user_added__சேர்க்கப்பட்டார்.", @@ -2676,6 +2680,7 @@ "Users_added": "பயனர்கள் சேர்க்கப்பட்டுள்ளனர்", "Users_in_role": "பாத்திரத்தில் பயனர்கள்", "UTF8_Names_Slugify": "UTF8 பெயர்கள் Slugify", + "Videocall_enabled": "வீடியோ அழைப்பு இயக்கப்பட்டது", "Validate_email_address": "மின்னஞ்சல் முகவரி சரிபார்க்கவும்", "Verification": "சரிபார்ப்பு", "Verification_Description": "நீங்கள் பின்வரும் பெட்டிகளைப் பயன்படுத்தலாம்: சரிபார்ப்பு URL க்கான
      • [சரிபார்ப்பு_உருல்]. முறையே பயனரின் முழுப்பெயர், முதல் பெயர் அல்லது கடைசி பெயர்
      • [name], [fname], [lname]. பயனர் மின்னஞ்சலுக்கான
      • [மின்னஞ்சல்]. விண்ணப்பம் பெயர் மற்றும் URL ஆகியவற்றை முறையே
      • [Site_Name] மற்றும் [Site_URL].
      ", @@ -2690,7 +2695,6 @@ "Video_Conference": "வீடியோ மாநாடு", "Video_message": "வீடியோ செய்தி", "Videocall_declined": "வீடியோ அழைப்பு மறுக்கப்பட்டது.", - "Videocall_enabled": "வீடியோ அழைப்பு இயக்கப்பட்டது", "View_All": "அனைத்தையும் பார்க்க", "View_Logs": "காண்க பதிவுகள்", "View_mode": "காண்க முறையில்", @@ -2785,6 +2789,7 @@ "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "நீங்கள் எளிதாக உங்கள் CRM உடன் livechat ஒருங்கிணைக்க webhooks பயன்படுத்த முடியும்.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "நீங்கள் ஒரு livechat அறையில் விட்டு போக முடியாது. தயவு செய்து, நெருங்கிய பொத்தானை பயன்படுத்த.", "You_have_been_muted": "நீங்கள் ஒலியடக்கப்பட்டுள்ளன இந்த அறையில் பேச முடியாது வேண்டும்", + "You_have_n_codes_remaining": null, "You_have_not_verified_your_email": "நீங்கள் உங்கள் மின்னஞ்சல் சரிபார்க்கப்படவில்லை.", "You_have_successfully_unsubscribed": "நீங்கள் வெற்றிகரமாக எங்கள் Mailling பட்டியல் விலகியுள்ளீர்கள்.", "You_have_to_set_an_API_token_first_in_order_to_use_the_integration": "ஒருங்கிணைப்புகளைப் பயன்படுத்த நீங்கள் முதலில் ஒரு API டோக்கனை அமைக்க வேண்டும்.", @@ -2811,4 +2816,4 @@ "Your_push_was_sent_to_s_devices": "உங்கள் மிகுதி% கள் சாதனங்கள் அனுப்பப்பட்டது", "Your_server_link": "உங்கள் சர்வர் இணைப்பு", "Your_workspace_is_ready": "உங்கள் பணியிடம் use பயன்படுத்த தயாராக உள்ளது" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/th-TH.i18n.json b/packages/rocketchat-i18n/i18n/th-TH.i18n.json index 5fa0c9f8999bf..bda06fa64cd84 100644 --- a/packages/rocketchat-i18n/i18n/th-TH.i18n.json +++ b/packages/rocketchat-i18n/i18n/th-TH.i18n.json @@ -554,6 +554,7 @@ "Continuous_sound_notifications_for_new_livechat_room": "การแจ้งเตือนเสียงอย่างต่อเนื่องสำหรับห้องสดใหม่", "Conversation": "การสนทนา", "Conversation_closed": "ปิดการสนทนา: __comment__", + "Conversation_finished": "การสนทนาเสร็จสิ้นแล้ว", "Conversation_finished_message": "การสนทนาข้อความเสร็จสิ้น", "conversation_with_s": "การสนทนากับ %s", "Convert_Ascii_Emojis": "แปลง ASCII เป็น Emoji", @@ -2670,6 +2671,7 @@ "Users_added": "มีการเพิ่มผู้ใช้แล้ว", "Users_in_role": "ผู้ใช้ที่มีบทบาท", "UTF8_Names_Slugify": "UTF8 ชื่อ Slugify", + "Videocall_enabled": "ใช้งานแฮงเอาท์วิดีโอแล้ว", "Validate_email_address": "ยืนยันที่อยู่อีเมล", "Verification": "การตรวจสอบ", "Verification_Description": "คุณสามารถใช้ตัวยึดตำแหน่งต่อไปนี้:
      • [Verification_Url] สำหรับ URL การยืนยัน
      • [name], [fname], [lname] สำหรับชื่อเต็มของผู้ใช้ชื่อหรือนามสกุลตามลำดับ
      • [email] สำหรับอีเมลของผู้ใช้
      • [Site_Name] และ [Site_URL] สำหรับชื่อแอ็พพลิเคชันและ URL ตามลำดับ
      ", @@ -2684,7 +2686,6 @@ "Video_Conference": "การประชุมทางไกลผ่านระบบวิดีโอ", "Video_message": "ข้อความวิดีโอ", "Videocall_declined": "ปฏิเสธการโทรทางวิดีโอแล้ว", - "Videocall_enabled": "ใช้งานแฮงเอาท์วิดีโอแล้ว", "View_All": "ดูสมาชิกทั้งหมด", "View_Logs": "ดูบันทึก", "View_mode": "โหมดดู", diff --git a/packages/rocketchat-i18n/i18n/tr.i18n.json b/packages/rocketchat-i18n/i18n/tr.i18n.json index 23e9e4dd517c8..8a1bad1788bcf 100644 --- a/packages/rocketchat-i18n/i18n/tr.i18n.json +++ b/packages/rocketchat-i18n/i18n/tr.i18n.json @@ -48,7 +48,7 @@ "Accounts_AvatarExternalProviderUrl": "Avatar için Dış Sağlayıcı URL'si", "Accounts_AvatarExternalProviderUrl_Description": "Örnek: `https://acme.com/api/v1/{username}`", "Accounts_AvatarResize": "Avatarlar Yeniden Boyutlandırılsın", - "Accounts_AvatarSize": "Avatar Boyutu", + "Accounts_AvatarSize": "Profil Resmi Boyutu", "Accounts_BlockedDomainsList": "Engellenen Alanlar Listesi", "Accounts_BlockedDomainsList_Description": "Engellenen alanların virgülle ayrılmış listesi", "Accounts_BlockedUsernameList": "Engellenen Kullanıcı Adı Listesi", @@ -261,7 +261,7 @@ "All_added_tokens_will_be_required_by_the_user": "Eklenen tüm işaretçiler kullanıcı tarafından gerekli olacaktır", "All_channels": "Tüm kanallar", "All_closed_chats_have_been_removed": "Tüm kapalı sohbetler kaldırıldı", - "All_logs": "Tüm kayıtlar", + "All_logs": "Tüm Kayıtlar", "All_messages": "Tüm iletiler", "All_users": "Tüm kullanıcılar", "All_users_in_the_channel_can_write_new_messages": "Kanaldaki tüm kullanıcılar yeni ileti yazabilir", @@ -1107,6 +1107,7 @@ "Discussion_target_channel_description": "Sormak istediğinizle ilgili bir kanal seçin", "Discussion_target_channel_prefix": "Burada bir tartışma oluşturuyorsunuz", "Discussion_title": "Yeni tartışma oluştur", + "discussion-created": "__message__", "Discussions": "Tartışmalar", "Display_chat_permissions": "Mesajlaşma yetkilerini göster", "Display_offline_form": "Çevrimdışı formu görüntüle", @@ -1539,6 +1540,7 @@ "Highlights_List": "Vurgulanacak sözcükler", "History": "Geçmiş", "Home": "Ev", + "Host": "evsahibi", "hours": "saatler", "Hours": "Saatler", "How_friendly_was_the_chat_agent": "Görüşme temsilcisi ne kadar dost canlısıydı?", @@ -1868,6 +1870,7 @@ "LDAP_User_Search_Filter_Description": "Bu filtreyle eşleşen belirtilen, sadece kullanıcıların oturum izin verilecek olursa filtre belirtilirse., belirtilen etki alanı tabanının kapsamındaki tüm kullanıcılar oturum mümkün olacak.
      Active Directory örneğin `memberOf = cn = ROCKET_CHAT, ou = Genel gruplarının iş.
      OpenLDAP için (örn genişletilebilir maç arama) `ou: dn: = ROCKET_CHAT`.", "LDAP_User_Search_Scope": "Kapsam", "LDAP_Username_Field": "Kullanıcı adı alanı", + "LDAP_Username_Field_Description": "Hangi alan yeni kullanıcılar için * kullanıcı adı * olarak kullanılacaktır. giriş sayfasında haberdar adını kullanmak için boş bırakın.
      Sen # {givenName} `gibi, çok şablon etiketlerini kullanabilirsiniz. # {Sn}`.
      Varsayılan değer `sAMAccountName` olduğunu.", "Lead_capture_email_regex": "Kurşun yakalama e-posta regex'i", "Lead_capture_phone_regex": "Telefon yakalama regex'ini yönet", "Leave": "Ayrıl", @@ -2725,6 +2728,7 @@ "Show_Setup_Wizard": "Kurulum Sihirbazını Göster", "Show_the_keyboard_shortcut_list": "Klavye kısayol listesini göster", "Showing_archived_results": "

      %s arşivlenmiş sonuçlar gösteriliyor

      ", + "Showing_online_users": null, "Showing_results": "

      %s kayıt bulundu

      ", "Sidebar": "Kenar çubuğu", "Sidebar_list_mode": "Kenar Çubuğu Kanal Listesi Modu", @@ -3172,6 +3176,7 @@ "Users_added": "Kullanıcılar eklendi", "Users_in_role": "Rol içerisindeki kullanıcılar", "UTF8_Names_Slugify": "UTF8 İsimler Slugify", + "Videocall_enabled": "Video Görüşmesi Etkin", "Validate_email_address": "E-Posta Adresini Doğrula", "Verification": "Doğrulama", "Verification_Description": "Şu yer tutucularını kullanabilirsiniz: Doğrulama URL'si için
      • [Doğrulama_Url]. Sırasıyla kullanıcının tam adı, adı veya soyadı için
      • [ad], [fname], [lname].
      • [e-posta] kullanıcının e-postası için. Sırasıyla Uygulama Adı ve URL için
      • [Site_Name] ve [Site_URL].
      ", @@ -3189,7 +3194,6 @@ "Video_Conference": "Görüntülü Görüşme", "Video_message": "Görüntülü ileti", "Videocall_declined": "Video Görüşmesi Reddedildi.", - "Videocall_enabled": "Video Görüşmesi Etkin", "Videos": "Videolar", "View_All": "Tüm Üyeleri Görüntüle", "View_Logs": "Günlükleri Görüntüle", @@ -3329,4 +3333,4 @@ "Your_question": "Sorunuz", "Your_server_link": "Sunucu bağlantınız", "Your_workspace_is_ready": "Çalışma alanınız kullanılmaya hazır 🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/ug.i18n.json b/packages/rocketchat-i18n/i18n/ug.i18n.json index 3bdfe9bf36f3c..dd77c6c4ac3cb 100644 --- a/packages/rocketchat-i18n/i18n/ug.i18n.json +++ b/packages/rocketchat-i18n/i18n/ug.i18n.json @@ -263,6 +263,7 @@ "Confirm_password": "مەخپىي نومۇرنى جەزملەشتۈرۈش", "Conversation": "پاراڭلىشىش", "Conversation_closed": "__comment__ پاراڭلىشىش ئاخىرلاشتى", + "Conversation_finished": "سۆھبەتلىشىش ئاخىرلاشتى", "Convert_Ascii_Emojis": "خەتلەردىكى چىراي ئىپادىسىنى ئۆزلۈكىدىن تونۇش", "Copied": "كۆپەيتىلدى", "Copy": "كۆپەيتىش", @@ -311,6 +312,7 @@ "Desktop_Notifications_Enabled": "ئۈستەل يۈزى ئۇقتۇرۇشىنى ئىشلىتىش باشلاندى", "Direct_message_someone": "بىۋاسىتە مەلۇم ئادەمگە ئۇچۇر يوللاش", "Direct_Messages": "بىۋاسىتە ئۇچۇر يوللاش", + "Direct_Reply_Password": "پارول", "Display_offline_form": "تورسىز ھالەتتە جەدۋەلنى كۆرسىتىش", "Displays_action_text": "ھەرىكەتتىكى خەتلەرنى كۆرسىتىش", "Do_you_want_to_change_to_s_question": "بۇنداق قىلىپ ئۆزگەرتكۈڭىز بارمۇ ؟%s سىزنىڭ", @@ -589,6 +591,7 @@ "Layout_Sidenav_Footer_description": "260 x 70pxبەت ئاستىنىڭ چوڭ-كىچىكلىكى بولسا", "Layout_Terms_of_Service": "مۇلازىمەت تارمىقى", "LDAP": "LDAP", + "LDAP_Authentication_Password": "پارول", "LDAP_Authentication_UserDN_Description": "`cn=Administrator,cn=Users,dc=Example,dc=com`دە ئۈچىنچى تەرەپنىڭ توپلىشىشى ئۈچۈن قۇرۇلغان ھېسابات نومۇرى. ئىناۋەتلىك بولغان پۈتۈن ئىسىم ئىشلىتىڭ مەسىلەن: . LDAP كۆپ ئەھۋالدا ، ئۇ ئەزا
      نىڭ ئىچىدە ئىزدەش ۋە دەلىللەش رولىنى ئۆتەيدۇ.LDAP ئەزا باشقا ئەزالار كىرگەن چاغدا LDAP بۇ", "LDAP_BaseDN_Description": ".تىكى يۇقىرى دەرىجىلىك مۇندەرىجىنى ئاساسىي تورنامى قىلىپ ئىشلىتىشنى تەكلىپ قىلىمىز .تۆۋەنكى 'بەلگىلەنگەن تورنامى ئىزدەش'تاللاش شەرتىگە ئاساسەن ئەزا چەكلەش ئېلىپ بېرىڭ. .LDAP .ئاستىدىكى ئەزانىلا ئىزدىيەلەيدۇ.DNنىڭ ئاساسىي تور نامى بولىشىنى ئەگەر سىز چەكلىگەن بولسىڭىز بۇ ۋاقىتتا پەقەت بۇ DN.نى سىز كۆپرەپ قاتسىڭىز بولىدۇ لېكىن گۇرۇپپا ۋە گۇرۇپپا ئىچىدە ئەزا نامى چوقۇم بىر تورنامى ئاستىدا بولىشى كېرەك DN .نىڭ تارماق دەرىخى ئىچىدىن ئەزا ۋە گۇرۇپ ئىزدىشىگە قولايلىق بولىدۇ .LDAP نىڭ DNبۇنداق بولغاندا (Domain Base)نى تولدۇرۇپ ئۇنى ئاساسى توربەت نامىد قىددلىڭ ،DNئىناۋەتلىك", "LDAP_CA_Cert": "CA گۇۋاھنامىسى", @@ -663,6 +666,7 @@ "Markdown_Headers": "تېمىسىMarkdown", "Markdown_SupportSchemesForLink": "قوللايدىغان ئۇلانما كېلىشمىىMarkdown", "Markdown_SupportSchemesForLink_Description": "ئىنگلىزچە پەش ئارقىلىق ئايرىپ تۇرىدىغان كېلىشمە تىزىملىكى", + "Members": "قوللانچىلار", "Members_List": "ئەزالار تىزىملىكى", "Mentions": "تىلغا ئېلىش", "Mentions_default": "تىلغا ئېلىش (بەلگىلەنگەن)", @@ -975,6 +979,7 @@ "Show_only_online": "توردا بار ئەزانى كۆرسىتىش", "Show_preregistration_form": "كىرىشتىن بۇرۇنقى ھۆججەتنى كۆرسىتىش", "Showing_archived_results": "

      ئاللىبۇرۇن تۈرگە ئايرىپ ساقلاندى %s كۆرسىتىش

      ", + "Showing_online_users": null, "Showing_results": "تال نەتىجە %sكۆرسىتىش

      ", "since_creation": "دىن باشلانغان %s", "Site_Name": "تور نامى", @@ -1076,6 +1081,7 @@ "This_is_a_desktop_notification": "بۇ بىر ئۈستەل ئۇقتۇرۇىشى", "This_is_a_push_test_messsage": "بۇ بىر سىناق ئۇچۇر", "This_room_has_been_archived_by__username_": "بۇ ياتاق پېچەتلەندى ، __username__تەرىپىدىن", + "This_room_has_been_unarchived_by__username_": "تەرىپىدىن بۇ ياتاق پېچەتلەشتىن بىكار قىلىندى __username__", "Time_in_seconds": "ۋاقىت ()سېكۇنت", "Title": "ماۋزۇ", "Title_bar_color": "ماۋزۇ بۆلىكى رەڭگى", @@ -1174,6 +1180,7 @@ "View_All": "ھەممىنى كۆرۈش", "View_Logs": "زىيارەت قىلغاندىكى كۈندىلىك خاتىرە", "View_mode": "ۋېدىئو ئەندىزىسى", + "view-logs": "زىيارەت قىلغاندىكى كۈندىلىك خاتىرە", "Viewing_room_administration": "مەنزىرە قىلىدىغان ئۆينى باشقۇرۇش", "Visibility": "كۆرۈنۈش دەرىجىسى", "Visible": "كۆرۈنىدىغان", @@ -1234,4 +1241,4 @@ "Your_mail_was_sent_to_s": "يوللاندى %s سىزنىڭ ئىلخىتىڭىز ئاللىبۇرۇن", "Your_password_is_wrong": "پارول خاتا !", "Your_push_was_sent_to_s_devices": "ئۈسكىنىگە يوللاندى %s سىزنىڭ ئىتتىرگىنىڭىز" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/uk.i18n.json b/packages/rocketchat-i18n/i18n/uk.i18n.json index b8dc3c7b161b3..57b9b48377669 100644 --- a/packages/rocketchat-i18n/i18n/uk.i18n.json +++ b/packages/rocketchat-i18n/i18n/uk.i18n.json @@ -1196,6 +1196,7 @@ "Discussion_target_channel_description": "Виберіть канал, пов’язаний із тим, що ви хочете запитати", "Discussion_target_channel_prefix": "Ви створили обговорення в", "Discussion_title": "Створити нове обговорення", + "discussion-created": "__message__", "Discussions": "Обговорення", "Display_chat_permissions": "Відображення дозволів чату", "Display_offline_form": "Відобразити офлайн-форму", @@ -2191,6 +2192,7 @@ "Me": "Я", "Media": "Медіа", "Medium": "Середній", + "Members": "Учасники", "Members_List": "Список учасників", "mention-all": "Згадати все", "mention-all_description": "Дозвіл на використання @all згадки", @@ -2849,6 +2851,7 @@ "Show_Setup_Wizard": "Показати майстер налаштування", "Show_the_keyboard_shortcut_list": "Показати список комбінацій клавіш", "Showing_archived_results": "

      Відображення %s заархівовані результати

      ", + "Showing_online_users": null, "Showing_results": "

      Показано результатів %s

      ", "Sidebar": "Бічна панель", "Sidebar_list_mode": "Режим списку каналів бічної панелі", @@ -3262,6 +3265,7 @@ "Users_in_role": "Користувачі з роллю", "Uses": "Використовує", "UTF8_Names_Slugify": "UTF8 Імена Slugify", + "Videocall_enabled": "Відеодзвінок увімкнено", "Validate_email_address": "Підтвердити адресу електронної пошти", "Verification": "Верифікація", "Verification_Description": "Ви можете використовувати наступні заповнювачі:
      • [Verification_Url] для URL-адреси для підтвердження.
      • [ім'я], [ім'я-псевдоніма], [Lname] для повного імені користувача, прізвища або прізвища, відповідно.
      • [email] для електронної пошти користувача.
      • [Назва сайту] і [Site_URL] для імені додатка та URL-адреси, відповідно.
      ", @@ -3277,7 +3281,6 @@ "Video_Conference": "Відеоконференція", "Video_message": "Відео повідомлення", "Videocall_declined": "Відеодзвінок відхилено.", - "Videocall_enabled": "Відеодзвінок увімкнено", "Videos": "Відео", "View_All": "Показати все", "View_Logs": "Перегляд журналів", @@ -3416,4 +3419,4 @@ "Your_server_link": "Посилання на Ваш сервер", "Your_temporary_password_is_password": "Ваш тимчасовий пароль [password].", "Your_workspace_is_ready": "Ваш робочий простір готовий до використання 🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/vi-VN.i18n.json b/packages/rocketchat-i18n/i18n/vi-VN.i18n.json index ab283e8a0a5ec..6ed3e62af3b66 100644 --- a/packages/rocketchat-i18n/i18n/vi-VN.i18n.json +++ b/packages/rocketchat-i18n/i18n/vi-VN.i18n.json @@ -546,6 +546,7 @@ "Continuous_sound_notifications_for_new_livechat_room": "Thông báo âm thanh liên tục cho phòng livechat mới", "Conversation": "Cuộc hội thoại", "Conversation_closed": "Trò chuyện đóng lại: __comment__.", + "Conversation_finished": "Cuộc hội thoại đã kết thúc", "Conversation_finished_message": "Tin nhắn hoàn thành cuộc trò chuyện", "conversation_with_s": "cuộc trò chuyện với %s", "Convert_Ascii_Emojis": "Chuyển đổi ASCII sang Emoji", @@ -2675,6 +2676,7 @@ "Users_added": "Người dùng đã được thêm vào", "Users_in_role": "Người dùng có vai trò", "UTF8_Names_Slugify": "UTF8 Tên Slugify", + "Videocall_enabled": "Bật cuộc gọi điện video", "Validate_email_address": "Xác nhận Địa chỉ Email", "Verification": "Xác minh", "Verification_Description": "Bạn có thể sử dụng các placeholder sau:
      • [Verification_Url] cho URL xác minh.
      • [name], [fname], [lname] tương ứng cho tên, họ hoặc họ của người dùng, tương ứng.
      • [email] cho email của người dùng.
      • [Site_Name] và [Site_URL] cho Tên Ứng dụng và URL tương ứng.
      ", @@ -2689,7 +2691,6 @@ "Video_Conference": "Hội nghị Video", "Video_message": "Tin nhắn video", "Videocall_declined": "Cuộc gọi video bị Từ chối.", - "Videocall_enabled": "Bật cuộc gọi điện video", "View_All": "Xem tất cả thành viên", "View_Logs": "Xem các bản ghi", "View_mode": "Chế độ xem", diff --git a/packages/rocketchat-i18n/i18n/zh-HK.i18n.json b/packages/rocketchat-i18n/i18n/zh-HK.i18n.json index d42251b9dd472..5e1669a48b8b9 100644 --- a/packages/rocketchat-i18n/i18n/zh-HK.i18n.json +++ b/packages/rocketchat-i18n/i18n/zh-HK.i18n.json @@ -576,6 +576,7 @@ "Continuous_sound_notifications_for_new_livechat_room": "新的即时聊天室的连续声音通知", "Conversation": "会话", "Conversation_closed": "对话已关闭:__comment__。", + "Conversation_finished": "對話已結束", "Conversation_finished_message": "对话完成的消息", "conversation_with_s": "与%s的对话", "Convert_Ascii_Emojis": "自动识别文字中的表情", @@ -1705,6 +1706,7 @@ "Max_length_is": "最大长度是%s", "Media": "媒体", "Medium": "中", + "Members": "成员", "Members_List": "成员列表", "mention-all": "提及所有", "mention-all_description": "允许使用@all提到", @@ -2322,6 +2324,7 @@ "Show_Setup_Wizard": "显示安装向导", "Show_the_keyboard_shortcut_list": "显示键盘快捷键列表", "Showing_archived_results": "

      显示%s已存档结果

      ", + "Showing_online_users": null, "Showing_results": "

      显示%s条结果

      ", "Sidebar": "侧边栏", "Sidebar_list_mode": "边栏频道列表模式", @@ -2698,6 +2701,7 @@ "Users_added": "用户已被添加", "Users_in_role": "角色中的用户", "UTF8_Names_Slugify": "UTF8命名Slugify", + "Videocall_enabled": "视频通话已启用", "Validate_email_address": "验证电子邮件地址", "Verification": "验证", "Verification_Description": "您可以使用以下占位符:
      • [Verification_Url]获取验证网址。
      • [姓名],[fname],[lname]分别代表用户的全名,名字或姓氏。用户的电子邮件为
      • [email]。分别为应用程序名称和URL分别为
      • [Site_Name]和[Site_URL]。
      ", @@ -2712,7 +2716,6 @@ "Video_Conference": "视频会议", "Video_message": "视频消息", "Videocall_declined": "视频通话被拒绝。", - "Videocall_enabled": "视频通话已启用", "View_All": "查看全部", "View_Logs": "查看日志", "View_mode": "视图", @@ -2835,4 +2838,4 @@ "Your_push_was_sent_to_s_devices": "您的推送已发送到%s设备", "Your_server_link": "您的服务器链接", "Your_workspace_is_ready": "您的工作区已准备好使用🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/zh-TW.i18n.json b/packages/rocketchat-i18n/i18n/zh-TW.i18n.json index c5fef69215b2e..f443c80593313 100644 --- a/packages/rocketchat-i18n/i18n/zh-TW.i18n.json +++ b/packages/rocketchat-i18n/i18n/zh-TW.i18n.json @@ -3028,7 +3028,7 @@ "Organization_Type": "組織類型", "Original": "原始的", "OS_Arch": "作業系統架構", - "OS_Cpus": "作業系統處理器數量", + "OS_Cpus": "處理器數量", "OS_Freemem": "作業系統可用記憶體", "OS_Loadavg": "作業系統平均負載", "OS_Platform": "作業系統平台", @@ -4172,6 +4172,7 @@ "Uses": "使用次數", "Uses_left": "剩使用次數", "UTF8_Names_Slugify": "UTF8 名稱 Slugify", + "Videocall_enabled": "視訊通話已啟用", "Validate_email_address": "驗證電子郵件地址", "Validation": "驗證", "Value_messages": "__value__條訊息", @@ -4193,7 +4194,6 @@ "Video_Conference": "多人視訊", "Video_message": "影音訊息", "Videocall_declined": "視訊通話被拒絕。", - "Videocall_enabled": "視訊通話已啟用", "Videos": "影片", "View_All": "檢視全部成員", "View_Logs": "查看日誌", diff --git a/packages/rocketchat-i18n/i18n/zh.i18n.json b/packages/rocketchat-i18n/i18n/zh.i18n.json index 8ae0df53fe9ed..68c6bf878fb11 100644 --- a/packages/rocketchat-i18n/i18n/zh.i18n.json +++ b/packages/rocketchat-i18n/i18n/zh.i18n.json @@ -4026,6 +4026,7 @@ "Uses": "使用次数", "Uses_left": "剩余使用次数", "UTF8_Names_Slugify": "着重显示 UTF-8 名字", + "Videocall_enabled": "视频通话已启用", "Validate_email_address": "验证邮箱", "Validation": "验证", "Value_messages": "__value__ 消息", @@ -4047,7 +4048,6 @@ "Video_Conference": "视频会议", "Video_message": "视频消息", "Videocall_declined": "视频通话被拒绝。", - "Videocall_enabled": "视频通话已启用", "Videos": "视频", "View_All": "查看全部", "View_Logs": "查看日志", From 91bc4218e5f0c07de518a981190e42d407226999 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Mon, 29 Nov 2021 18:38:05 -0300 Subject: [PATCH 119/137] Regression: "When is the chat busier" and "Users by time of day" charts are not working (#23815) * Regression: Fix incorrect API path for Livechat calls (#23778) * Update ee/client/views/admin/engagementDashboard/users/UsersByTimeOfTheDaySection.tsx Co-authored-by: Tasso Evangelista Co-authored-by: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> Co-authored-by: Tasso Evangelista --- app/models/server/raw/Sessions.ts | 5 +++-- .../users/UsersByTimeOfTheDaySection.tsx | 8 +++++--- ee/server/lib/engagementDashboard/users.ts | 8 ++++---- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/app/models/server/raw/Sessions.ts b/app/models/server/raw/Sessions.ts index 2d24a98691a88..1a100bff01f4f 100644 --- a/app/models/server/raw/Sessions.ts +++ b/app/models/server/raw/Sessions.ts @@ -7,6 +7,7 @@ import type { IUser } from '../../../../definition/IUser'; type DestructuredDate = {year: number; month: number; day: number}; type DestructuredDateWithType = {year: number; month: number; day: number; type?: 'month' | 'week'}; type DestructuredRange = {start: DestructuredDate; end: DestructuredDate}; +type DateRange = {start: Date; end: Date}; type FullReturn = { year: number; month: number; day: number; data: ISession[] }; const matchBasedOnDate = (start: DestructuredDate, end: DestructuredDate): FilterQuery => { @@ -688,7 +689,7 @@ export class SessionsRaw extends BaseRaw { ]).toArray(); } - async getBusiestTimeWithinHoursPeriod({ start, end, groupSize }: DestructuredRange & { groupSize: number }): Promise<{ + async getBusiestTimeWithinHoursPeriod({ start, end, groupSize }: DateRange & { groupSize: number }): Promise<{ hour: number; users: number; }[]> { @@ -768,7 +769,7 @@ export class SessionsRaw extends BaseRaw { ]).toArray(); } - async getTotalOfSessionByHourAndDayBetweenDates({ start, end }: DestructuredRange): Promise<{ + async getTotalOfSessionByHourAndDayBetweenDates({ start, end }: DateRange): Promise<{ hour: number; day: number; month: number; diff --git a/ee/client/views/admin/engagementDashboard/users/UsersByTimeOfTheDaySection.tsx b/ee/client/views/admin/engagementDashboard/users/UsersByTimeOfTheDaySection.tsx index 933e4869cb422..68baf0619b8bd 100644 --- a/ee/client/views/admin/engagementDashboard/users/UsersByTimeOfTheDaySection.tsx +++ b/ee/client/views/admin/engagementDashboard/users/UsersByTimeOfTheDaySection.tsx @@ -42,9 +42,11 @@ const UsersByTimeOfTheDaySection = ({ : moment(data.end).diff(data.start, 'days') - 1, }, (_, i) => - moment(data.start) - .endOf('day') - .add(utc ? i : i + 1, 'days'), + utc + ? moment.utc(data.start).endOf('day').add(i, 'days') + : moment(data.start) + .endOf('day') + .add(i + 1, 'days'), ); const values = Array.from( diff --git a/ee/server/lib/engagementDashboard/users.ts b/ee/server/lib/engagementDashboard/users.ts index f5bde290d01bc..8e3faa3ff74ed 100644 --- a/ee/server/lib/engagementDashboard/users.ts +++ b/ee/server/lib/engagementDashboard/users.ts @@ -113,8 +113,8 @@ export const findBusiestsChatsInADayByHours = async ({ start }: { start: Date }) }[]; }> => ({ hours: await Sessions.getBusiestTimeWithinHoursPeriod({ - start: createDestructuredDate(moment(start).subtract(24, 'hours')), - end: createDestructuredDate(start), + start: moment(start).subtract(24, 'hours').toDate(), + end: start, groupSize: 2, }), }); @@ -143,7 +143,7 @@ export const findUserSessionsByHourWithinAWeek = async ({ start, end }: { start: }[]; }> => ({ week: await Sessions.getTotalOfSessionByHourAndDayBetweenDates({ - start: createDestructuredDate(start), - end: createDestructuredDate(end), + start, + end, }), }); From 44127c7d51c6e61341ce471eb2139606fdd2a6b7 Mon Sep 17 00:00:00 2001 From: pierre-lehnen-rc <55164754+pierre-lehnen-rc@users.noreply.github.com> Date: Mon, 29 Nov 2021 19:06:12 -0300 Subject: [PATCH 120/137] [FIX] LDAP users being disabled when an AD security policy is enabled (#23820) --- ee/server/lib/ldap/Manager.ts | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/ee/server/lib/ldap/Manager.ts b/ee/server/lib/ldap/Manager.ts index b146244a18507..07d4821ebd468 100644 --- a/ee/server/lib/ldap/Manager.ts +++ b/ee/server/lib/ldap/Manager.ts @@ -17,7 +17,7 @@ import { import { LDAPDataConverter } from '../../../../server/lib/ldap/DataConverter'; import { LDAPConnection } from '../../../../server/lib/ldap/Connection'; import { LDAPManager } from '../../../../server/lib/ldap/Manager'; -import { logger, searchLogger } from '../../../../server/lib/ldap/Logger'; +import { logger, searchLogger, mapLogger } from '../../../../server/lib/ldap/Logger'; import { templateVarHandler } from '../../../../app/utils/lib/templateVarHandler'; import { api } from '../../../../server/sdk/api'; import { addUserToRoom, removeUserFromRoom, createRoom } from '../../../../app/lib/server/functions'; @@ -409,36 +409,45 @@ export class LDAPEEManager extends LDAPManager { private static isUserDeactivated(ldapUser: ILDAPEntry): boolean { // Account locked by "Draft-behera-ldap-password-policy" if (ldapUser.pwdAccountLockedTime) { + mapLogger.debug('User account is locked by password policy (attribute pwdAccountLockedTime)'); return true; } // EDirectory: Account manually disabled by an admin if (ldapUser.loginDisabled) { + mapLogger.debug('User account was manually disabled by an admin (attribute loginDisabled)'); return true; } // Oracle: Account must not be allowed to authenticate if (ldapUser.orclIsEnabled && ldapUser.orclIsEnabled !== 'ENABLED') { + mapLogger.debug('User must not be allowed to authenticate (attribute orclIsEnabled)'); return true; } // Active Directory - Account locked automatically by security policies - if (ldapUser.lockoutTime) { - // Automatic unlock is disabled - if (!ldapUser.lockoutDuration) { - return true; - } + if (ldapUser.lockoutTime && ldapUser.lockoutTime !== '0') { + const lockoutTimeValue = Number(ldapUser.lockoutTime); + if (lockoutTimeValue && !isNaN(lockoutTimeValue)) { + // Automatic unlock is disabled + if (!ldapUser.lockoutDuration) { + mapLogger.debug('User account locked indefinitely by security policy (attribute lockoutTime)'); + return true; + } - const lockoutTime = new Date(Number(ldapUser.lockoutTime)); - lockoutTime.setMinutes(lockoutTime.getMinutes() + Number(ldapUser.lockoutDuration)); - // Account has not unlocked itself yet - if (lockoutTime.valueOf() > Date.now()) { - return true; + const lockoutTime = new Date(lockoutTimeValue); + lockoutTime.setMinutes(lockoutTime.getMinutes() + Number(ldapUser.lockoutDuration)); + // Account has not unlocked itself yet + if (lockoutTime.valueOf() > Date.now()) { + mapLogger.debug('User account locked temporarily by security policy (attribute lockoutTime)'); + return true; + } } } // Active Directory - Account disabled by an Admin if (ldapUser.userAccountControl && (ldapUser.userAccountControl & 2) === 2) { + mapLogger.debug('User account disabled by an admin (attribute userAccountControl)'); return true; } @@ -465,7 +474,7 @@ export class LDAPEEManager extends LDAPManager { } userData.deleted = deleted; - logger.debug(`${ deleted ? 'Deactivating' : 'Activating' } user ${ userData.name } (${ userData.username })`); + logger.info(`${ deleted ? 'Deactivating' : 'Activating' } user ${ userData.name } (${ userData.username })`); } public static copyCustomFields(ldapUser: ILDAPEntry, userData: IImportUser): void { From 3b27db434b65cc7185f366fca05208ee1cdf1774 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 30 Nov 2021 00:28:00 -0300 Subject: [PATCH 121/137] Regression: Add trash to raw models (#23774) Co-authored-by: Guilherme Gazzo --- .../server/streamer/permissions/index.ts | 6 +- app/file-upload/server/lib/FileUpload.js | 1 + app/models/server/raw/Banners.ts | 2 +- app/models/server/raw/BannersDismiss.ts | 2 +- app/models/server/raw/BaseRaw.ts | 251 ++++++++++++++---- app/models/server/raw/Nps.ts | 2 +- app/models/server/raw/NpsVote.ts | 2 +- app/models/server/raw/Roles.ts | 2 +- app/models/server/raw/Sessions.ts | 2 +- app/models/server/raw/Subscriptions.ts | 2 +- app/models/server/raw/Team.ts | 2 +- app/models/server/raw/TeamMember.ts | 2 +- app/models/server/raw/WebdavAccounts.ts | 2 +- definition/IRocketChatRecord.ts | 5 + definition/rest/v1/roles.ts | 3 +- server/publications/settings/index.ts | 4 +- 16 files changed, 222 insertions(+), 68 deletions(-) diff --git a/app/authorization/server/streamer/permissions/index.ts b/app/authorization/server/streamer/permissions/index.ts index 5494f8f1f78ec..fcc3bad0e34cd 100644 --- a/app/authorization/server/streamer/permissions/index.ts +++ b/app/authorization/server/streamer/permissions/index.ts @@ -10,7 +10,9 @@ Meteor.methods({ // TODO: should we return this for non logged users? // TODO: we could cache this collection - const records = await Permissions.find(updatedAt && { _updatedAt: { $gt: updatedAt } }).toArray(); + const records = await Permissions.find( + updatedAt && { _updatedAt: { $gt: updatedAt } }, + ).toArray(); if (updatedAt instanceof Date) { return { @@ -18,7 +20,7 @@ Meteor.methods({ remove: await Permissions.trashFindDeletedAfter( updatedAt, {}, - { fields: { _id: 1, _deletedAt: 1 } }, + { projection: { _id: 1, _deletedAt: 1 } }, ).toArray(), }; } diff --git a/app/file-upload/server/lib/FileUpload.js b/app/file-upload/server/lib/FileUpload.js index d361ed3e2294a..70ecac27f2037 100644 --- a/app/file-upload/server/lib/FileUpload.js +++ b/app/file-upload/server/lib/FileUpload.js @@ -569,6 +569,7 @@ export class FileUploadClass { } delete(fileId) { + // TODO: Remove this method if (this.store && this.store.delete) { this.store.delete(fileId); } diff --git a/app/models/server/raw/Banners.ts b/app/models/server/raw/Banners.ts index 301ea579bc008..a414332e2fa95 100644 --- a/app/models/server/raw/Banners.ts +++ b/app/models/server/raw/Banners.ts @@ -7,7 +7,7 @@ type T = IBanner; export class BannersRaw extends BaseRaw { constructor( public readonly col: Collection, - public readonly trash?: Collection, + trash?: Collection, ) { super(col, trash); diff --git a/app/models/server/raw/BannersDismiss.ts b/app/models/server/raw/BannersDismiss.ts index bea87b6876113..6c4e69f20b969 100644 --- a/app/models/server/raw/BannersDismiss.ts +++ b/app/models/server/raw/BannersDismiss.ts @@ -6,7 +6,7 @@ import { BaseRaw } from './BaseRaw'; export class BannersDismissRaw extends BaseRaw { constructor( public readonly col: Collection, - public readonly trash?: Collection, + trash?: Collection, ) { super(col, trash); diff --git a/app/models/server/raw/BaseRaw.ts b/app/models/server/raw/BaseRaw.ts index 1385bf890c6f4..47fd8bbdbdd17 100644 --- a/app/models/server/raw/BaseRaw.ts +++ b/app/models/server/raw/BaseRaw.ts @@ -23,11 +23,13 @@ import { WriteOpResult, } from 'mongodb'; +import { + IRocketChatRecord, + RocketChatRecordDeleted, +} from '../../../../definition/IRocketChatRecord'; import { setUpdatedAt } from '../lib/setUpdatedAt'; -export { - IndexSpecification, -} from 'mongodb'; +export { IndexSpecification } from 'mongodb'; // [extracted from @types/mongo] TypeScript Omit (Exclude to be specific) does not work for objects with an "any" indexed type, and breaks discriminated unions type EnhancedOmit = string | number extends keyof T @@ -47,20 +49,27 @@ type ExtractIdType = TSchema extends { _id: infer U } // user has defin export type ModelOptionalId = EnhancedOmit & { _id?: ExtractIdType }; // InsertionModel forces both _id and _updatedAt to be optional, regardless of how they are declared in T -export type InsertionModel = EnhancedOmit, '_updatedAt'> & { _updatedAt?: Date }; +export type InsertionModel = EnhancedOmit, '_updatedAt'> & { + _updatedAt?: Date; +}; export interface IBaseRaw { col: Collection; } const baseName = 'rocketchat_'; -const isWithoutProjection = (props: T): props is WithoutProjection => !('projection' in props) && !('fields' in props); type DefaultFields = Record | Record | void; -type ResultFields = Defaults extends void ? Base : Defaults[keyof Defaults] extends 1 ? Pick : Omit; +type ResultFields = Defaults extends void + ? Base + : Defaults[keyof Defaults] extends 1 + ? Pick + : Omit; const warnFields = process.env.NODE_ENV !== 'production' - ? (...rest: any): void => { console.warn(...rest, new Error().stack); } + ? (...rest: any): void => { + console.warn(...rest, new Error().stack); + } : new Function(); export class BaseRaw = undefined> implements IBaseRaw { @@ -72,12 +81,15 @@ export class BaseRaw = undefined> implements IBase private preventSetUpdatedAt: boolean; + public readonly trash?: Collection>; + constructor( public readonly col: Collection, - public readonly trash?: Collection, + trash?: Collection, options?: { preventSetUpdatedAt?: boolean }, ) { this.name = this.col.collectionName.replace(baseName, ''); + this.trash = trash as unknown as Collection>; if (this.indexes?.length) { this.col.createIndexes(this.indexes); @@ -105,13 +117,19 @@ export class BaseRaw = undefined> implements IBase }; } - private ensureDefaultFields(options?: undefined): C extends void ? undefined : WithoutProjection>; + private ensureDefaultFields( + options?: undefined, + ): C extends void ? undefined : WithoutProjection>; - private ensureDefaultFields(options: WithoutProjection>): WithoutProjection>; + private ensureDefaultFields( + options: WithoutProjection>, + ): WithoutProjection>; private ensureDefaultFields

      (options: FindOneOptions

      ): FindOneOptions

      ; - private ensureDefaultFields

      (options?: any): FindOneOptions

      | undefined | WithoutProjection> { + private ensureDefaultFields

      ( + options?: any, + ): FindOneOptions

      | undefined | WithoutProjection> { if (this.defaultFields === undefined) { return options; } @@ -119,7 +137,7 @@ export class BaseRaw = undefined> implements IBase const { fields: deprecatedFields, projection, ...rest } = options || {}; if (deprecatedFields) { - warnFields('Using \'fields\' in models is deprecated.', options); + warnFields("Using 'fields' in models is deprecated.", options); } const fields = { ...deprecatedFields, ...projection }; @@ -131,13 +149,23 @@ export class BaseRaw = undefined> implements IBase }; } - public findOneAndUpdate(query: FilterQuery, update: UpdateQuery | T, options?: FindOneAndUpdateOption): Promise> { + public findOneAndUpdate( + query: FilterQuery, + update: UpdateQuery | T, + options?: FindOneAndUpdateOption, + ): Promise> { return this.col.findOneAndUpdate(query, update, options); } - async findOneById(_id: string, options?: WithoutProjection> | undefined): Promise; + async findOneById( + _id: string, + options?: WithoutProjection> | undefined, + ): Promise; - async findOneById

      (_id: string, options: FindOneOptions

      ): Promise

      ; + async findOneById

      ( + _id: string, + options: FindOneOptions

      , + ): Promise

      ; async findOneById

      (_id: string, options?: any): Promise { const query = { _id } as FilterQuery; @@ -147,12 +175,18 @@ export class BaseRaw = undefined> implements IBase async findOne(query?: FilterQuery | string, options?: undefined): Promise; - async findOne(query: FilterQuery | string, options: WithoutProjection>): Promise; + async findOne( + query: FilterQuery | string, + options: WithoutProjection>, + ): Promise; - async findOne

      (query: FilterQuery | string, options: FindOneOptions

      ): Promise

      ; + async findOne

      ( + query: FilterQuery | string, + options: FindOneOptions

      , + ): Promise

      ; async findOne

      (query: FilterQuery | string = {}, options?: any): Promise { - const q = typeof query === 'string' ? { _id: query } as FilterQuery : query; + const q = typeof query === 'string' ? ({ _id: query } as FilterQuery) : query; const optionsDef = this.doNotMixInclusionAndExclusionFields(options); return this.col.findOne(q, optionsDef); @@ -164,7 +198,10 @@ export class BaseRaw = undefined> implements IBase find(query?: FilterQuery): Cursor>; - find(query: FilterQuery, options: WithoutProjection>): Cursor>; + find( + query: FilterQuery, + options: WithoutProjection>, + ): Cursor>; find

      (query: FilterQuery, options: FindOneOptions

      ): Cursor

      ; @@ -173,22 +210,37 @@ export class BaseRaw = undefined> implements IBase return this.col.find(query, optionsDef); } - update(filter: FilterQuery, update: UpdateQuery | Partial, options?: UpdateOneOptions & { multi?: boolean }): Promise { + update( + filter: FilterQuery, + update: UpdateQuery | Partial, + options?: UpdateOneOptions & { multi?: boolean }, + ): Promise { this.setUpdatedAt(update); return this.col.update(filter, update, options); } - updateOne(filter: FilterQuery, update: UpdateQuery | Partial, options?: UpdateOneOptions & { multi?: boolean }): Promise { + updateOne( + filter: FilterQuery, + update: UpdateQuery | Partial, + options?: UpdateOneOptions & { multi?: boolean }, + ): Promise { this.setUpdatedAt(update); return this.col.updateOne(filter, update, options); } - updateMany(filter: FilterQuery, update: UpdateQuery | Partial, options?: UpdateManyOptions): Promise { + updateMany( + filter: FilterQuery, + update: UpdateQuery | Partial, + options?: UpdateManyOptions, + ): Promise { this.setUpdatedAt(update); return this.col.updateMany(filter, update, options); } - insertMany(docs: Array>, options?: CollectionInsertOneOptions): Promise>> { + insertMany( + docs: Array>, + options?: CollectionInsertOneOptions, + ): Promise>> { docs = docs.map((doc) => { if (!doc._id || typeof doc._id !== 'string') { const oid = new ObjectID(); @@ -202,7 +254,10 @@ export class BaseRaw = undefined> implements IBase return this.col.insertMany(docs as unknown as Array>, options); } - insertOne(doc: InsertionModel, options?: CollectionInsertOneOptions): Promise>> { + insertOne( + doc: InsertionModel, + options?: CollectionInsertOneOptions, + ): Promise>> { if (!doc._id || typeof doc._id !== 'string') { const oid = new ObjectID(); doc = { _id: oid.toHexString(), ...doc }; @@ -215,56 +270,126 @@ export class BaseRaw = undefined> implements IBase } removeById(_id: string): Promise { - const query: object = { _id }; - return this.col.deleteOne(query); + return this.deleteOne({ _id } as FilterQuery); } - deleteOne(filter: FilterQuery, options?: CommonOptions & { bypassDocumentValidation?: boolean }): Promise { + async deleteOne( + filter: FilterQuery, + options?: CommonOptions & { bypassDocumentValidation?: boolean }, + ): Promise { + if (!this.trash) { + return this.col.deleteOne(filter, options); + } + + const doc = (await this.findOne(filter)) as unknown as (IRocketChatRecord & T) | undefined; + + if (doc) { + const { _id, ...record } = doc; + + const trash = { + ...record, + + _deletedAt: new Date(), + __collection__: this.name, + } as RocketChatRecordDeleted; + + // since the operation is not atomic, we need to make sure that the record is not already deleted/inserted + await this.trash?.updateOne( + { _id } as FilterQuery>, + { $set: trash }, + { + upsert: true, + }, + ); + } + return this.col.deleteOne(filter, options); } - deleteMany(filter: FilterQuery, options?: CommonOptions): Promise { - return this.col.deleteMany(filter, options); - } + async deleteMany( + filter: FilterQuery, + options?: CommonOptions, + ): Promise { + if (!this.trash) { + return this.col.deleteMany(filter, options); + } + const cursor = this.find(filter); + + const ids: string[] = []; + for await (const doc of cursor) { + const { _id, ...record } = doc as unknown as IRocketChatRecord & T; + + const trash = { + ...record, + + _deletedAt: new Date(), + __collection__: this.name, + } as RocketChatRecordDeleted; + + ids.push(_id); + + // since the operation is not atomic, we need to make sure that the record is not already deleted/inserted + await this.trash?.updateOne( + { _id } as FilterQuery>, + { $set: trash }, + { + upsert: true, + }, + ); + } + + return this.col.deleteMany({ _id: { $in: ids } } as unknown as FilterQuery, options); + } // Trash - trashFind

      (query: FilterQuery, options: FindOneOptions

      ): Cursor

      | undefined { + trashFind

      >( + query: FilterQuery>, + options: FindOneOptions

      ? RocketChatRecordDeleted : P>, + ): Cursor> | undefined { if (!this.trash) { return undefined; } const { trash } = this; - return trash.find({ - __collection__: this.name, - ...query, - }, options); + return trash.find( + { + __collection__: this.name, + ...query, + }, + options, + ); } - - trashFindOneById(_id: string): Promise; - - trashFindOneById(_id: string, options: WithoutProjection>): Promise; - - trashFindOneById

      (_id: string, options: FindOneOptions

      ): Promise

      ; - - async trashFindOneById

      (_id: string, options?: undefined | WithoutProjection> | FindOneOptions

      ): Promise { + trashFindOneById(_id: string): Promise | null>; + + trashFindOneById( + _id: string, + options: WithoutProjection>, + ): Promise> | null>; + + trashFindOneById

      ( + _id: string, + options: FindOneOptions

      ? RocketChatRecordDeleted : P>, + ): Promise

      ; + + async trashFindOneById

      >( + _id: string, + options?: + | undefined + | WithoutProjection> + | FindOneOptions

      ? RocketChatRecordDeleted : P>, + ): Promise | null> { const query = { _id, __collection__: this.name, - } as FilterQuery; + } as FilterQuery>; if (!this.trash) { return null; } const { trash } = this; - if (options === undefined) { - return trash.findOne(query); - } - if (isWithoutProjection(options)) { - return trash.findOne(query, options); - } return trash.findOne(query, options); } @@ -275,14 +400,34 @@ export class BaseRaw = undefined> implements IBase setUpdatedAt(record); } - trashFindDeletedAfter

      (deletedAt: Date, query: FilterQuery = {}, options?: any): Cursor { + trashFindDeletedAfter(deletedAt: Date): Cursor>; + + trashFindDeletedAfter( + deletedAt: Date, + query: FilterQuery>, + options: WithoutProjection>, + ): Cursor>; + + trashFindDeletedAfter

      >( + deletedAt: Date, + query: FilterQuery

      , + options: FindOneOptions

      ? RocketChatRecordDeleted : P>, + ): Cursor>; + + trashFindDeletedAfter

      >( + deletedAt: Date, + query?: FilterQuery>, + options?: + | WithoutProjection> + | FindOneOptions

      ? RocketChatRecordDeleted : P>, + ): Cursor> { const q = { __collection__: this.name, _deletedAt: { $gt: deletedAt, }, ...query, - } as FilterQuery; + } as FilterQuery>; const { trash } = this; @@ -290,6 +435,6 @@ export class BaseRaw = undefined> implements IBase throw new Error('Trash is not enabled for this collection'); } - return trash.find(q, options); + return trash.find(q, options as any); } } diff --git a/app/models/server/raw/Nps.ts b/app/models/server/raw/Nps.ts index 715628e7146e6..f77e0b61bde34 100644 --- a/app/models/server/raw/Nps.ts +++ b/app/models/server/raw/Nps.ts @@ -7,7 +7,7 @@ type T = INps; export class NpsRaw extends BaseRaw { constructor( public readonly col: Collection, - public readonly trash?: Collection, + trash?: Collection, ) { super(col, trash); diff --git a/app/models/server/raw/NpsVote.ts b/app/models/server/raw/NpsVote.ts index f6ebb6dcc34eb..e215e1a925306 100644 --- a/app/models/server/raw/NpsVote.ts +++ b/app/models/server/raw/NpsVote.ts @@ -7,7 +7,7 @@ type T = INpsVote; export class NpsVoteRaw extends BaseRaw { constructor( public readonly col: Collection, - public readonly trash?: Collection, + trash?: Collection, ) { super(col, trash); diff --git a/app/models/server/raw/Roles.ts b/app/models/server/raw/Roles.ts index c858d2445ce15..3d776945677ec 100644 --- a/app/models/server/raw/Roles.ts +++ b/app/models/server/raw/Roles.ts @@ -12,7 +12,7 @@ type ScopedModelRoles = { export class RolesRaw extends BaseRaw { constructor(public readonly col: Collection, - private readonly models: ScopedModelRoles, public readonly trash?: Collection) { + private readonly models: ScopedModelRoles, trash?: Collection) { super(col, trash); } diff --git a/app/models/server/raw/Sessions.ts b/app/models/server/raw/Sessions.ts index 1a100bff01f4f..64d0c5cca7ff0 100644 --- a/app/models/server/raw/Sessions.ts +++ b/app/models/server/raw/Sessions.ts @@ -597,7 +597,7 @@ export class SessionsRaw extends BaseRaw { constructor( public readonly col: Collection, public readonly colSecondary: Collection, - public readonly trash?: Collection, + trash?: Collection, ) { super(col, trash); diff --git a/app/models/server/raw/Subscriptions.ts b/app/models/server/raw/Subscriptions.ts index af75749ab8284..2877c147312e1 100644 --- a/app/models/server/raw/Subscriptions.ts +++ b/app/models/server/raw/Subscriptions.ts @@ -11,7 +11,7 @@ type T = ISubscription; export class SubscriptionsRaw extends BaseRaw { constructor(public readonly col: Collection, private readonly models: { Users: UsersRaw }, - public readonly trash?: Collection) { + trash?: Collection) { super(col, trash); } diff --git a/app/models/server/raw/Team.ts b/app/models/server/raw/Team.ts index 8c8d51b724fc5..f8d1874797cb0 100644 --- a/app/models/server/raw/Team.ts +++ b/app/models/server/raw/Team.ts @@ -6,7 +6,7 @@ import { ITeam, TEAM_TYPE } from '../../../../definition/ITeam'; export class TeamRaw extends BaseRaw { constructor( public readonly col: Collection, - public readonly trash?: Collection, + trash?: Collection, ) { super(col, trash); diff --git a/app/models/server/raw/TeamMember.ts b/app/models/server/raw/TeamMember.ts index e411980a8627b..c65fcea6e533e 100644 --- a/app/models/server/raw/TeamMember.ts +++ b/app/models/server/raw/TeamMember.ts @@ -8,7 +8,7 @@ type T = ITeamMember; export class TeamMemberRaw extends BaseRaw { constructor( public readonly col: Collection, - public readonly trash?: Collection, + trash?: Collection, ) { super(col, trash); diff --git a/app/models/server/raw/WebdavAccounts.ts b/app/models/server/raw/WebdavAccounts.ts index c189cb7e3799b..1a7fea7114e6f 100644 --- a/app/models/server/raw/WebdavAccounts.ts +++ b/app/models/server/raw/WebdavAccounts.ts @@ -12,7 +12,7 @@ type T = IWebdavAccount; export class WebdavAccountsRaw extends BaseRaw { constructor( public readonly col: Collection, - public readonly trash?: Collection, + trash?: Collection, ) { super(col, trash); diff --git a/definition/IRocketChatRecord.ts b/definition/IRocketChatRecord.ts index 0dc3f5f511667..12a3ae9eb5d31 100644 --- a/definition/IRocketChatRecord.ts +++ b/definition/IRocketChatRecord.ts @@ -2,3 +2,8 @@ export interface IRocketChatRecord { _id: string; _updatedAt: Date; } + +export type RocketChatRecordDeleted = T & IRocketChatRecord & { + _deletedAt: Date; + __collection__: string; +}; diff --git a/definition/rest/v1/roles.ts b/definition/rest/v1/roles.ts index 746d09d824559..ff3bee7728906 100644 --- a/definition/rest/v1/roles.ts +++ b/definition/rest/v1/roles.ts @@ -1,5 +1,6 @@ import Ajv, { JSONSchemaType } from 'ajv'; +import { RocketChatRecordDeleted } from '../../IRocketChatRecord'; import { IRole, IUser } from '../../IUser'; const ajv = new Ajv(); @@ -150,7 +151,7 @@ export type RolesEndpoints = { GET: (params: RoleSyncProps) => { roles: { update: IRole[]; - remove: IRole[]; + remove: RocketChatRecordDeleted[]; }; }; }; diff --git a/server/publications/settings/index.ts b/server/publications/settings/index.ts index 2c54b6fc0eff3..bd2af883b14c6 100644 --- a/server/publications/settings/index.ts +++ b/server/publications/settings/index.ts @@ -20,7 +20,7 @@ Meteor.methods({ }, public: true, }, { - fields: { + projection: { _id: 1, _deletedAt: 1, }, @@ -69,7 +69,7 @@ Meteor.methods({ $ne: true, }, }, { - fields: { + projection: { _id: 1, _deletedAt: 1, }, From 32b3b89cd54fd28dd8ced99f623d392d98d20ee9 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 30 Nov 2021 00:28:58 -0300 Subject: [PATCH 122/137] Bump version to 4.2.0-rc.4 --- .docker/Dockerfile.rhel | 2 +- .github/history.json | 74 ++++++++++++++++++++++++++ .snapcraft/resources/prepareRocketChat | 2 +- .snapcraft/snap/snapcraft.yaml | 2 +- HISTORY.md | 37 +++++++++++++ app/utils/rocketchat.info | 2 +- package-lock.json | 2 +- package.json | 2 +- 8 files changed, 117 insertions(+), 6 deletions(-) diff --git a/.docker/Dockerfile.rhel b/.docker/Dockerfile.rhel index 74895d7ba08bd..8a72483de7b0a 100644 --- a/.docker/Dockerfile.rhel +++ b/.docker/Dockerfile.rhel @@ -1,6 +1,6 @@ FROM registry.access.redhat.com/ubi8/nodejs-12 -ENV RC_VERSION 4.2.0-rc.3 +ENV RC_VERSION 4.2.0-rc.4 MAINTAINER buildmaster@rocket.chat diff --git a/.github/history.json b/.github/history.json index ef4ced39ce0d7..3967e08585702 100644 --- a/.github/history.json +++ b/.github/history.json @@ -67774,6 +67774,80 @@ ] } ] + }, + "4.2.0-rc.4": { + "node_version": "12.22.1", + "npm_version": "6.14.1", + "apps_engine_version": "1.28.1", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "23774", + "title": "Regression: Add trash to raw models", + "userLogin": "sampaiodiego", + "milestone": "4.2.0", + "contributors": [ + "sampaiodiego", + "ggazzo" + ] + }, + { + "pr": "23820", + "title": "[FIX] LDAP users being disabled when an AD security policy is enabled", + "userLogin": "pierre-lehnen-rc", + "milestone": "4.2.0", + "contributors": [ + "pierre-lehnen-rc" + ] + }, + { + "pr": "23815", + "title": "Regression: \"When is the chat busier\" and \"Users by time of day\" charts are not working", + "userLogin": "matheusbsilva137", + "description": "- Fix \"When is the chat busier\" (Hours) and \"Users by time of day\" charts, which weren't displaying any data;", + "milestone": "4.2.0", + "contributors": [ + "murtaza98", + "matheusbsilva137", + "web-flow" + ] + }, + { + "pr": "23812", + "title": "i18n: Language update from LingoHub 🤖 on 2021-11-29Z", + "userLogin": "lingohub[bot]", + "milestone": "4.2.0", + "contributors": [ + null, + "sampaiodiego" + ] + }, + { + "pr": "23813", + "title": "Regression: Mark Livechat WebRTC video calling as alpha", + "userLogin": "murtaza98", + "description": "![image](https://user-images.githubusercontent.com/34130764/143832378-82b99a72-23e8-4115-8b28-a0d210de598b.png)", + "milestone": "4.2.0", + "contributors": [ + "murtaza98" + ] + }, + { + "pr": "23803", + "title": "Regression: Current Chats not Filtering", + "userLogin": "MartinSchoeler", + "milestone": "4.2.0", + "contributors": [ + "MartinSchoeler" + ] + } + ] } } } \ No newline at end of file diff --git a/.snapcraft/resources/prepareRocketChat b/.snapcraft/resources/prepareRocketChat index a49e9fe82a72e..24c2726b35da1 100755 --- a/.snapcraft/resources/prepareRocketChat +++ b/.snapcraft/resources/prepareRocketChat @@ -1,6 +1,6 @@ #!/bin/bash -curl -SLf "https://releases.rocket.chat/4.2.0-rc.3/download/" -o rocket.chat.tgz +curl -SLf "https://releases.rocket.chat/4.2.0-rc.4/download/" -o rocket.chat.tgz tar xf rocket.chat.tgz --strip 1 diff --git a/.snapcraft/snap/snapcraft.yaml b/.snapcraft/snap/snapcraft.yaml index b7d88cd083b83..ff9fe43e7005c 100644 --- a/.snapcraft/snap/snapcraft.yaml +++ b/.snapcraft/snap/snapcraft.yaml @@ -7,7 +7,7 @@ # 5. `snapcraft snap` name: rocketchat-server -version: 4.2.0-rc.3 +version: 4.2.0-rc.4 summary: Rocket.Chat server description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ confinement: strict diff --git a/HISTORY.md b/HISTORY.md index b28966703db5d..6fff18e6f3f82 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,43 @@ # 4.2.0 (Under Release Candidate Process) +## 4.2.0-rc.4 +`2021-11-30 · 1 🐛 · 5 🔍 · 6 👩‍💻👨‍💻` + +### 🐛 Bug fixes + + +- LDAP users being disabled when an AD security policy is enabled ([#23820](https://github.com/RocketChat/Rocket.Chat/pull/23820)) + +

      +🔍 Minor changes + + +- i18n: Language update from LingoHub 🤖 on 2021-11-29Z ([#23812](https://github.com/RocketChat/Rocket.Chat/pull/23812)) + +- Regression: "When is the chat busier" and "Users by time of day" charts are not working ([#23815](https://github.com/RocketChat/Rocket.Chat/pull/23815)) + + - Fix "When is the chat busier" (Hours) and "Users by time of day" charts, which weren't displaying any data; + +- Regression: Add trash to raw models ([#23774](https://github.com/RocketChat/Rocket.Chat/pull/23774)) + +- Regression: Current Chats not Filtering ([#23803](https://github.com/RocketChat/Rocket.Chat/pull/23803)) + +- Regression: Mark Livechat WebRTC video calling as alpha ([#23813](https://github.com/RocketChat/Rocket.Chat/pull/23813)) + + ![image](https://user-images.githubusercontent.com/34130764/143832378-82b99a72-23e8-4115-8b28-a0d210de598b.png) + +
      + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@MartinSchoeler](https://github.com/MartinSchoeler) +- [@ggazzo](https://github.com/ggazzo) +- [@matheusbsilva137](https://github.com/matheusbsilva137) +- [@murtaza98](https://github.com/murtaza98) +- [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) +- [@sampaiodiego](https://github.com/sampaiodiego) + ## 4.2.0-rc.3 `2021-11-26 · 1 🔍 · 1 👩‍💻👨‍💻` diff --git a/app/utils/rocketchat.info b/app/utils/rocketchat.info index 3efcecab6bdc3..c0fe666bd5db4 100644 --- a/app/utils/rocketchat.info +++ b/app/utils/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "4.2.0-rc.3" + "version": "4.2.0-rc.4" } diff --git a/package-lock.json b/package-lock.json index a0be360ad8185..977df71843c74 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "Rocket.Chat", - "version": "4.2.0-rc.3", + "version": "4.2.0-rc.4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 58017e67808e7..70268db8d1e62 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Rocket.Chat", "description": "The Ultimate Open Source WebChat Platform", - "version": "4.2.0-rc.3", + "version": "4.2.0-rc.4", "author": { "name": "Rocket.Chat", "url": "https://rocket.chat/" From 6be430d1cf7e48ad562099a081daea24212a9ca4 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 30 Nov 2021 00:31:12 -0300 Subject: [PATCH 123/137] Bump version to 4.2.0 --- .docker/Dockerfile.rhel | 2 +- .github/history.json | 13 +++ .snapcraft/resources/prepareRocketChat | 2 +- .snapcraft/snap/snapcraft.yaml | 2 +- HISTORY.md | 126 +++++++------------------ app/utils/rocketchat.info | 2 +- package-lock.json | 2 +- package.json | 2 +- 8 files changed, 53 insertions(+), 98 deletions(-) diff --git a/.docker/Dockerfile.rhel b/.docker/Dockerfile.rhel index 8a72483de7b0a..1e0f562724d76 100644 --- a/.docker/Dockerfile.rhel +++ b/.docker/Dockerfile.rhel @@ -1,6 +1,6 @@ FROM registry.access.redhat.com/ubi8/nodejs-12 -ENV RC_VERSION 4.2.0-rc.4 +ENV RC_VERSION 4.2.0 MAINTAINER buildmaster@rocket.chat diff --git a/.github/history.json b/.github/history.json index 3967e08585702..b441b4f94ab11 100644 --- a/.github/history.json +++ b/.github/history.json @@ -67848,6 +67848,19 @@ ] } ] + }, + "4.2.0": { + "node_version": "12.22.1", + "npm_version": "6.14.1", + "apps_engine_version": "1.28.1", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [] } } } \ No newline at end of file diff --git a/.snapcraft/resources/prepareRocketChat b/.snapcraft/resources/prepareRocketChat index 24c2726b35da1..c27dc0cb3b562 100755 --- a/.snapcraft/resources/prepareRocketChat +++ b/.snapcraft/resources/prepareRocketChat @@ -1,6 +1,6 @@ #!/bin/bash -curl -SLf "https://releases.rocket.chat/4.2.0-rc.4/download/" -o rocket.chat.tgz +curl -SLf "https://releases.rocket.chat/4.2.0/download/" -o rocket.chat.tgz tar xf rocket.chat.tgz --strip 1 diff --git a/.snapcraft/snap/snapcraft.yaml b/.snapcraft/snap/snapcraft.yaml index ff9fe43e7005c..226a11c4a7935 100644 --- a/.snapcraft/snap/snapcraft.yaml +++ b/.snapcraft/snap/snapcraft.yaml @@ -7,7 +7,7 @@ # 5. `snapcraft snap` name: rocketchat-server -version: 4.2.0-rc.4 +version: 4.2.0 summary: Rocket.Chat server description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ confinement: strict diff --git a/HISTORY.md b/HISTORY.md index 6fff18e6f3f82..453efbf632d33 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,97 +1,12 @@ -# 4.2.0 (Under Release Candidate Process) +# 4.2.0 +`2021-11-30 · 9 🎉 · 7 🚀 · 26 🐛 · 27 🔍 · 24 👩‍💻👨‍💻` -## 4.2.0-rc.4 -`2021-11-30 · 1 🐛 · 5 🔍 · 6 👩‍💻👨‍💻` - -### 🐛 Bug fixes - - -- LDAP users being disabled when an AD security policy is enabled ([#23820](https://github.com/RocketChat/Rocket.Chat/pull/23820)) - -
      -🔍 Minor changes - - -- i18n: Language update from LingoHub 🤖 on 2021-11-29Z ([#23812](https://github.com/RocketChat/Rocket.Chat/pull/23812)) - -- Regression: "When is the chat busier" and "Users by time of day" charts are not working ([#23815](https://github.com/RocketChat/Rocket.Chat/pull/23815)) - - - Fix "When is the chat busier" (Hours) and "Users by time of day" charts, which weren't displaying any data; - -- Regression: Add trash to raw models ([#23774](https://github.com/RocketChat/Rocket.Chat/pull/23774)) - -- Regression: Current Chats not Filtering ([#23803](https://github.com/RocketChat/Rocket.Chat/pull/23803)) - -- Regression: Mark Livechat WebRTC video calling as alpha ([#23813](https://github.com/RocketChat/Rocket.Chat/pull/23813)) - - ![image](https://user-images.githubusercontent.com/34130764/143832378-82b99a72-23e8-4115-8b28-a0d210de598b.png) - -
      - -### 👩‍💻👨‍💻 Core Team 🤓 - -- [@MartinSchoeler](https://github.com/MartinSchoeler) -- [@ggazzo](https://github.com/ggazzo) -- [@matheusbsilva137](https://github.com/matheusbsilva137) -- [@murtaza98](https://github.com/murtaza98) -- [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) -- [@sampaiodiego](https://github.com/sampaiodiego) - -## 4.2.0-rc.3 -`2021-11-26 · 1 🔍 · 1 👩‍💻👨‍💻` - -
      -🔍 Minor changes - - -- Regression: Add @rocket.chat/emitter to EE services ([#23802](https://github.com/RocketChat/Rocket.Chat/pull/23802)) - -
      - -### 👩‍💻👨‍💻 Core Team 🤓 - -- [@sampaiodiego](https://github.com/sampaiodiego) - -## 4.2.0-rc.2 -`2021-11-26 · 2 🔍 · 2 👩‍💻👨‍💻` - -
      -🔍 Minor changes - - -- Regression: Fix sort param on omnichannel endpoints ([#23789](https://github.com/RocketChat/Rocket.Chat/pull/23789)) - -- Regression: Include files on EE services build ([#23793](https://github.com/RocketChat/Rocket.Chat/pull/23793)) - -
      - -### 👩‍💻👨‍💻 Core Team 🤓 - -- [@KevLehman](https://github.com/KevLehman) -- [@sampaiodiego](https://github.com/sampaiodiego) - -## 4.2.0-rc.1 -`2021-11-23 · 2 🔍 · 3 👩‍💻👨‍💻` - -
      -🔍 Minor changes - - -- Regression: Fix incorrect API path for livechat calls ([#23778](https://github.com/RocketChat/Rocket.Chat/pull/23778)) - -- Regression: Fix LDAP sync route ([#23775](https://github.com/RocketChat/Rocket.Chat/pull/23775)) - -
      - -### 👩‍💻👨‍💻 Core Team 🤓 - -- [@ggazzo](https://github.com/ggazzo) -- [@murtaza98](https://github.com/murtaza98) -- [@sampaiodiego](https://github.com/sampaiodiego) - -## 4.2.0-rc.0 -`2021-11-23 · 9 🎉 · 7 🚀 · 25 🐛 · 17 🔍 · 23 👩‍💻👨‍💻` +### Engine versions +- Node: `12.22.1` +- NPM: `6.14.1` +- MongoDB: `3.6, 4.0, 4.2, 4.4, 5.0` +- Apps-Engine: `1.28.1` ### 🎉 New features @@ -215,6 +130,8 @@ - Fixed E2E default room settings not being honoured ([#23468](https://github.com/RocketChat/Rocket.Chat/pull/23468) by [@TheDigitalEagle](https://github.com/TheDigitalEagle)) +- LDAP users being disabled when an AD security policy is enabled ([#23820](https://github.com/RocketChat/Rocket.Chat/pull/23820)) + - LDAP users not being re-activated on login ([#23627](https://github.com/RocketChat/Rocket.Chat/pull/23627)) - Missing user roles in edit user tab ([#23734](https://github.com/RocketChat/Rocket.Chat/pull/23734)) @@ -309,14 +226,38 @@ - i18n: Language update from LingoHub 🤖 on 2021-11-01Z ([#23603](https://github.com/RocketChat/Rocket.Chat/pull/23603)) +- i18n: Language update from LingoHub 🤖 on 2021-11-29Z ([#23812](https://github.com/RocketChat/Rocket.Chat/pull/23812)) + - Merge master into develop & Set version to 4.2.0-develop ([#23586](https://github.com/RocketChat/Rocket.Chat/pull/23586)) - Regression: Units endpoint to TS ([#23757](https://github.com/RocketChat/Rocket.Chat/pull/23757)) +- Regression: "When is the chat busier" and "Users by time of day" charts are not working ([#23815](https://github.com/RocketChat/Rocket.Chat/pull/23815)) + + - Fix "When is the chat busier" (Hours) and "Users by time of day" charts, which weren't displaying any data; + +- Regression: Add @rocket.chat/emitter to EE services ([#23802](https://github.com/RocketChat/Rocket.Chat/pull/23802)) + +- Regression: Add trash to raw models ([#23774](https://github.com/RocketChat/Rocket.Chat/pull/23774)) + +- Regression: Current Chats not Filtering ([#23803](https://github.com/RocketChat/Rocket.Chat/pull/23803)) + +- Regression: Fix incorrect API path for livechat calls ([#23778](https://github.com/RocketChat/Rocket.Chat/pull/23778)) + +- Regression: Fix LDAP sync route ([#23775](https://github.com/RocketChat/Rocket.Chat/pull/23775)) + - Regression: Fix sendMessagesToAdmins not in Fiber ([#23770](https://github.com/RocketChat/Rocket.Chat/pull/23770)) +- Regression: Fix sort param on omnichannel endpoints ([#23789](https://github.com/RocketChat/Rocket.Chat/pull/23789)) + - Regression: Improve AggregationCursor types ([#23692](https://github.com/RocketChat/Rocket.Chat/pull/23692)) +- Regression: Include files on EE services build ([#23793](https://github.com/RocketChat/Rocket.Chat/pull/23793)) + +- Regression: Mark Livechat WebRTC video calling as alpha ([#23813](https://github.com/RocketChat/Rocket.Chat/pull/23813)) + + ![image](https://user-images.githubusercontent.com/34130764/143832378-82b99a72-23e8-4115-8b28-a0d210de598b.png) + ### 👩‍💻👨‍💻 Contributors 😍 @@ -332,6 +273,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 - [@KevLehman](https://github.com/KevLehman) +- [@MartinSchoeler](https://github.com/MartinSchoeler) - [@cauefcr](https://github.com/cauefcr) - [@d-gubert](https://github.com/d-gubert) - [@dougfabris](https://github.com/dougfabris) diff --git a/app/utils/rocketchat.info b/app/utils/rocketchat.info index c0fe666bd5db4..a0aa10e4c04b4 100644 --- a/app/utils/rocketchat.info +++ b/app/utils/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "4.2.0-rc.4" + "version": "4.2.0" } diff --git a/package-lock.json b/package-lock.json index 977df71843c74..ae0af47f05714 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "Rocket.Chat", - "version": "4.2.0-rc.4", + "version": "4.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 70268db8d1e62..deb4eb3ee8ad8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Rocket.Chat", "description": "The Ultimate Open Source WebChat Platform", - "version": "4.2.0-rc.4", + "version": "4.2.0", "author": { "name": "Rocket.Chat", "url": "https://rocket.chat/" From c9ce2017eff348e054635cdf722daca889f46f45 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 30 Nov 2021 09:04:40 -0300 Subject: [PATCH 124/137] Bump version to 4.3.0-develop --- .docker/Dockerfile.rhel | 2 +- .snapcraft/resources/prepareRocketChat | 2 +- .snapcraft/snap/snapcraft.yaml | 2 +- app/utils/rocketchat.info | 2 +- package-lock.json | 2 +- package.json | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.docker/Dockerfile.rhel b/.docker/Dockerfile.rhel index 1e0f562724d76..20f7de35cc0d5 100644 --- a/.docker/Dockerfile.rhel +++ b/.docker/Dockerfile.rhel @@ -1,6 +1,6 @@ FROM registry.access.redhat.com/ubi8/nodejs-12 -ENV RC_VERSION 4.2.0 +ENV RC_VERSION 4.3.0-develop MAINTAINER buildmaster@rocket.chat diff --git a/.snapcraft/resources/prepareRocketChat b/.snapcraft/resources/prepareRocketChat index c27dc0cb3b562..2573d0855ee3a 100755 --- a/.snapcraft/resources/prepareRocketChat +++ b/.snapcraft/resources/prepareRocketChat @@ -1,6 +1,6 @@ #!/bin/bash -curl -SLf "https://releases.rocket.chat/4.2.0/download/" -o rocket.chat.tgz +curl -SLf "https://releases.rocket.chat/4.3.0-develop/download/" -o rocket.chat.tgz tar xf rocket.chat.tgz --strip 1 diff --git a/.snapcraft/snap/snapcraft.yaml b/.snapcraft/snap/snapcraft.yaml index 226a11c4a7935..0f18d6ee45e23 100644 --- a/.snapcraft/snap/snapcraft.yaml +++ b/.snapcraft/snap/snapcraft.yaml @@ -7,7 +7,7 @@ # 5. `snapcraft snap` name: rocketchat-server -version: 4.2.0 +version: 4.3.0-develop summary: Rocket.Chat server description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ confinement: strict diff --git a/app/utils/rocketchat.info b/app/utils/rocketchat.info index a0aa10e4c04b4..5024baa06f80c 100644 --- a/app/utils/rocketchat.info +++ b/app/utils/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "4.2.0" + "version": "4.3.0-develop" } diff --git a/package-lock.json b/package-lock.json index ae0af47f05714..f116739dc0886 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "Rocket.Chat", - "version": "4.2.0", + "version": "4.3.0-develop", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index deb4eb3ee8ad8..adc12dc93f3e2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Rocket.Chat", "description": "The Ultimate Open Source WebChat Platform", - "version": "4.2.0", + "version": "4.3.0-develop", "author": { "name": "Rocket.Chat", "url": "https://rocket.chat/" From 1a71c705d6959a64637e551084d18e22e1783089 Mon Sep 17 00:00:00 2001 From: Aman-Maheshwari <50165440+Aman-Maheshwari@users.noreply.github.com> Date: Wed, 1 Dec 2021 19:52:32 +0530 Subject: [PATCH 125/137] [FIX] error after properly deleting user from admin panel #23690 --- client/views/admin/users/UserInfo.js | 1 + client/views/admin/users/UserInfoActions.js | 11 ++++------- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/client/views/admin/users/UserInfo.js b/client/views/admin/users/UserInfo.js index 01ee1762c4b51..cadb6b4d82c23 100644 --- a/client/views/admin/users/UserInfo.js +++ b/client/views/admin/users/UserInfo.js @@ -108,6 +108,7 @@ export function UserInfoWithData({ uid, username, onReload, ...props }) { _id={data.user._id} username={data.user.username} onChange={onChange} + onReload={onReload} /> ) } diff --git a/client/views/admin/users/UserInfoActions.js b/client/views/admin/users/UserInfoActions.js index a326997d74716..61c09dbbc3b6a 100644 --- a/client/views/admin/users/UserInfoActions.js +++ b/client/views/admin/users/UserInfoActions.js @@ -13,7 +13,7 @@ import { useTranslation } from '../../../contexts/TranslationContext'; import { useActionSpread } from '../../hooks/useActionSpread'; import UserInfo from '../../room/contextualBar/UserInfo'; -export const UserInfoActions = ({ username, _id, isActive, isAdmin, onChange }) => { +export const UserInfoActions = ({ username, _id, isActive, isAdmin, onChange, onReload }) => { const t = useTranslation(); const setModal = useSetModal(); @@ -39,7 +39,7 @@ export const UserInfoActions = ({ username, _id, isActive, isAdmin, onChange }) const handleDeletedUser = () => { setModal(); userRoute.push({}); - onChange(); + onReload(); }; const confirmOwnerChanges = @@ -84,11 +84,8 @@ export const UserInfoActions = ({ username, _id, isActive, isAdmin, onChange }) const result = await deleteUserEndpoint(deleteUserQuery); if (result.success) { - setModal( - - {t('User_has_been_deleted')} - , - ); + handleDeletedUser(); + dispatchToastMessage({ type: 'success', message: t('User_has_been_deleted') }); } else { setModal(); } From 76a4380c1fc61754d3a2a257f2c542b18172c129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Jaeger=20Foresti?= <60678893+juliajforesti@users.noreply.github.com> Date: Wed, 1 Dec 2021 18:39:24 -0300 Subject: [PATCH 126/137] Chore: Replace new typography (#23756) Co-authored-by: Guilherme Gazzo --- app/theme/client/imports/general/base_old.css | 2 +- client/components/Card/Title.tsx | 2 +- client/components/GenericModal.tsx | 2 +- .../components/GenericTable/GenericTable.tsx | 2 +- client/components/GenericTable/NoResults.tsx | 4 +- client/components/Header/Subtitle.tsx | 2 +- client/components/Header/Title.tsx | 2 +- .../Attachments/Attachment/AuthorName.tsx | 2 +- .../Attachments/Attachment/Details.tsx | 2 +- .../Message/Attachments/Attachment/Text.tsx | 2 +- .../Attachments/FieldsAttachment/Field.tsx | 2 +- .../Message/Attachments/components/Load.tsx | 2 +- .../Message/Attachments/components/Retry.tsx | 2 +- client/components/NotAuthorizedPage.tsx | 2 +- .../Omnichannel/modals/CloseChatModal.js | 2 +- .../Omnichannel/modals/ForwardChatModal.js | 2 +- .../modals/ReturnChatQueueModal.tsx | 2 +- .../Omnichannel/modals/TranscriptModal.tsx | 2 +- client/components/Page/PageHeader.tsx | 2 +- client/components/PageSkeleton.tsx | 2 +- client/components/RoomForeword.js | 2 +- client/components/Sidebar/Header.js | 2 +- client/components/Sidebar/NavigationItem.js | 2 +- client/components/SortList/GroupingList.js | 2 +- client/components/SortList/SortListItem.js | 2 +- client/components/SortList/SortModeList.js | 2 +- client/components/SortList/ViewModeList.js | 2 +- client/components/Subtitle.js | 2 +- client/components/TextCopy.js | 2 +- client/components/UserCard/Info.js | 2 +- client/components/UserCard/UserCard.js | 2 +- client/components/UserCard/Username.js | 2 +- .../VerticalBar/VerticalBarHeader.tsx | 2 +- .../UserAvatarEditor/UserAvatarEditor.js | 2 +- client/components/data/Counter.js | 6 +- client/components/data/Growth.stories.js | 2 +- client/sidebar/header/EditStatusModal.tsx | 2 +- client/sidebar/header/UserDropdown.js | 2 +- .../header/actions/CreateRoomListItem.js | 2 +- client/views/InfoPanel/Label.tsx | 2 +- client/views/InfoPanel/Text.tsx | 2 +- client/views/InfoPanel/Title.tsx | 2 +- .../views/account/preferences/MyDataModal.tsx | 2 +- .../account/security/BackupCodesModal.tsx | 2 +- client/views/account/security/EndToEnd.js | 4 +- .../views/account/security/TwoFactorEmail.js | 2 +- .../views/account/security/TwoFactorTOTP.js | 4 +- .../views/account/tokens/AccountTokensRow.tsx | 2 +- client/views/account/tokens/InfoModal.tsx | 2 +- client/views/admin/apps/APIsDisplay.tsx | 4 +- .../admin/apps/AppDetailsPageContent.tsx | 18 ++-- client/views/admin/apps/AppLogsPage.js | 2 +- .../admin/apps/AppPermissionsReviewModal.js | 4 +- client/views/admin/apps/AppRow.tsx | 4 +- client/views/admin/apps/AppUpdateModal.tsx | 2 +- client/views/admin/apps/AppsWhatIsIt.js | 2 +- client/views/admin/apps/CloudLoginModal.js | 2 +- client/views/admin/apps/MarketplaceRow.tsx | 4 +- client/views/admin/apps/SettingsDisplay.tsx | 2 +- client/views/admin/apps/WarningModal.js | 2 +- client/views/admin/cloud/CopyStep.tsx | 2 +- client/views/admin/cloud/PasteStep.tsx | 2 +- client/views/admin/customEmoji/CustomEmoji.js | 65 +++++++++++ .../customEmoji/EditCustomEmojiWithData.tsx | 2 +- .../views/admin/customSounds/AdminSounds.js | 2 +- .../admin/customSounds/EditCustomSound.js | 2 +- .../customUserStatus/CustomUserStatus.js | 4 +- .../EditCustomUserStatusWithData.tsx | 2 +- .../views/admin/emailInbox/SendTestButton.js | 2 +- .../views/admin/import/ImportProgressPage.js | 4 +- .../views/admin/import/PrepareImportPage.js | 4 +- .../views/admin/info/DescriptionListEntry.js | 2 +- .../components/FederationModal/DNSText.tsx | 2 +- .../FederationModal/FederationModal.tsx | 2 +- .../FederationModal/InviteUsers.tsx | 2 +- .../views/admin/info/OfflineLicenseModal.js | 2 +- client/views/admin/info/UsagePieGraph.tsx | 2 +- .../admin/integrations/IncomingWebhookForm.js | 2 +- .../admin/integrations/IntegrationRow.js | 2 +- .../integrations/OutgoiongWebhookForm.js | 2 +- client/views/admin/integrations/new/NewBot.js | 2 +- .../views/admin/integrations/new/NewZapier.js | 2 +- client/views/admin/invites/InviteRow.js | 2 +- .../admin/oauthApps/EditOauthAppWithData.js | 2 +- .../views/admin/oauthApps/OAuthAppsTable.js | 2 +- client/views/admin/permissions/UserRow.js | 4 +- client/views/admin/rooms/EditRoom.js | 2 +- client/views/admin/rooms/RoomsTable.js | 6 +- client/views/admin/settings/GroupPage.js | 2 +- .../views/admin/settings/GroupPageSkeleton.js | 2 +- client/views/admin/settings/Section.js | 2 +- .../views/admin/settings/SectionSkeleton.js | 2 +- .../admin/sidebar/AdminSidebarSettings.tsx | 2 +- client/views/admin/users/InviteUsers.js | 4 +- client/views/admin/users/UserForm.js | 2 +- client/views/admin/users/UserRow.js | 8 +- client/views/directory/ChannelsTable.js | 12 +-- client/views/directory/TeamsTable.js | 8 +- client/views/directory/UserTable.js | 8 +- client/views/location/MapViewFallback.tsx | 2 +- client/views/notFound/NotFoundPage.js | 6 +- .../views/omnichannel/agents/AgentsRoute.js | 6 +- .../omnichannel/agents/RemoveAgentButton.js | 2 +- client/views/omnichannel/components/Label.js | 2 +- .../views/omnichannel/currentChats/Label.tsx | 2 +- .../currentChats/RemoveChatButton.tsx | 2 +- .../customFields/RemoveCustomFieldButton.js | 2 +- .../views/omnichannel/departments/AgentRow.js | 10 +- .../departments/RemoveDepartmentButton.js | 2 +- .../omnichannel/facebook/FacebookPage.tsx | 2 +- .../views/omnichannel/installation/Wrapper.js | 2 +- .../omnichannel/managers/ManagersRoute.js | 6 +- .../managers/RemoveManagerButton.js | 2 +- .../realTimeMonitoring/counter/CounterItem.js | 4 +- .../Announcement/AnnouncementComponent.tsx | 2 +- .../room/contextualBar/Call/BBB/CallBBB.tsx | 4 +- .../contextualBar/Call/Jitsi/CallJitsi.js | 4 +- .../Call/Jitsi/components/CallModal.js | 2 +- .../KeyboardShortcuts/ShortcutSection.js | 4 +- .../components/NotificationByDevice.js | 2 +- client/views/room/contextualBar/OTR/OTR.js | 6 +- .../RoomFiles/components/FileItem.js | 4 +- .../RoomMembers/List/RoomMembers.js | 4 +- client/views/room/hooks/useUserInfoActions.js | 2 +- .../modals/FileUploadModal/GenericPreview.tsx | 2 +- client/views/setupWizard/SideBar.js | 6 +- client/views/setupWizard/StepHeader.js | 2 +- client/views/setupWizard/steps/FinalStep.js | 4 +- .../setupWizard/steps/RegisterServerStep.js | 2 +- .../ModalSteps/FirstStep.tsx | 4 +- .../RemoveUsersModal/RemoveUsersFirstStep.js | 2 +- .../modals/PlaceChatOnHoldModal.tsx | 2 +- ee/client/audit/UserRow.js | 8 +- .../omnichannel/RemoveBusinessHourButton.js | 2 +- .../cannedResponses/CannedResponseEdit.tsx | 2 +- .../cannedResponses/CannedResponsesRoute.tsx | 2 +- .../RemoveCannedResponseButton.tsx | 2 +- .../InsertPlaceholderDropdown.tsx | 10 +- .../CreateCannedResponseModal.tsx | 2 +- .../CannedResponse/CannedResponse.tsx | 8 +- .../contextualBar/CannedResponse/Item.tsx | 4 +- .../priorities/RemovePriorityButton.js | 2 +- ee/client/omnichannel/tags/RemoveTagButton.js | 2 +- .../omnichannel/units/RemoveUnitButton.js | 2 +- .../admin/engagementDashboard/Section.tsx | 2 +- ee/client/views/admin/info/SeatsCard.tsx | 2 +- package-lock.json | 101 +++++++++++++++--- package.json | 6 +- 148 files changed, 371 insertions(+), 233 deletions(-) create mode 100644 client/views/admin/customEmoji/CustomEmoji.js diff --git a/app/theme/client/imports/general/base_old.css b/app/theme/client/imports/general/base_old.css index 4378dc27b44a4..339396f3e718d 100644 --- a/app/theme/client/imports/general/base_old.css +++ b/app/theme/client/imports/general/base_old.css @@ -1894,7 +1894,7 @@ font-family: inherit; font-size: 0.875rem; - font-weight: 600; + font-weight: 700; line-height: inherit; } diff --git a/client/components/Card/Title.tsx b/client/components/Card/Title.tsx index 5f547f649d39e..a4bb2db606073 100644 --- a/client/components/Card/Title.tsx +++ b/client/components/Card/Title.tsx @@ -2,7 +2,7 @@ import { Box } from '@rocket.chat/fuselage'; import React, { FC } from 'react'; const Title: FC = ({ children }) => ( - + {children} ); diff --git a/client/components/GenericModal.tsx b/client/components/GenericModal.tsx index a2ca56fe05ccf..f9f2508fcf451 100644 --- a/client/components/GenericModal.tsx +++ b/client/components/GenericModal.tsx @@ -75,7 +75,7 @@ const GenericModal: FC = ({ {title ?? t('Are_you_sure')} - {children} + {children} {dontAskAgain} diff --git a/client/components/GenericTable/GenericTable.tsx b/client/components/GenericTable/GenericTable.tsx index c6e5317d24ed4..a7070a5094fc9 100644 --- a/client/components/GenericTable/GenericTable.tsx +++ b/client/components/GenericTable/GenericTable.tsx @@ -88,7 +88,7 @@ const GenericTable = forwardRef(function GenericTable< ? renderFilter({ ...props, onChange: setFilter } as any) // TODO: ugh : null} {results && !results.length ? ( - + {t('No_data_found')} ) : ( diff --git a/client/components/GenericTable/NoResults.tsx b/client/components/GenericTable/NoResults.tsx index 00291af32a1f2..7260f34f33a95 100644 --- a/client/components/GenericTable/NoResults.tsx +++ b/client/components/GenericTable/NoResults.tsx @@ -25,10 +25,10 @@ const NoResults: FC = ({ icon, title, description, buttonTitle, > - + {title} - + {description} diff --git a/client/components/Header/Subtitle.tsx b/client/components/Header/Subtitle.tsx index 95b8365c38676..3b7cf9abcca01 100644 --- a/client/components/Header/Subtitle.tsx +++ b/client/components/Header/Subtitle.tsx @@ -2,7 +2,7 @@ import { Box } from '@rocket.chat/fuselage'; import React, { FC } from 'react'; const Subtitle: FC = (props) => ( - + ); export default Subtitle; diff --git a/client/components/Header/Title.tsx b/client/components/Header/Title.tsx index a7797beeaad23..79775233d0406 100644 --- a/client/components/Header/Title.tsx +++ b/client/components/Header/Title.tsx @@ -2,7 +2,7 @@ import { Box } from '@rocket.chat/fuselage'; import React, { FC } from 'react'; const Title: FC = (props) => ( - + ); export default Title; diff --git a/client/components/Message/Attachments/Attachment/AuthorName.tsx b/client/components/Message/Attachments/Attachment/AuthorName.tsx index 143caf1748a5d..5790dcf040075 100644 --- a/client/components/Message/Attachments/Attachment/AuthorName.tsx +++ b/client/components/Message/Attachments/Attachment/AuthorName.tsx @@ -2,7 +2,7 @@ import { Box } from '@rocket.chat/fuselage'; import React, { ComponentProps, FC } from 'react'; const AuthorName: FC> = (props) => ( - + ); export default AuthorName; diff --git a/client/components/Message/Attachments/Attachment/Details.tsx b/client/components/Message/Attachments/Attachment/Details.tsx index d1961d5d2cfcd..2cdcf2b3e19b8 100644 --- a/client/components/Message/Attachments/Attachment/Details.tsx +++ b/client/components/Message/Attachments/Attachment/Details.tsx @@ -4,7 +4,7 @@ import React, { FC, ComponentProps } from 'react'; const Details: FC> = ({ ...props }) => ( > = (props) => ( - + ); export default Text; diff --git a/client/components/Message/Attachments/FieldsAttachment/Field.tsx b/client/components/Message/Attachments/FieldsAttachment/Field.tsx index 6ed0bcea845b0..635151e2e82dd 100644 --- a/client/components/Message/Attachments/FieldsAttachment/Field.tsx +++ b/client/components/Message/Attachments/FieldsAttachment/Field.tsx @@ -9,7 +9,7 @@ type FieldProps = { const Field: FC = ({ title, value, ...props }) => ( - + {title} {value} diff --git a/client/components/Message/Attachments/components/Load.tsx b/client/components/Message/Attachments/components/Load.tsx index aaf66518292b6..5a901efb99bca 100644 --- a/client/components/Message/Attachments/components/Load.tsx +++ b/client/components/Message/Attachments/components/Load.tsx @@ -22,7 +22,7 @@ const Load: FC = ({ load, ...props }) => { return ( - + {t('Click_to_load')} diff --git a/client/components/Message/Attachments/components/Retry.tsx b/client/components/Message/Attachments/components/Retry.tsx index 341ae63f0b090..3f6351295343c 100644 --- a/client/components/Message/Attachments/components/Retry.tsx +++ b/client/components/Message/Attachments/components/Retry.tsx @@ -22,7 +22,7 @@ const Retry: FC = ({ retry }) => { return ( - + {t('Retry')} diff --git a/client/components/NotAuthorizedPage.tsx b/client/components/NotAuthorizedPage.tsx index aad02ca26c6cb..7de9cb348085a 100644 --- a/client/components/NotAuthorizedPage.tsx +++ b/client/components/NotAuthorizedPage.tsx @@ -10,7 +10,7 @@ const NotAuthorizedPage = (): ReactElement => { return ( - + {t('You_are_not_authorized_to_view_this_page')} diff --git a/client/components/Omnichannel/modals/CloseChatModal.js b/client/components/Omnichannel/modals/CloseChatModal.js index 0c63646b831b0..a53757fce1970 100644 --- a/client/components/Omnichannel/modals/CloseChatModal.js +++ b/client/components/Omnichannel/modals/CloseChatModal.js @@ -67,7 +67,7 @@ const CloseChatModal = ({ department = {}, onCancel, onConfirm }) => { {t('Closing_chat')} - + {t('Close_room_description')} {t('Comment')} diff --git a/client/components/Omnichannel/modals/ForwardChatModal.js b/client/components/Omnichannel/modals/ForwardChatModal.js index 1c8ba7048bf59..0dce6f6e139d6 100644 --- a/client/components/Omnichannel/modals/ForwardChatModal.js +++ b/client/components/Omnichannel/modals/ForwardChatModal.js @@ -89,7 +89,7 @@ const ForwardChatModal = ({ onForward, onCancel, room, ...props }) => { {t('Forward_chat')} - + {t('Forward_to_department')} diff --git a/client/components/Omnichannel/modals/ReturnChatQueueModal.tsx b/client/components/Omnichannel/modals/ReturnChatQueueModal.tsx index 1489118ffe67c..474f25f6c50e4 100644 --- a/client/components/Omnichannel/modals/ReturnChatQueueModal.tsx +++ b/client/components/Omnichannel/modals/ReturnChatQueueModal.tsx @@ -22,7 +22,7 @@ const ReturnChatQueueModal: FC = ({ {t('Return_to_the_queue')} - {t('Would_you_like_to_return_the_queue')} + {t('Would_you_like_to_return_the_queue')} diff --git a/client/components/Omnichannel/modals/TranscriptModal.tsx b/client/components/Omnichannel/modals/TranscriptModal.tsx index 4a5fc1be54e5b..2d872514db11e 100644 --- a/client/components/Omnichannel/modals/TranscriptModal.tsx +++ b/client/components/Omnichannel/modals/TranscriptModal.tsx @@ -76,7 +76,7 @@ const TranscriptModal: FC = ({ {t('Transcript')} - + {!!transcriptRequest &&

      {t('Livechat_transcript_already_requested_warning')}

      } {t('Email')}* diff --git a/client/components/Page/PageHeader.tsx b/client/components/Page/PageHeader.tsx index 64dafe06611b2..8158657bcc3f3 100644 --- a/client/components/Page/PageHeader.tsx +++ b/client/components/Page/PageHeader.tsx @@ -32,7 +32,7 @@ const PageHeader: FC = ({ children = undefined, title, ...props )} - + {title} {children} diff --git a/client/components/PageSkeleton.tsx b/client/components/PageSkeleton.tsx index 5fbef4d1f99d3..a7f7a0a2b2253 100644 --- a/client/components/PageSkeleton.tsx +++ b/client/components/PageSkeleton.tsx @@ -13,7 +13,7 @@ const PageSkeleton = (): ReactElement => ( - + diff --git a/client/components/RoomForeword.js b/client/components/RoomForeword.js index 79784e72cbb22..fac7f41d15c0d 100644 --- a/client/components/RoomForeword.js +++ b/client/components/RoomForeword.js @@ -45,7 +45,7 @@ const RoomForeword = ({ _id: rid }) => { - + {t('Direct_message_you_have_joined')} diff --git a/client/components/Sidebar/Header.js b/client/components/Sidebar/Header.js index 8a7a0ecc98b5f..80854fe21c503 100644 --- a/client/components/Sidebar/Header.js +++ b/client/components/Sidebar/Header.js @@ -13,7 +13,7 @@ const Header = ({ title, onClose, children = undefined, ...props }) => ( flexGrow={1} > {title && ( - + {title} )} diff --git a/client/components/Sidebar/NavigationItem.js b/client/components/Sidebar/NavigationItem.js index 28e7f4f2da21f..eb67d3c391777 100644 --- a/client/components/Sidebar/NavigationItem.js +++ b/client/components/Sidebar/NavigationItem.js @@ -22,7 +22,7 @@ const NavigationItem = ({ return ( {icon && } - + {label}{' '} {tag && ( diff --git a/client/components/SortList/GroupingList.js b/client/components/SortList/GroupingList.js index 28eecf52dc792..5749a8899148c 100644 --- a/client/components/SortList/GroupingList.js +++ b/client/components/SortList/GroupingList.js @@ -28,7 +28,7 @@ function GroupingList() { return ( <> - + {t('Group_by')} diff --git a/client/components/SortList/SortListItem.js b/client/components/SortList/SortListItem.js index e7b980491fb7a..99ae40572c8c8 100644 --- a/client/components/SortList/SortListItem.js +++ b/client/components/SortList/SortListItem.js @@ -14,7 +14,7 @@ function SortListItem({ text, icon, input }) { - + {text} diff --git a/client/components/SortList/SortModeList.js b/client/components/SortList/SortModeList.js index 69680228c4132..0f9521b8fcba7 100644 --- a/client/components/SortList/SortModeList.js +++ b/client/components/SortList/SortModeList.js @@ -24,7 +24,7 @@ function SortModeList() { return ( <> - + {t('Sort_By')} diff --git a/client/components/SortList/ViewModeList.js b/client/components/SortList/ViewModeList.js index 8bc1735f1a04e..9d9cfd72a0ea8 100644 --- a/client/components/SortList/ViewModeList.js +++ b/client/components/SortList/ViewModeList.js @@ -33,7 +33,7 @@ function ViewModeList() { return ( <> - + {t('Display')} diff --git a/client/components/Subtitle.js b/client/components/Subtitle.js index eaace9306f7a4..aa69bd45a788c 100644 --- a/client/components/Subtitle.js +++ b/client/components/Subtitle.js @@ -6,7 +6,7 @@ function Subtitle(props) { ( ( - + ); export default Info; diff --git a/client/components/UserCard/UserCard.js b/client/components/UserCard/UserCard.js index 59c6da1b38060..dbb1779d5572f 100644 --- a/client/components/UserCard/UserCard.js +++ b/client/components/UserCard/UserCard.js @@ -63,7 +63,7 @@ const UserCard = forwardRef(function UserCard( {nickname && ( - + ({nickname}) )} diff --git a/client/components/UserCard/Username.js b/client/components/UserCard/Username.js index 5e7fa18e073cb..90792452e99c0 100644 --- a/client/components/UserCard/Username.js +++ b/client/components/UserCard/Username.js @@ -10,7 +10,7 @@ const Username = ({ name, status = , title, ...props }) => title={title} flexShrink={0} alignItems='center' - fontScale='s2' + fontScale='h4' color='default' withTruncatedText > diff --git a/client/components/VerticalBar/VerticalBarHeader.tsx b/client/components/VerticalBar/VerticalBarHeader.tsx index 4f53dbeec16eb..ece29f07c0f8a 100644 --- a/client/components/VerticalBar/VerticalBarHeader.tsx +++ b/client/components/VerticalBar/VerticalBarHeader.tsx @@ -21,7 +21,7 @@ const VerticalBarHeader: FC<{ children: ReactNode; props?: ComponentProps + {t('Profile_picture')} {count} - {variation} + {variation} - + {description} diff --git a/client/components/data/Growth.stories.js b/client/components/data/Growth.stories.js index eb7c9a4cae668..26fea94a2482f 100644 --- a/client/components/data/Growth.stories.js +++ b/client/components/data/Growth.stories.js @@ -16,7 +16,7 @@ export const Zero = () => {0}; export const Negative = () => {-3}; export const WithTextStyle = () => - ['h1', 's1', 'c1', 'micro'].map((fontScale) => ( + ['h2', 's1', 'c1', 'micro'].map((fontScale) => ( {3} {-3} diff --git a/client/sidebar/header/EditStatusModal.tsx b/client/sidebar/header/EditStatusModal.tsx index d16eab0f96907..981114746acf4 100644 --- a/client/sidebar/header/EditStatusModal.tsx +++ b/client/sidebar/header/EditStatusModal.tsx @@ -68,7 +68,7 @@ const EditStatusModal = ({ {t('Edit_Status')} - + {t('StatusMessage')} diff --git a/client/sidebar/header/UserDropdown.js b/client/sidebar/header/UserDropdown.js index f98aef0844492..9ce7f5ce87730 100644 --- a/client/sidebar/header/UserDropdown.js +++ b/client/sidebar/header/UserDropdown.js @@ -105,7 +105,7 @@ const UserDropdown = ({ user, onClose }) => { display='flex' overflow='hidden' flexDirection='column' - fontScale='p1' + fontScale='p3' mb='neg-x4' flexGrow={1} flexShrink={1} diff --git a/client/sidebar/header/actions/CreateRoomListItem.js b/client/sidebar/header/actions/CreateRoomListItem.js index 23ebf864e5da7..8c42f2d3aff3c 100644 --- a/client/sidebar/header/actions/CreateRoomListItem.js +++ b/client/sidebar/header/actions/CreateRoomListItem.js @@ -14,7 +14,7 @@ export default function CreateRoomListItem({ text, icon, action }) { - + {text} diff --git a/client/views/InfoPanel/Label.tsx b/client/views/InfoPanel/Label.tsx index ec0d6cf4e3454..7d6402fcc2476 100644 --- a/client/views/InfoPanel/Label.tsx +++ b/client/views/InfoPanel/Label.tsx @@ -2,7 +2,7 @@ import { Box } from '@rocket.chat/fuselage'; import React, { ComponentProps, FC } from 'react'; const Label: FC> = (props) => ( - + ); export default Label; diff --git a/client/views/InfoPanel/Text.tsx b/client/views/InfoPanel/Text.tsx index efd146d8802dc..1aafff9971d23 100644 --- a/client/views/InfoPanel/Text.tsx +++ b/client/views/InfoPanel/Text.tsx @@ -7,7 +7,7 @@ const wordBreak = css` `; const Text: FC> = (props) => ( - + ); export default Text; diff --git a/client/views/InfoPanel/Title.tsx b/client/views/InfoPanel/Title.tsx index 8fdfa8966bce1..67fb15f44fa93 100644 --- a/client/views/InfoPanel/Title.tsx +++ b/client/views/InfoPanel/Title.tsx @@ -12,7 +12,7 @@ const Title: FC = ({ title, icon }) => ( title={title} flexShrink={0} alignItems='center' - fontScale='s2' + fontScale='h4' color='default' withTruncatedText > diff --git a/client/views/account/preferences/MyDataModal.tsx b/client/views/account/preferences/MyDataModal.tsx index 5c6bf956f4a3a..ab4cd905b3613 100644 --- a/client/views/account/preferences/MyDataModal.tsx +++ b/client/views/account/preferences/MyDataModal.tsx @@ -19,7 +19,7 @@ const MyDataModal: FC = ({ onCancel, title, text, ...props }) {title} - + {text} diff --git a/client/views/account/security/BackupCodesModal.tsx b/client/views/account/security/BackupCodesModal.tsx index c79c2f0c9ecf8..30d532c0db31a 100644 --- a/client/views/account/security/BackupCodesModal.tsx +++ b/client/views/account/security/BackupCodesModal.tsx @@ -21,7 +21,7 @@ const BackupCodesModal: FC = ({ codes, onClose, ...props {t('Backup_codes')} - + {t('Make_sure_you_have_a_copy_of_your_codes_1')} diff --git a/client/views/account/security/EndToEnd.js b/client/views/account/security/EndToEnd.js index e7d1121dd7cd6..555fda99ff664 100644 --- a/client/views/account/security/EndToEnd.js +++ b/client/views/account/security/EndToEnd.js @@ -75,7 +75,7 @@ const EndToEnd = (props) => { return ( - {t('E2E_Encryption_Password_Change')} + {t('E2E_Encryption_Password_Change')} @@ -106,7 +106,7 @@ const EndToEnd = (props) => { - + {t('Reset_E2E_Key')} diff --git a/client/views/account/security/TwoFactorEmail.js b/client/views/account/security/TwoFactorEmail.js index 10087ec7b7135..0c0d347642654 100644 --- a/client/views/account/security/TwoFactorEmail.js +++ b/client/views/account/security/TwoFactorEmail.js @@ -35,7 +35,7 @@ const TwoFactorEmail = (props) => { return ( - {t('Two-factor_authentication_email')} + {t('Two-factor_authentication_email')} {isEnabled && ( - + {t('Backup_codes')} {t('You_have_n_codes_remaining', { number: codesRemaining })} diff --git a/client/views/account/tokens/AccountTokensRow.tsx b/client/views/account/tokens/AccountTokensRow.tsx index 3456beb3d3b5f..c83157d27ec1e 100644 --- a/client/views/account/tokens/AccountTokensRow.tsx +++ b/client/views/account/tokens/AccountTokensRow.tsx @@ -30,7 +30,7 @@ const AccountTokensRow: FC = ({ return ( - + {name} {isMedium && {formatDateAndTime(createdAt)}} diff --git a/client/views/account/tokens/InfoModal.tsx b/client/views/account/tokens/InfoModal.tsx index fb6b2a3498e2e..c28615ad35f72 100644 --- a/client/views/account/tokens/InfoModal.tsx +++ b/client/views/account/tokens/InfoModal.tsx @@ -27,7 +27,7 @@ const InfoModal: FC = ({ {title} - {content} + {content} {cancelText && } diff --git a/client/views/admin/apps/APIsDisplay.tsx b/client/views/admin/apps/APIsDisplay.tsx index 497265d7be42d..ec895c7948dae 100644 --- a/client/views/admin/apps/APIsDisplay.tsx +++ b/client/views/admin/apps/APIsDisplay.tsx @@ -25,12 +25,12 @@ const APIsDisplay: FC = ({ apis }) => { <> - + {t('APIs')} {apis.map((api) => ( - + {api.methods.join(' | ').toUpperCase()} {api.path} {api.methods.map((method) => ( diff --git a/client/views/admin/apps/AppDetailsPageContent.tsx b/client/views/admin/apps/AppDetailsPageContent.tsx index 1fd8d884d26dc..5c3810a56a2fc 100644 --- a/client/views/admin/apps/AppDetailsPageContent.tsx +++ b/client/views/admin/apps/AppDetailsPageContent.tsx @@ -42,9 +42,9 @@ const AppDetailsPageContent: FC = ({ data }) => { iconFileData={iconFileData} /> - {name} + {name} - + {t('By_author', { author: authorName })} |{t('Version_version', { version })} @@ -97,7 +97,7 @@ const AppDetailsPageContent: FC = ({ data }) => { - {t('Categories')} + {t('Categories')} {categories && categories.map((current) => ( @@ -107,7 +107,7 @@ const AppDetailsPageContent: FC = ({ data }) => { ))} - {t('Contact')} + {t('Contact')} = ({ data }) => { flexWrap='wrap' > - + {t('Author_Site')} - + {t('Support')} - {t('Details')} + {t('Details')} {description} @@ -140,7 +140,7 @@ const AppDetailsPageContent: FC = ({ data }) => { - {t('Bundles')} + {t('Bundles')} {bundledIn.map((bundle) => ( = ({ data }) => { ))} - {bundle.bundleName} + {bundle.bundleName} {bundle.apps.map((app) => ( {app.latest.name}, ))} diff --git a/client/views/admin/apps/AppLogsPage.js b/client/views/admin/apps/AppLogsPage.js index 00d133544356f..1e6d9a8bc2d9c 100644 --- a/client/views/admin/apps/AppLogsPage.js +++ b/client/views/admin/apps/AppLogsPage.js @@ -81,7 +81,7 @@ function AppLogsPage({ id, ...props }) { {loading && } {app.error && ( - + {app.error.message} )} diff --git a/client/views/admin/apps/AppPermissionsReviewModal.js b/client/views/admin/apps/AppPermissionsReviewModal.js index e16ed7d410901..43eee0b80f29c 100644 --- a/client/views/admin/apps/AppPermissionsReviewModal.js +++ b/client/views/admin/apps/AppPermissionsReviewModal.js @@ -25,10 +25,10 @@ const AppPermissionsReviewModal = ({ appPermissions, cancel, confirm, modalProps {t('Apps_Permissions_Review_Modal_Title')} - + {t('Apps_Permissions_Review_Modal_Subtitle')} - +
        {appPermissions.length ? appPermissions.map((permission, count) => ( diff --git a/client/views/admin/apps/AppRow.tsx b/client/views/admin/apps/AppRow.tsx index 762257cb5c867..b20e01afab4c1 100644 --- a/client/views/admin/apps/AppRow.tsx +++ b/client/views/admin/apps/AppRow.tsx @@ -74,10 +74,10 @@ const AppRow: FC = ({ medium, ...props }) => { iconFileData={iconFileData} /> - + {name} - {`${t('By')} ${authorName}`} + {`${t('By')} ${authorName}`} {medium && ( diff --git a/client/views/admin/apps/AppUpdateModal.tsx b/client/views/admin/apps/AppUpdateModal.tsx index 88ebcadc3e34f..cdaf6ddb9f5e6 100644 --- a/client/views/admin/apps/AppUpdateModal.tsx +++ b/client/views/admin/apps/AppUpdateModal.tsx @@ -30,7 +30,7 @@ const AppUpdateModal: FC = ({ confirm, cancel, ...props }) {t('Apps_Manual_Update_Modal_Title')} - {t('Apps_Manual_Update_Modal_Body')} + {t('Apps_Manual_Update_Modal_Body')} diff --git a/client/views/omnichannel/components/Label.js b/client/views/omnichannel/components/Label.js index 80c1fb2353ba9..400e523419f62 100644 --- a/client/views/omnichannel/components/Label.js +++ b/client/views/omnichannel/components/Label.js @@ -1,6 +1,6 @@ import { Box } from '@rocket.chat/fuselage'; import React from 'react'; -const Label = (props) => ; +const Label = (props) => ; export default Label; diff --git a/client/views/omnichannel/currentChats/Label.tsx b/client/views/omnichannel/currentChats/Label.tsx index 27bbf61dab16a..f4259e297b5da 100644 --- a/client/views/omnichannel/currentChats/Label.tsx +++ b/client/views/omnichannel/currentChats/Label.tsx @@ -2,7 +2,7 @@ import { Box } from '@rocket.chat/fuselage'; import React, { ComponentProps, FC } from 'react'; const Label: FC> = (props) => ( - + ); export default Label; diff --git a/client/views/omnichannel/currentChats/RemoveChatButton.tsx b/client/views/omnichannel/currentChats/RemoveChatButton.tsx index 34aca32b83c70..f8eaa765672ec 100644 --- a/client/views/omnichannel/currentChats/RemoveChatButton.tsx +++ b/client/views/omnichannel/currentChats/RemoveChatButton.tsx @@ -51,7 +51,7 @@ const RemoveChatButton: FC<{ _id: string; reload: () => void }> = ({ _id, reload }); return ( - + diff --git a/client/views/omnichannel/customFields/RemoveCustomFieldButton.js b/client/views/omnichannel/customFields/RemoveCustomFieldButton.js index 911ae1c1782f9..7921e89ddf4f3 100644 --- a/client/views/omnichannel/customFields/RemoveCustomFieldButton.js +++ b/client/views/omnichannel/customFields/RemoveCustomFieldButton.js @@ -46,7 +46,7 @@ function RemoveCustomFieldButton({ _id, reload }) { }); return ( - + diff --git a/client/views/omnichannel/departments/AgentRow.js b/client/views/omnichannel/departments/AgentRow.js index d636b775a13c2..527a280627f25 100644 --- a/client/views/omnichannel/departments/AgentRow.js +++ b/client/views/omnichannel/departments/AgentRow.js @@ -18,11 +18,11 @@ const AgentRow = ({ agentId, username, name, avatarETag, mediaQuery, agentList, /> - + {name || username} {!mediaQuery && name && ( - + {' '} {`@${username}`}{' '} @@ -31,13 +31,13 @@ const AgentRow = ({ agentId, username, name, avatarETag, mediaQuery, agentList, - + - + - + diff --git a/client/views/omnichannel/departments/RemoveDepartmentButton.js b/client/views/omnichannel/departments/RemoveDepartmentButton.js index 10b170e6def92..e7aee9c6dae53 100644 --- a/client/views/omnichannel/departments/RemoveDepartmentButton.js +++ b/client/views/omnichannel/departments/RemoveDepartmentButton.js @@ -44,7 +44,7 @@ function RemoveDepartmentButton({ _id, reload }) { }); return ( - + diff --git a/client/views/omnichannel/facebook/FacebookPage.tsx b/client/views/omnichannel/facebook/FacebookPage.tsx index 694ea1e5ce00c..01821cbeea4ea 100644 --- a/client/views/omnichannel/facebook/FacebookPage.tsx +++ b/client/views/omnichannel/facebook/FacebookPage.tsx @@ -57,7 +57,7 @@ const FacebookPage: FC = ({ )} {enabled && ( <> - + {t('Pages')} {pages?.length ? ( diff --git a/client/views/omnichannel/installation/Wrapper.js b/client/views/omnichannel/installation/Wrapper.js index 2894098e5a5ba..ac763f686b3f3 100644 --- a/client/views/omnichannel/installation/Wrapper.js +++ b/client/views/omnichannel/installation/Wrapper.js @@ -5,7 +5,7 @@ const Wrapper = (text) => ( - + {name || username} {!mediaQuery && name && ( - + {' '} {`@${username}`}{' '} @@ -120,7 +120,7 @@ function ManagersRoute() { {mediaQuery && ( - + {username} {' '} diff --git a/client/views/omnichannel/managers/RemoveManagerButton.js b/client/views/omnichannel/managers/RemoveManagerButton.js index 1509b6bfa03ca..6e8f3532a5adf 100644 --- a/client/views/omnichannel/managers/RemoveManagerButton.js +++ b/client/views/omnichannel/managers/RemoveManagerButton.js @@ -43,7 +43,7 @@ function RemoveManagerButton({ _id, reload }) { }); return ( - + diff --git a/client/views/omnichannel/realTimeMonitoring/counter/CounterItem.js b/client/views/omnichannel/realTimeMonitoring/counter/CounterItem.js index bd6a28fd2dfa9..9cfb00c23b39e 100644 --- a/client/views/omnichannel/realTimeMonitoring/counter/CounterItem.js +++ b/client/views/omnichannel/realTimeMonitoring/counter/CounterItem.js @@ -10,10 +10,10 @@ const CounterItem = ({ title = '', count = '-', ...props }) => ( flexGrow={1} {...props} > - + {title} - {count} + {count} ); diff --git a/client/views/room/Announcement/AnnouncementComponent.tsx b/client/views/room/Announcement/AnnouncementComponent.tsx index c5eb385aed41e..2b6db62b54b52 100644 --- a/client/views/room/Announcement/AnnouncementComponent.tsx +++ b/client/views/room/Announcement/AnnouncementComponent.tsx @@ -39,7 +39,7 @@ const AnnouncementComponent: FC = ({ children, onCl pi='x24' alignItems='center' display='flex' - fontScale='p2' + fontScale='p4' textAlign='center' className={announcementBar} > diff --git a/client/views/room/contextualBar/Call/BBB/CallBBB.tsx b/client/views/room/contextualBar/Call/BBB/CallBBB.tsx index d2ac6124f1925..7503f88341a35 100644 --- a/client/views/room/contextualBar/Call/BBB/CallBBB.tsx +++ b/client/views/room/contextualBar/Call/BBB/CallBBB.tsx @@ -33,8 +33,8 @@ const CallBBB: FC = ({ {openNewWindow ? ( <> - {t('Video_Conference')} - + {t('Video_Conference')} + {t('Opened_in_a_new_window')} diff --git a/client/views/room/contextualBar/Call/Jitsi/CallJitsi.js b/client/views/room/contextualBar/Call/Jitsi/CallJitsi.js index 536c7afe9b9e8..ee6c6eedc5e3b 100644 --- a/client/views/room/contextualBar/Call/Jitsi/CallJitsi.js +++ b/client/views/room/contextualBar/Call/Jitsi/CallJitsi.js @@ -9,8 +9,8 @@ const CallJitsi = ({ handleClose, openNewWindow, refContent, children }) => { const content = openNewWindow ? ( <> - {t('Video_Conference')} - + {t('Video_Conference')} + {t('Opened_in_a_new_window')} diff --git a/client/views/room/contextualBar/Call/Jitsi/components/CallModal.js b/client/views/room/contextualBar/Call/Jitsi/components/CallModal.js index 4ecea1950881f..07546dd0c7a70 100644 --- a/client/views/room/contextualBar/Call/Jitsi/components/CallModal.js +++ b/client/views/room/contextualBar/Call/Jitsi/components/CallModal.js @@ -14,7 +14,7 @@ export const CallModal = ({ handleYes, handleCancel }) => { - {t('Start_video_call')} + {t('Start_video_call')} diff --git a/client/views/room/contextualBar/KeyboardShortcuts/ShortcutSection.js b/client/views/room/contextualBar/KeyboardShortcuts/ShortcutSection.js index 3a0858fbb8b5e..7f2082ab5f520 100644 --- a/client/views/room/contextualBar/KeyboardShortcuts/ShortcutSection.js +++ b/client/views/room/contextualBar/KeyboardShortcuts/ShortcutSection.js @@ -3,11 +3,11 @@ import React from 'react'; const ShortcutSection = ({ title, command }) => ( - + {title} - {command} + {command} ); diff --git a/client/views/room/contextualBar/NotificationPreferences/components/NotificationByDevice.js b/client/views/room/contextualBar/NotificationPreferences/components/NotificationByDevice.js index 871eb9b898c9e..e0107b46309b5 100644 --- a/client/views/room/contextualBar/NotificationPreferences/components/NotificationByDevice.js +++ b/client/views/room/contextualBar/NotificationPreferences/components/NotificationByDevice.js @@ -6,7 +6,7 @@ export const NotificationByDevice = ({ device, icon, children }) => ( title={ - + {device} diff --git a/client/views/room/contextualBar/OTR/OTR.js b/client/views/room/contextualBar/OTR/OTR.js index d20e8a1b9e476..2fd7cc4e1d097 100644 --- a/client/views/room/contextualBar/OTR/OTR.js +++ b/client/views/room/contextualBar/OTR/OTR.js @@ -24,7 +24,7 @@ const OTR = ({ - {t('Off_the_record_conversation')} + {t('Off_the_record_conversation')} {!isEstablishing && !isEstablished && isOnline && ( diff --git a/ee/client/omnichannel/cannedResponses/CannedResponseEdit.tsx b/ee/client/omnichannel/cannedResponses/CannedResponseEdit.tsx index 621ad8b66fd93..efb6f924d3a14 100644 --- a/ee/client/omnichannel/cannedResponses/CannedResponseEdit.tsx +++ b/ee/client/omnichannel/cannedResponses/CannedResponseEdit.tsx @@ -163,7 +163,7 @@ const CannedResponseEdit: FC<{ - + { - + {createdBy.username} diff --git a/ee/client/omnichannel/cannedResponses/RemoveCannedResponseButton.tsx b/ee/client/omnichannel/cannedResponses/RemoveCannedResponseButton.tsx index 9dd40091f0107..f9de5e441307b 100644 --- a/ee/client/omnichannel/cannedResponses/RemoveCannedResponseButton.tsx +++ b/ee/client/omnichannel/cannedResponses/RemoveCannedResponseButton.tsx @@ -60,7 +60,7 @@ const RemoveCannedResponseButton: FC = ({ }); return ( - + diff --git a/ee/client/omnichannel/components/CannedResponse/MarkdownTextEditor/InsertPlaceholderDropdown.tsx b/ee/client/omnichannel/components/CannedResponse/MarkdownTextEditor/InsertPlaceholderDropdown.tsx index fb6769f643224..12e14da95c26a 100644 --- a/ee/client/omnichannel/components/CannedResponse/MarkdownTextEditor/InsertPlaceholderDropdown.tsx +++ b/ee/client/omnichannel/components/CannedResponse/MarkdownTextEditor/InsertPlaceholderDropdown.tsx @@ -41,17 +41,17 @@ const InsertPlaceholderDropdown: FC<{ setPlaceholder('contact.name')}> - + {t('Name')} setPlaceholder('contact.email')}> - + {t('Email')} setPlaceholder('contact.phone')}> - + {t('Phone')} @@ -62,12 +62,12 @@ const InsertPlaceholderDropdown: FC<{ setPlaceholder('agent.name')}> - + {t('Name')} setPlaceholder('agent.email')}> - + {t('Email')} diff --git a/ee/client/omnichannel/components/CannedResponse/modals/CreateCannedResponse/CreateCannedResponseModal.tsx b/ee/client/omnichannel/components/CannedResponse/modals/CreateCannedResponse/CreateCannedResponseModal.tsx index a035377dc3be1..626b03f82973a 100644 --- a/ee/client/omnichannel/components/CannedResponse/modals/CreateCannedResponse/CreateCannedResponseModal.tsx +++ b/ee/client/omnichannel/components/CannedResponse/modals/CreateCannedResponse/CreateCannedResponseModal.tsx @@ -49,7 +49,7 @@ const CreateCannedResponseModal: FC<{ }} /> - + - + {t('Shortcut')}: @@ -59,7 +59,7 @@ const CannedResponse: FC<{ - + {t('Content')}: @@ -67,7 +67,7 @@ const CannedResponse: FC<{ - + {t('Sharing')}: @@ -75,7 +75,7 @@ const CannedResponse: FC<{ - + {t('Tags')}: diff --git a/ee/client/omnichannel/components/contextualBar/CannedResponse/Item.tsx b/ee/client/omnichannel/components/contextualBar/CannedResponse/Item.tsx index ac63b3dbf3b12..3c06aaac0ad53 100644 --- a/ee/client/omnichannel/components/contextualBar/CannedResponse/Item.tsx +++ b/ee/client/omnichannel/components/contextualBar/CannedResponse/Item.tsx @@ -37,7 +37,7 @@ const Item: FC<{ > - + !{data.shortcut} @@ -57,7 +57,7 @@ const Item: FC<{ - + "{data.text}" {data.tags && data.tags.length > 0 && ( diff --git a/ee/client/omnichannel/priorities/RemovePriorityButton.js b/ee/client/omnichannel/priorities/RemovePriorityButton.js index 878500943611e..a729ad1859bd4 100644 --- a/ee/client/omnichannel/priorities/RemovePriorityButton.js +++ b/ee/client/omnichannel/priorities/RemovePriorityButton.js @@ -49,7 +49,7 @@ function RemovePriorityButton({ _id, reload }) { }); return ( - + diff --git a/ee/client/omnichannel/tags/RemoveTagButton.js b/ee/client/omnichannel/tags/RemoveTagButton.js index f7b38b8d91c02..ce229eae58c58 100644 --- a/ee/client/omnichannel/tags/RemoveTagButton.js +++ b/ee/client/omnichannel/tags/RemoveTagButton.js @@ -49,7 +49,7 @@ function RemoveTagButton({ _id, reload }) { }); return ( - + diff --git a/ee/client/omnichannel/units/RemoveUnitButton.js b/ee/client/omnichannel/units/RemoveUnitButton.js index d539186ea9cdb..e6d364e646e02 100644 --- a/ee/client/omnichannel/units/RemoveUnitButton.js +++ b/ee/client/omnichannel/units/RemoveUnitButton.js @@ -49,7 +49,7 @@ function RemoveUnitButton({ _id, reload }) { }); return ( - + diff --git a/ee/client/views/admin/engagementDashboard/Section.tsx b/ee/client/views/admin/engagementDashboard/Section.tsx index 3029fd2904ff3..72a37f7474546 100644 --- a/ee/client/views/admin/engagementDashboard/Section.tsx +++ b/ee/client/views/admin/engagementDashboard/Section.tsx @@ -16,7 +16,7 @@ const Section = ({ {title && ( - + {title} )} diff --git a/ee/client/views/admin/info/SeatsCard.tsx b/ee/client/views/admin/info/SeatsCard.tsx index 777444835206c..2ced4ceaca326 100644 --- a/ee/client/views/admin/info/SeatsCard.tsx +++ b/ee/client/views/admin/info/SeatsCard.tsx @@ -34,7 +34,7 @@ const SeatsCard = (): ReactElement | null => { display='flex' flexDirection='row' justifyContent='center' - fontScale={isNearLimit ? 'p2' : 'p1'} + fontScale={isNearLimit ? 'p4' : 'p3'} > {!seatsCap ? ( diff --git a/package-lock.json b/package-lock.json index f116739dc0886..5151742de941c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5363,22 +5363,67 @@ } }, "@rocket.chat/fuselage": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage/-/fuselage-0.30.1.tgz", - "integrity": "sha512-tO178KfR8GFRuIm/2Lv5f0mKoHS4ZSKAJB6jE5UOrGehDN4kjJ8lKcyCoF24yBKcp/3q8ZbkNTgyGY1nB+jhlQ==", - "requires": { - "@rocket.chat/css-in-js": "^0.30.0", - "@rocket.chat/css-supports": "^0.30.0", - "@rocket.chat/fuselage-tokens": "^0.30.0", - "@rocket.chat/memo": "^0.30.0", + "version": "0.6.3-dev.357", + "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage/-/fuselage-0.6.3-dev.357.tgz", + "integrity": "sha512-8pW5NzSgn0dy+Zo+ebuXEhsJP6ByIXlH0OOr23tfIegMQQz/rULPZvK66E+PxExNv+nFQ+X/HemcmkEi1/BNbw==", + "requires": { + "@rocket.chat/css-in-js": "^0.6.3-dev.357+eda6fa37", + "@rocket.chat/css-supports": "^0.6.3-dev.357+eda6fa37", + "@rocket.chat/fuselage-tokens": "^0.6.3-dev.357+eda6fa37", + "@rocket.chat/memo": "^0.6.3-dev.357+eda6fa37", "invariant": "^2.2.4", "react-keyed-flatten-children": "^1.3.0" + }, + "dependencies": { + "@rocket.chat/css-in-js": { + "version": "0.6.3-dev.357", + "resolved": "https://registry.npmjs.org/@rocket.chat/css-in-js/-/css-in-js-0.6.3-dev.357.tgz", + "integrity": "sha512-SMTb2/XtLOrMiuNnviBLfhOp00IJ+PZB1GYUh4qIN0+2j4pv9VKJ+oUgX9s1Gumzrrh3O/Y1mEGl7V02KbXyYg==", + "requires": { + "@emotion/hash": "^0.8.0", + "@rocket.chat/css-supports": "^0.6.3-dev.357+eda6fa37", + "@rocket.chat/memo": "^0.6.3-dev.357+eda6fa37", + "@rocket.chat/stylis-logical-props-middleware": "^0.6.3-dev.357+eda6fa37", + "stylis": "^4.0.10" + } + }, + "@rocket.chat/css-supports": { + "version": "0.6.3-dev.357", + "resolved": "https://registry.npmjs.org/@rocket.chat/css-supports/-/css-supports-0.6.3-dev.357.tgz", + "integrity": "sha512-SXvr/0of9EV38HCYRGs5fsFnCDmRDUBE/TUXa08btara/aiPSAVrCfrLDnZocqIoc/+8JTgZb28EyQql1Hm1TA==", + "requires": { + "@rocket.chat/memo": "^0.6.3-dev.357+eda6fa37", + "tslib": "^2.3.1" + } + }, + "@rocket.chat/memo": { + "version": "0.6.3-dev.357", + "resolved": "https://registry.npmjs.org/@rocket.chat/memo/-/memo-0.6.3-dev.357.tgz", + "integrity": "sha512-mHct/JufxozOBUylaCresEpwNR9/Ad9L3tU/avt+MzrVi4nbqr9X4tZw2Nqz7uaHfyzJQyqye1KECqX/3t2IiA==", + "requires": { + "tslib": "^2.3.1" + } + }, + "@rocket.chat/stylis-logical-props-middleware": { + "version": "0.6.3-dev.357", + "resolved": "https://registry.npmjs.org/@rocket.chat/stylis-logical-props-middleware/-/stylis-logical-props-middleware-0.6.3-dev.357.tgz", + "integrity": "sha512-RiFGdIIUKOcjTMfuC2DlJUfR0QlpyYft8awUimGqahxzX1QmWh9mzwuYo5Y0bmyrRySJU5g3KJx8OcS1pvSoOQ==", + "requires": { + "@rocket.chat/css-supports": "^0.6.3-dev.357+eda6fa37", + "tslib": "^2.3.1" + } + }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } } }, "@rocket.chat/fuselage-hooks": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-hooks/-/fuselage-hooks-0.30.1.tgz", - "integrity": "sha512-9mfe8yx9MjgjYGDKctHqDkV5Byb5PuAXDter1sl37w/Y9gk8TB1tohU+bF88YthBYSLnqBaNWZMsO2zpKuTGiQ==" + "version": "0.6.3-dev.358", + "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-hooks/-/fuselage-hooks-0.6.3-dev.358.tgz", + "integrity": "sha512-P5iBg7e0GBi+GPWgqrFJoN1CbraeisofMENz1MzqtXX5krW/ar+gQOUxXhsjMYdRQYy4wiGmqv7Y50xRCNqipw==" }, "@rocket.chat/fuselage-polyfills": { "version": "0.30.1", @@ -5394,9 +5439,9 @@ } }, "@rocket.chat/fuselage-tokens": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-tokens/-/fuselage-tokens-0.30.1.tgz", - "integrity": "sha512-bnwuNahkG9+EHYjZ8IpKqjxRAHnWHpQGZWwLaLE0XKt7xP8B/ubEQeV7PLDYI0KPJ5oWJCkk8CSf4FGJ9gSyxg==" + "version": "0.6.3-dev.357", + "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-tokens/-/fuselage-tokens-0.6.3-dev.357.tgz", + "integrity": "sha512-sxI2CLyxDTpFUP8j+BJ8DCFmmxq6x24eiagzNM/ii1HjAyS16hIRCOydYY6h0MG7EtA95BLV7nowRPk1D2dA0Q==" }, "@rocket.chat/fuselage-ui-kit": { "version": "0.30.1", @@ -5410,6 +5455,29 @@ "tslib": "^2.3.1" }, "dependencies": { + "@rocket.chat/fuselage": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage/-/fuselage-0.30.1.tgz", + "integrity": "sha512-tO178KfR8GFRuIm/2Lv5f0mKoHS4ZSKAJB6jE5UOrGehDN4kjJ8lKcyCoF24yBKcp/3q8ZbkNTgyGY1nB+jhlQ==", + "requires": { + "@rocket.chat/css-in-js": "^0.30.0", + "@rocket.chat/css-supports": "^0.30.0", + "@rocket.chat/fuselage-tokens": "^0.30.0", + "@rocket.chat/memo": "^0.30.0", + "invariant": "^2.2.4", + "react-keyed-flatten-children": "^1.3.0" + } + }, + "@rocket.chat/fuselage-hooks": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-hooks/-/fuselage-hooks-0.30.1.tgz", + "integrity": "sha512-9mfe8yx9MjgjYGDKctHqDkV5Byb5PuAXDter1sl37w/Y9gk8TB1tohU+bF88YthBYSLnqBaNWZMsO2zpKuTGiQ==" + }, + "@rocket.chat/fuselage-tokens": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-tokens/-/fuselage-tokens-0.30.1.tgz", + "integrity": "sha512-bnwuNahkG9+EHYjZ8IpKqjxRAHnWHpQGZWwLaLE0XKt7xP8B/ubEQeV7PLDYI0KPJ5oWJCkk8CSf4FGJ9gSyxg==" + }, "tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", @@ -5483,6 +5551,11 @@ "tslib": "^2.3.1" }, "dependencies": { + "@rocket.chat/fuselage-hooks": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-hooks/-/fuselage-hooks-0.30.1.tgz", + "integrity": "sha512-9mfe8yx9MjgjYGDKctHqDkV5Byb5PuAXDter1sl37w/Y9gk8TB1tohU+bF88YthBYSLnqBaNWZMsO2zpKuTGiQ==" + }, "tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", diff --git a/package.json b/package.json index adc12dc93f3e2..bf08ea48e1e36 100644 --- a/package.json +++ b/package.json @@ -174,10 +174,10 @@ "@rocket.chat/apps-engine": "^1.28.1", "@rocket.chat/css-in-js": "^0.30.1", "@rocket.chat/emitter": "^0.30.1", - "@rocket.chat/fuselage": "^0.30.1", - "@rocket.chat/fuselage-hooks": "^0.30.1", + "@rocket.chat/fuselage": "^0.6.3-dev.357", + "@rocket.chat/fuselage-hooks": "^0.6.3-dev.358", "@rocket.chat/fuselage-polyfills": "^0.30.1", - "@rocket.chat/fuselage-tokens": "^0.30.1", + "@rocket.chat/fuselage-tokens": "^0.6.3-dev.357", "@rocket.chat/fuselage-ui-kit": "^0.30.1", "@rocket.chat/icons": "^0.30.1", "@rocket.chat/logo": "^0.30.1", From a0ed0b1e6cb186f6338f3b4f5aeac6a96e6dd782 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Thu, 2 Dec 2021 09:43:54 -0300 Subject: [PATCH 127/137] Chore: Change Menu props to accept next fuselage version (#23839) --- client/views/room/Header/ToolBox/ToolBox.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/views/room/Header/ToolBox/ToolBox.tsx b/client/views/room/Header/ToolBox/ToolBox.tsx index 3c96c73bf07af..07c8ede8d74fd 100644 --- a/client/views/room/Header/ToolBox/ToolBox.tsx +++ b/client/views/room/Header/ToolBox/ToolBox.tsx @@ -102,8 +102,8 @@ const ToolBox: FC = ({ className }) => { aria-keyshortcuts='alt' tabIndex={-1} options={hiddenActions} - renderItem={({ value, ...props }): ReactNode => - hiddenActionRenderers.current[value](props) + renderItem={(props): ReactNode => + props.id && hiddenActionRenderers.current[props.id](props) } /> )} From 8b90ef8f4b743582e102573b2a04ebd03bb958ab Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Thu, 2 Dec 2021 16:22:53 -0300 Subject: [PATCH 128/137] [IMPROVE] Rewrite remove room invite modal #23781 --- client/components/GenericModal.tsx | 1 + client/hooks/useTimeFromNow.ts | 5 + client/views/admin/invites/InviteRow.js | 100 ------------------ client/views/admin/invites/InviteRow.tsx | 96 +++++++++++++++++ client/views/admin/invites/InvitesPage.js | 79 -------------- client/views/admin/invites/InvitesPage.tsx | 91 ++++++++++++++++ .../{InvitesRoute.js => InvitesRoute.tsx} | 6 +- definition/IInvite.ts | 2 +- definition/rest/index.ts | 4 +- definition/rest/v1/invites.ts | 10 ++ packages/rocketchat-i18n/i18n/en.i18n.json | 3 + 11 files changed, 213 insertions(+), 184 deletions(-) create mode 100644 client/hooks/useTimeFromNow.ts delete mode 100644 client/views/admin/invites/InviteRow.js create mode 100644 client/views/admin/invites/InviteRow.tsx delete mode 100644 client/views/admin/invites/InvitesPage.js create mode 100644 client/views/admin/invites/InvitesPage.tsx rename client/views/admin/invites/{InvitesRoute.js => InvitesRoute.tsx} (80%) create mode 100644 definition/rest/v1/invites.ts diff --git a/client/components/GenericModal.tsx b/client/components/GenericModal.tsx index f9f2508fcf451..ad69cc0ef9138 100644 --- a/client/components/GenericModal.tsx +++ b/client/components/GenericModal.tsx @@ -8,6 +8,7 @@ type VariantType = 'danger' | 'warning' | 'info' | 'success'; type GenericModalProps = RequiredModalProps & { variant?: VariantType; + children?: ReactNode; cancelText?: string; confirmText?: string; title?: string | ReactElement; diff --git a/client/hooks/useTimeFromNow.ts b/client/hooks/useTimeFromNow.ts new file mode 100644 index 0000000000000..6ae2b65d2d512 --- /dev/null +++ b/client/hooks/useTimeFromNow.ts @@ -0,0 +1,5 @@ +import moment from 'moment'; +import { useCallback } from 'react'; + +export const useTimeFromNow = (withSuffix: boolean): ((date: Date) => string) => + useCallback((date) => moment(date).fromNow(!withSuffix), [withSuffix]); diff --git a/client/views/admin/invites/InviteRow.js b/client/views/admin/invites/InviteRow.js deleted file mode 100644 index 3d0b4d1f82b56..0000000000000 --- a/client/views/admin/invites/InviteRow.js +++ /dev/null @@ -1,100 +0,0 @@ -import { Button, Icon, Table, Box } from '@rocket.chat/fuselage'; -import { useMediaQuery } from '@rocket.chat/fuselage-hooks'; -import moment from 'moment'; -import React from 'react'; - -import { useModal } from '../../../contexts/ModalContext'; -import { useEndpoint } from '../../../contexts/ServerContext'; -import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; -import { useFormatDateAndTime } from '../../../hooks/useFormatDateAndTime'; - -function InviteRow({ _id, createdAt, expires, days, uses, maxUses, onRemove }) { - const t = useTranslation(); - const formatDateAndTime = useFormatDateAndTime(); - const modal = useModal(); - const dispatchToastMessage = useToastMessageDispatch(); - - const removeInvite = useEndpoint('DELETE', `removeInvite/${_id}`); - - const daysToExpire = ({ expires, days }) => { - if (days > 0) { - if (expires < Date.now()) { - return t('Expired'); - } - - return moment(expires).fromNow(true); - } - - return t('Never'); - }; - - const maxUsesLeft = ({ maxUses, uses }) => { - if (maxUses > 0) { - if (uses >= maxUses) { - return 0; - } - - return maxUses - uses; - } - - return t('Unlimited'); - }; - - const handleRemoveButtonClick = async (event) => { - event.stopPropagation(); - - modal.open( - { - // TODO REFACTOR - text: t('Are_you_sure_you_want_to_delete_this_record'), - type: 'warning', - showCancelButton: true, - confirmButtonColor: '#DD6B55', - confirmButtonText: t('Yes'), - cancelButtonText: t('No'), - closeOnConfirm: true, - html: false, - }, - async (confirmed) => { - if (!confirmed) { - return; - } - - try { - await removeInvite(); - onRemove && onRemove(_id); - } catch (error) { - dispatchToastMessage({ type: 'error', message: error }); - } - }, - ); - }; - - const notSmall = useMediaQuery('(min-width: 768px)'); - - return ( - - - - {_id} - - - {notSmall && ( - <> - {formatDateAndTime(createdAt)} - {daysToExpire({ expires, days })} - {uses} - {maxUsesLeft({ maxUses, uses })} - - )} - - - - - ); -} - -export default InviteRow; diff --git a/client/views/admin/invites/InviteRow.tsx b/client/views/admin/invites/InviteRow.tsx new file mode 100644 index 0000000000000..42521a89aa239 --- /dev/null +++ b/client/views/admin/invites/InviteRow.tsx @@ -0,0 +1,96 @@ +import { Button, Icon, Box } from '@rocket.chat/fuselage'; +import { useMediaQuery } from '@rocket.chat/fuselage-hooks'; +import React, { ReactElement, MouseEvent } from 'react'; + +import { IInvite } from '../../../../definition/IInvite'; +import { GenericTableCell, GenericTableRow } from '../../../components/GenericTable'; +import { useEndpoint } from '../../../contexts/ServerContext'; +import { useTranslation } from '../../../contexts/TranslationContext'; +import { useFormatDateAndTime } from '../../../hooks/useFormatDateAndTime'; +import { useTimeFromNow } from '../../../hooks/useTimeFromNow'; + +const isExpired = (expires: IInvite['expires']): boolean => { + if (expires && expires.getTime() < new Date().getTime()) { + return true; + } + + return false; +}; + +type InviteRowProps = Omit & { + onRemove: (removeInvite: () => void) => void; + _updatedAt: string; + createdAt: string; + expires: string | null; +}; + +const InviteRow = ({ + _id, + createdAt, + expires, + uses, + maxUses, + onRemove, +}: InviteRowProps): ReactElement => { + const t = useTranslation(); + const formatDateAndTime = useFormatDateAndTime(); + const removeInvite = useEndpoint('DELETE', `removeInvite/${_id}`); + + const getTimeFromNow = useTimeFromNow(false); + + const daysToExpire = (expires: IInvite['expires']): string => { + if (expires) { + if (isExpired(expires)) { + return t('Expired'); + } + + return getTimeFromNow(expires); + } + + return t('Never'); + }; + + const maxUsesLeft = (maxUses: IInvite['maxUses'], uses: IInvite['uses']): number | string => { + if (maxUses > 0) { + if (uses >= maxUses) { + return 0; + } + + return maxUses - uses; + } + + return t('Unlimited'); + }; + + const handleRemoveButtonClick = async (event: MouseEvent): Promise => { + event.stopPropagation(); + onRemove(removeInvite); + }; + + const notSmall = useMediaQuery('(min-width: 768px)'); + + return ( + + + + {_id} + + + {notSmall && ( + <> + {formatDateAndTime(new Date(createdAt))} + {daysToExpire(expires ? new Date(expires) : null)} + {uses} + {maxUsesLeft(maxUses, uses)} + + )} + + + + + ); +}; + +export default InviteRow; diff --git a/client/views/admin/invites/InvitesPage.js b/client/views/admin/invites/InvitesPage.js deleted file mode 100644 index cdb321e138cf0..0000000000000 --- a/client/views/admin/invites/InvitesPage.js +++ /dev/null @@ -1,79 +0,0 @@ -import { Table } from '@rocket.chat/fuselage'; -import { useMediaQuery } from '@rocket.chat/fuselage-hooks'; -import React, { useState, useEffect } from 'react'; - -import GenericTable from '../../../components/GenericTable'; -import Page from '../../../components/Page'; -import { useEndpoint } from '../../../contexts/ServerContext'; -import { useTranslation } from '../../../contexts/TranslationContext'; -import InviteRow from './InviteRow'; - -function InvitesPage() { - const t = useTranslation(); - - const [invites, setInvites] = useState([]); - - const listInvites = useEndpoint('GET', 'listInvites'); - - useEffect(() => { - const loadInvites = async () => { - const result = (await listInvites()) || []; - - const invites = result.map((data) => ({ - ...data, - createdAt: new Date(data.createdAt), - expires: data.expires ? new Date(data.expires) : '', - })); - - setInvites(invites); - }; - - loadInvites(); - }, [listInvites]); - - const handleInviteRemove = (_id) => { - setInvites((invites = []) => invites.filter((invite) => invite._id !== _id)); - }; - - const notSmall = useMediaQuery('(min-width: 768px)'); - - return ( - - - - - - {t('Token')} - - {notSmall && ( - <> - - {t('Created_at')} - - - {t('Expiration')} - - - {t('Uses')} - - - {t('Uses_left')} - - - )} - - - } - renderRow={(invite) => ( - - )} - /> - - - ); -} - -export default InvitesPage; diff --git a/client/views/admin/invites/InvitesPage.tsx b/client/views/admin/invites/InvitesPage.tsx new file mode 100644 index 0000000000000..2be2d90c9496f --- /dev/null +++ b/client/views/admin/invites/InvitesPage.tsx @@ -0,0 +1,91 @@ +import { useMediaQuery } from '@rocket.chat/fuselage-hooks'; +import React, { ReactElement } from 'react'; + +import GenericModal from '../../../components/GenericModal'; +import { + GenericTable, + GenericTableBody, + GenericTableHeader, + GenericTableHeaderCell, + GenericTableLoadingTable, +} from '../../../components/GenericTable'; +import Page from '../../../components/Page'; +import { useSetModal } from '../../../contexts/ModalContext'; +import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext'; +import { useTranslation } from '../../../contexts/TranslationContext'; +import { useEndpointData } from '../../../hooks/useEndpointData'; +import { AsyncStatePhase } from '../../../lib/asyncState'; +import InviteRow from './InviteRow'; + +const InvitesPage = (): ReactElement => { + const t = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + const setModal = useSetModal(); + + const { phase, value, reload } = useEndpointData('listInvites'); + + const onRemove = (removeInvite: () => void): void => { + const confirmRemove = async (): Promise => { + try { + await removeInvite(); + dispatchToastMessage({ type: 'success', message: t('Invite_removed') }); + reload(); + } catch (error) { + if (typeof error === 'string' || error instanceof Error) { + dispatchToastMessage({ type: 'error', message: error }); + } + } finally { + setModal(); + } + }; + + setModal( + setModal()} + onCancel={(): void => setModal()} + onConfirm={confirmRemove} + />, + ); + }; + + const notSmall = useMediaQuery('(min-width: 768px)'); + + return ( + + + + + + + {t('Token')} + + {notSmall && ( + <> + {t('Created_at')} + {t('Expiration')} + {t('Uses')} + {t('Uses_left')} + + )} + + + + {phase === AsyncStatePhase.LOADING && ( + + )} + {phase === AsyncStatePhase.RESOLVED && + Array.isArray(value) && + value.map((invite) => )} + + + + + ); +}; + +export default InvitesPage; diff --git a/client/views/admin/invites/InvitesRoute.js b/client/views/admin/invites/InvitesRoute.tsx similarity index 80% rename from client/views/admin/invites/InvitesRoute.js rename to client/views/admin/invites/InvitesRoute.tsx index c5b7b2e492df2..258ce90c487b5 100644 --- a/client/views/admin/invites/InvitesRoute.js +++ b/client/views/admin/invites/InvitesRoute.tsx @@ -1,10 +1,10 @@ -import React from 'react'; +import React, { ReactElement } from 'react'; import NotAuthorizedPage from '../../../components/NotAuthorizedPage'; import { usePermission } from '../../../contexts/AuthorizationContext'; import InvitesPage from './InvitesPage'; -function InvitesRoute() { +const InvitesRoute = (): ReactElement => { const canCreateInviteLinks = usePermission('create-invite-links'); if (!canCreateInviteLinks) { @@ -12,6 +12,6 @@ function InvitesRoute() { } return ; -} +}; export default InvitesRoute; diff --git a/definition/IInvite.ts b/definition/IInvite.ts index b02a02576257d..e9604f6e22f13 100644 --- a/definition/IInvite.ts +++ b/definition/IInvite.ts @@ -6,6 +6,6 @@ export interface IInvite extends IRocketChatRecord { rid: string; userId: string; createdAt: Date; - expires: Date; + expires: Date | null; uses: number; } diff --git a/definition/rest/index.ts b/definition/rest/index.ts index 05d803a89c5a7..90a828fe65488 100644 --- a/definition/rest/index.ts +++ b/definition/rest/index.ts @@ -13,6 +13,7 @@ import type { EmojiCustomEndpoints } from './v1/emojiCustom'; import type { GroupsEndpoints } from './v1/groups'; import type { ImEndpoints } from './v1/im'; import type { InstancesEndpoints } from './v1/instances'; +import type { InvitesEndpoints } from './v1/invites'; import type { LDAPEndpoints } from './v1/ldap'; import type { LicensesEndpoints } from './v1/licenses'; import type { MiscEndpoints } from './v1/misc'; @@ -47,7 +48,8 @@ type CommunityEndpoints = BannersEndpoints & LicensesEndpoints & MiscEndpoints & PermissionsEndpoints & - InstancesEndpoints; + InstancesEndpoints & + InvitesEndpoints; type Endpoints = CommunityEndpoints & EnterpriseEndpoints; diff --git a/definition/rest/v1/invites.ts b/definition/rest/v1/invites.ts new file mode 100644 index 0000000000000..6c65b09b65419 --- /dev/null +++ b/definition/rest/v1/invites.ts @@ -0,0 +1,10 @@ +import { IInvite } from '../../IInvite'; + +export type InvitesEndpoints = { + 'listInvites': { + GET: () => Array; + }; + 'removeInvite/:_id': { + DELETE: () => void; + }; +}; diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index ce54f0dcc6cad..7cdf7ea5cca93 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -1818,6 +1818,7 @@ "Exit_Full_Screen": "Exit Full Screen", "Expand": "Expand", "Experimental_Feature_Alert": "This is an experimental feature! Please be aware that it may change, break, or even be removed in the future without any notice.", + "Expired": "Expired", "Expiration": "Expiration", "Expiration_(Days)": "Expiration (Days)", "Export_as_file": "Export as file", @@ -2309,7 +2310,9 @@ "Invitation_Subject": "Invitation Subject", "Invitation_Subject_Default": "You have been invited to [Site_Name]", "Invite": "Invite", + "Invites": "Invites", "Invite_Link": "Invite Link", + "Invite_removed": "Invite removed successfully", "Invite_user_to_join_channel": "Invite one user to join this channel", "Invite_user_to_join_channel_all_from": "Invite all users from [#channel] to join this channel", "Invite_user_to_join_channel_all_to": "Invite all users from this channel to join [#channel]", From 9e7fcb12369b903b542732e4a60fce45c0505163 Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Thu, 2 Dec 2021 20:51:37 -0300 Subject: [PATCH 129/137] [FIX] Missing custom user status ellipsis (#23831) --- app/ui-utils/client/lib/AccountBox.d.ts | 13 +++ app/user-status/client/index.js | 2 +- app/user-status/client/lib/userStatus.js | 36 -------- app/user-status/client/lib/userStatus.ts | 52 +++++++++++ client/components/UserStatus/Away.js | 7 -- client/components/UserStatus/Away.tsx | 7 ++ client/components/UserStatus/Busy.js | 7 -- client/components/UserStatus/Busy.tsx | 7 ++ client/components/UserStatus/Loading.js | 7 -- client/components/UserStatus/Loading.tsx | 7 ++ client/components/UserStatus/Offline.js | 7 -- client/components/UserStatus/Offline.tsx | 7 ++ client/components/UserStatus/Online.js | 7 -- client/components/UserStatus/Online.tsx | 7 ++ ...veUserStatus.js => ReactiveUserStatus.tsx} | 11 ++- .../{UserDropdown.js => UserDropdown.tsx} | 89 ++++++++++++------- client/views/room/Header/ToolBox/ToolBox.tsx | 4 +- package-lock.json | 79 ++++++++-------- package.json | 6 +- packages/rocketchat-i18n/i18n/en.i18n.json | 1 + 20 files changed, 214 insertions(+), 149 deletions(-) create mode 100644 app/ui-utils/client/lib/AccountBox.d.ts delete mode 100644 app/user-status/client/lib/userStatus.js create mode 100644 app/user-status/client/lib/userStatus.ts delete mode 100644 client/components/UserStatus/Away.js create mode 100644 client/components/UserStatus/Away.tsx delete mode 100644 client/components/UserStatus/Busy.js create mode 100644 client/components/UserStatus/Busy.tsx delete mode 100644 client/components/UserStatus/Loading.js create mode 100644 client/components/UserStatus/Loading.tsx delete mode 100644 client/components/UserStatus/Offline.js create mode 100644 client/components/UserStatus/Offline.tsx delete mode 100644 client/components/UserStatus/Online.js create mode 100644 client/components/UserStatus/Online.tsx rename client/components/UserStatus/{ReactiveUserStatus.js => ReactiveUserStatus.tsx} (51%) rename client/sidebar/header/{UserDropdown.js => UserDropdown.tsx} (71%) diff --git a/app/ui-utils/client/lib/AccountBox.d.ts b/app/ui-utils/client/lib/AccountBox.d.ts new file mode 100644 index 0000000000000..71217df759c63 --- /dev/null +++ b/app/ui-utils/client/lib/AccountBox.d.ts @@ -0,0 +1,13 @@ +import { IUser } from '../../../../definition/IUser'; +import { TranslationKey } from '../../../../client/contexts/TranslationContext'; + +export declare const AccountBox: { + setStatus: (status: IUser['status'], statusText?: IUser['statusText']) => void; + getItems: () => Array<{ + condition: () => boolean; + name: TranslationKey; + icon: string; + sideNav: string; + href: string; + }>; +}; diff --git a/app/user-status/client/index.js b/app/user-status/client/index.js index 6c9a73b43f5da..ecada9a17f9fa 100644 --- a/app/user-status/client/index.js +++ b/app/user-status/client/index.js @@ -3,5 +3,5 @@ import './admin/startup'; import './notifications/deleteCustomUserStatus'; import './notifications/updateCustomUserStatus'; -export { userStatus } from './lib/userStatus'; +export { userStatus, UserStatusProps } from './lib/userStatus'; export { deleteCustomUserStatus, updateCustomUserStatus } from './lib/customUserStatus'; diff --git a/app/user-status/client/lib/userStatus.js b/app/user-status/client/lib/userStatus.js deleted file mode 100644 index 71fded4e86d5b..0000000000000 --- a/app/user-status/client/lib/userStatus.js +++ /dev/null @@ -1,36 +0,0 @@ -export const userStatus = { - packages: { - base: { - render(html) { - return html; - }, - }, - }, - - list: { - online: { - name: 'online', - localizeName: true, - id: 'online', - statusType: 'online', - }, - away: { - name: 'away', - localizeName: true, - id: 'away', - statusType: 'away', - }, - busy: { - name: 'busy', - localizeName: true, - id: 'busy', - statusType: 'busy', - }, - invisible: { - name: 'invisible', - localizeName: true, - id: 'offline', - statusType: 'offline', - }, - }, -}; diff --git a/app/user-status/client/lib/userStatus.ts b/app/user-status/client/lib/userStatus.ts new file mode 100644 index 0000000000000..1eb824d169fc1 --- /dev/null +++ b/app/user-status/client/lib/userStatus.ts @@ -0,0 +1,52 @@ +import { UserStatus } from '../../../../definition/UserStatus'; + +type Status = { + name: string; + localizeName: boolean; + id: string; + statusType: UserStatus; +}; + +type UserStatusTypes = { + packages: any; + list: { + [status: string]: Status; + }; +} + +export const userStatus: UserStatusTypes = { + packages: { + base: { + render(html: string): string { + return html; + }, + }, + }, + + list: { + online: { + name: UserStatus.ONLINE, + localizeName: true, + id: UserStatus.ONLINE, + statusType: UserStatus.ONLINE, + }, + away: { + name: UserStatus.AWAY, + localizeName: true, + id: UserStatus.AWAY, + statusType: UserStatus.AWAY, + }, + busy: { + name: UserStatus.BUSY, + localizeName: true, + id: UserStatus.BUSY, + statusType: UserStatus.BUSY, + }, + invisible: { + name: UserStatus.OFFLINE, + localizeName: true, + id: UserStatus.OFFLINE, + statusType: UserStatus.OFFLINE, + }, + }, +}; diff --git a/client/components/UserStatus/Away.js b/client/components/UserStatus/Away.js deleted file mode 100644 index b1240ee485a11..0000000000000 --- a/client/components/UserStatus/Away.js +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react'; - -import UserStatus from './UserStatus'; - -const Away = (props) => ; - -export default Away; diff --git a/client/components/UserStatus/Away.tsx b/client/components/UserStatus/Away.tsx new file mode 100644 index 0000000000000..b4d16c96e5a14 --- /dev/null +++ b/client/components/UserStatus/Away.tsx @@ -0,0 +1,7 @@ +import React, { FC } from 'react'; + +import UserStatus from './UserStatus'; + +const Away: FC = (props) => ; + +export default Away; diff --git a/client/components/UserStatus/Busy.js b/client/components/UserStatus/Busy.js deleted file mode 100644 index 432f562279ef4..0000000000000 --- a/client/components/UserStatus/Busy.js +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react'; - -import UserStatus from './UserStatus'; - -const Busy = (props) => ; - -export default Busy; diff --git a/client/components/UserStatus/Busy.tsx b/client/components/UserStatus/Busy.tsx new file mode 100644 index 0000000000000..460b30945e55d --- /dev/null +++ b/client/components/UserStatus/Busy.tsx @@ -0,0 +1,7 @@ +import React, { FC } from 'react'; + +import UserStatus from './UserStatus'; + +const Busy: FC = (props) => ; + +export default Busy; diff --git a/client/components/UserStatus/Loading.js b/client/components/UserStatus/Loading.js deleted file mode 100644 index 429cf6b1f64f7..0000000000000 --- a/client/components/UserStatus/Loading.js +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react'; - -import UserStatus from './UserStatus'; - -const Loading = (props) => ; - -export default Loading; diff --git a/client/components/UserStatus/Loading.tsx b/client/components/UserStatus/Loading.tsx new file mode 100644 index 0000000000000..85f6b3aeae711 --- /dev/null +++ b/client/components/UserStatus/Loading.tsx @@ -0,0 +1,7 @@ +import React, { FC } from 'react'; + +import UserStatus from './UserStatus'; + +const Loading: FC = (props) => ; + +export default Loading; diff --git a/client/components/UserStatus/Offline.js b/client/components/UserStatus/Offline.js deleted file mode 100644 index 16d4365d148f5..0000000000000 --- a/client/components/UserStatus/Offline.js +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react'; - -import UserStatus from './UserStatus'; - -const Offline = (props) => ; - -export default Offline; diff --git a/client/components/UserStatus/Offline.tsx b/client/components/UserStatus/Offline.tsx new file mode 100644 index 0000000000000..3724c02eb0725 --- /dev/null +++ b/client/components/UserStatus/Offline.tsx @@ -0,0 +1,7 @@ +import React, { FC } from 'react'; + +import UserStatus from './UserStatus'; + +const Offline: FC = (props) => ; + +export default Offline; diff --git a/client/components/UserStatus/Online.js b/client/components/UserStatus/Online.js deleted file mode 100644 index 13190b49bd68a..0000000000000 --- a/client/components/UserStatus/Online.js +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react'; - -import UserStatus from './UserStatus'; - -const Online = (props) => ; - -export default Online; diff --git a/client/components/UserStatus/Online.tsx b/client/components/UserStatus/Online.tsx new file mode 100644 index 0000000000000..e80afb8d69d54 --- /dev/null +++ b/client/components/UserStatus/Online.tsx @@ -0,0 +1,7 @@ +import React, { FC } from 'react'; + +import UserStatus from './UserStatus'; + +const Online: FC = (props) => ; + +export default Online; diff --git a/client/components/UserStatus/ReactiveUserStatus.js b/client/components/UserStatus/ReactiveUserStatus.tsx similarity index 51% rename from client/components/UserStatus/ReactiveUserStatus.js rename to client/components/UserStatus/ReactiveUserStatus.tsx index 30a0b19242b5c..8fceb19bff716 100644 --- a/client/components/UserStatus/ReactiveUserStatus.js +++ b/client/components/UserStatus/ReactiveUserStatus.tsx @@ -1,9 +1,16 @@ -import React, { memo } from 'react'; +import React, { memo, ReactElement } from 'react'; +import { IUser } from '../../../definition/IUser'; import { usePresence } from '../../hooks/usePresence'; import UserStatus from './UserStatus'; -const ReactiveUserStatus = ({ uid, ...props }) => { +const ReactiveUserStatus = ({ + uid, + ...props +}: { + uid: IUser['_id']; + props: typeof UserStatus; +}): ReactElement => { const status = usePresence(uid)?.status; return ; }; diff --git a/client/sidebar/header/UserDropdown.js b/client/sidebar/header/UserDropdown.tsx similarity index 71% rename from client/sidebar/header/UserDropdown.js rename to client/sidebar/header/UserDropdown.tsx index 9ce7f5ce87730..bccce0352a39b 100644 --- a/client/sidebar/header/UserDropdown.js +++ b/client/sidebar/header/UserDropdown.tsx @@ -1,11 +1,13 @@ import { Box, Margins, Divider, Option } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { FlowRouter } from 'meteor/kadira:flow-router'; -import React from 'react'; +import React, { ReactElement } from 'react'; import { callbacks } from '../../../app/callbacks/lib/callbacks'; import { popover, AccountBox, SideNav } from '../../../app/ui-utils/client'; import { userStatus } from '../../../app/user-status/client'; +import { IUser } from '../../../definition/IUser'; +import { UserStatus as UserStatusEnum } from '../../../definition/UserStatus'; import MarkdownText from '../../components/MarkdownText'; import { UserStatus } from '../../components/UserStatus'; import UserAvatar from '../../components/avatar/UserAvatar'; @@ -16,6 +18,7 @@ import { useSetting } from '../../contexts/SettingsContext'; import { useTranslation } from '../../contexts/TranslationContext'; import { useLogout } from '../../contexts/UserContext'; import { useReactiveValue } from '../../hooks/useReactiveValue'; +import { useUserDisplayName } from '../../hooks/useUserDisplayName'; import { imperativeModal } from '../../lib/imperativeModal'; import EditStatusModal from './EditStatusModal'; @@ -37,32 +40,41 @@ const ADMIN_PERMISSIONS = [ 'view-engagement-dashboard', ]; -const style = { - marginLeft: '-16px', - marginRight: '-16px', -}; - -const setStatus = (status) => { +const setStatus = (status: IUser['status']): void => { AccountBox.setStatus(status); callbacks.run('userStatusManuallySet', status); }; -const getItems = () => AccountBox.getItems(); +const getItems = (): ReturnType => AccountBox.getItems(); + +const translateStatusName = (t: ReturnType, name: string): string => { + const isDefaultStatus = (name: string): name is UserStatusEnum => name in UserStatusEnum; + if (isDefaultStatus(name)) { + return t(name); + } + + return name; +}; + +type UserDropdownProps = { + user: IUser; + onClose: () => void; +}; -const UserDropdown = ({ user, onClose }) => { +const UserDropdown = ({ user, onClose }: UserDropdownProps): ReactElement => { const t = useTranslation(); const accountRoute = useRoute('account'); const adminRoute = useRoute('admin'); + const logout = useLogout(); const { sidebar } = useLayout(); - const logout = useLogout(); + const { username, avatarETag, status, statusText } = user; - const { name, username, avatarETag, status, statusText } = user; + const displayName = useUserDisplayName(user); - const useRealName = useSetting('UI_Use_Real_Name'); const filterInvisibleStatus = !useSetting('Accounts_AllowInvisibleStatusOption') - ? (key) => userStatus.list[key].name !== 'invisible' - : () => true; + ? (key: keyof typeof userStatus['list']): boolean => userStatus.list[key].name !== 'invisible' + : (): boolean => true; const showAdmin = useAtLeastOnePermission(ADMIN_PERMISSIONS); @@ -97,7 +109,7 @@ const UserDropdown = ({ user, onClose }) => { - + { - {useRealName ? name || username : username} + {displayName} - - + + - -
        + {t('Status')} @@ -133,16 +148,16 @@ const UserDropdown = ({ user, onClose }) => { .filter(filterInvisibleStatus) .map((key, i) => { const status = userStatus.list[key]; - const name = status.localizeName ? t(status.name) : status.name; + const name = status.localizeName ? translateStatusName(t, status.name) : status.name; const modifier = status.statusType || user.status; return ( ); })} -
        + +
        {(accountBoxItems.length || showAdmin) && ( <> -
        + {showAdmin && ( - )} {accountBoxItems.map((item, i) => { let action; if (item.href || item.sideNav) { - action = () => { + action = (): void => { if (item.href) { FlowRouter.go(item.href); popover.close(); @@ -178,17 +197,19 @@ const UserDropdown = ({ user, onClose }) => { }; } - return + ); })} -
        +
        )} -
        -
        + + + +
        ); }; diff --git a/client/views/room/Header/ToolBox/ToolBox.tsx b/client/views/room/Header/ToolBox/ToolBox.tsx index 07c8ede8d74fd..354e7086ff088 100644 --- a/client/views/room/Header/ToolBox/ToolBox.tsx +++ b/client/views/room/Header/ToolBox/ToolBox.tsx @@ -102,8 +102,8 @@ const ToolBox: FC = ({ className }) => { aria-keyshortcuts='alt' tabIndex={-1} options={hiddenActions} - renderItem={(props): ReactNode => - props.id && hiddenActionRenderers.current[props.id](props) + renderItem={({ value, ...props }): ReactNode => + props.id && hiddenActionRenderers.current[value || ''](props) } /> )} diff --git a/package-lock.json b/package-lock.json index 5151742de941c..7b98464003cb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5363,53 +5363,58 @@ } }, "@rocket.chat/fuselage": { - "version": "0.6.3-dev.357", - "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage/-/fuselage-0.6.3-dev.357.tgz", - "integrity": "sha512-8pW5NzSgn0dy+Zo+ebuXEhsJP6ByIXlH0OOr23tfIegMQQz/rULPZvK66E+PxExNv+nFQ+X/HemcmkEi1/BNbw==", - "requires": { - "@rocket.chat/css-in-js": "^0.6.3-dev.357+eda6fa37", - "@rocket.chat/css-supports": "^0.6.3-dev.357+eda6fa37", - "@rocket.chat/fuselage-tokens": "^0.6.3-dev.357+eda6fa37", - "@rocket.chat/memo": "^0.6.3-dev.357+eda6fa37", + "version": "0.6.3-dev.364", + "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage/-/fuselage-0.6.3-dev.364.tgz", + "integrity": "sha512-E391ik+GHVSiedvUQNA/rRD2P1JsPyO2ktvJGMN7z0ExolGUgVtth577RerVzyjH/VcI3LM5e7FeYBdn33VKEA==", + "requires": { + "@rocket.chat/css-in-js": "^0.6.3-dev.364+c2b02aa4", + "@rocket.chat/css-supports": "^0.6.3-dev.364+c2b02aa4", + "@rocket.chat/fuselage-tokens": "^0.6.3-dev.364+c2b02aa4", + "@rocket.chat/memo": "^0.6.3-dev.364+c2b02aa4", "invariant": "^2.2.4", "react-keyed-flatten-children": "^1.3.0" }, "dependencies": { "@rocket.chat/css-in-js": { - "version": "0.6.3-dev.357", - "resolved": "https://registry.npmjs.org/@rocket.chat/css-in-js/-/css-in-js-0.6.3-dev.357.tgz", - "integrity": "sha512-SMTb2/XtLOrMiuNnviBLfhOp00IJ+PZB1GYUh4qIN0+2j4pv9VKJ+oUgX9s1Gumzrrh3O/Y1mEGl7V02KbXyYg==", + "version": "0.6.3-dev.364", + "resolved": "https://registry.npmjs.org/@rocket.chat/css-in-js/-/css-in-js-0.6.3-dev.364.tgz", + "integrity": "sha512-NHdVoFzESe4S9sqUX8BPbUzxX8xxv4iguHnqzLhomglnLhd5Vb6ND6H4K00tbHC2G9V50sXDAM7efN5xhHb7sw==", "requires": { "@emotion/hash": "^0.8.0", - "@rocket.chat/css-supports": "^0.6.3-dev.357+eda6fa37", - "@rocket.chat/memo": "^0.6.3-dev.357+eda6fa37", - "@rocket.chat/stylis-logical-props-middleware": "^0.6.3-dev.357+eda6fa37", + "@rocket.chat/css-supports": "^0.6.3-dev.364+c2b02aa4", + "@rocket.chat/memo": "^0.6.3-dev.364+c2b02aa4", + "@rocket.chat/stylis-logical-props-middleware": "^0.6.3-dev.364+c2b02aa4", "stylis": "^4.0.10" } }, "@rocket.chat/css-supports": { - "version": "0.6.3-dev.357", - "resolved": "https://registry.npmjs.org/@rocket.chat/css-supports/-/css-supports-0.6.3-dev.357.tgz", - "integrity": "sha512-SXvr/0of9EV38HCYRGs5fsFnCDmRDUBE/TUXa08btara/aiPSAVrCfrLDnZocqIoc/+8JTgZb28EyQql1Hm1TA==", + "version": "0.6.3-dev.364", + "resolved": "https://registry.npmjs.org/@rocket.chat/css-supports/-/css-supports-0.6.3-dev.364.tgz", + "integrity": "sha512-GHzTZRG5nkvqtakGJnmOKUcl2Oi+zhDYTszzPnEyQI6kLMjLRkA9tK3Pqn2BO1AE03JJnA7InojjzYwPs4vgJA==", "requires": { - "@rocket.chat/memo": "^0.6.3-dev.357+eda6fa37", + "@rocket.chat/memo": "^0.6.3-dev.364+c2b02aa4", "tslib": "^2.3.1" } }, + "@rocket.chat/fuselage-tokens": { + "version": "0.6.3-dev.364", + "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-tokens/-/fuselage-tokens-0.6.3-dev.364.tgz", + "integrity": "sha512-n0LgpvQWkACOLc0E3j8X0UEO6VHlVXgDWgN4yIIQfNT2FI0OUxhoCoedYFymLxg3E+DmUrI3EMr76O5Gp1AbLg==" + }, "@rocket.chat/memo": { - "version": "0.6.3-dev.357", - "resolved": "https://registry.npmjs.org/@rocket.chat/memo/-/memo-0.6.3-dev.357.tgz", - "integrity": "sha512-mHct/JufxozOBUylaCresEpwNR9/Ad9L3tU/avt+MzrVi4nbqr9X4tZw2Nqz7uaHfyzJQyqye1KECqX/3t2IiA==", + "version": "0.6.3-dev.364", + "resolved": "https://registry.npmjs.org/@rocket.chat/memo/-/memo-0.6.3-dev.364.tgz", + "integrity": "sha512-vF7drq9tjcH8e0fbWYVkIJsCrNvTrtnC+ZgLgFDqyjWvOJWrw33QkVg7ytb0aCGXpR+YSCtDJ9gAAmE4ho4pkg==", "requires": { "tslib": "^2.3.1" } }, "@rocket.chat/stylis-logical-props-middleware": { - "version": "0.6.3-dev.357", - "resolved": "https://registry.npmjs.org/@rocket.chat/stylis-logical-props-middleware/-/stylis-logical-props-middleware-0.6.3-dev.357.tgz", - "integrity": "sha512-RiFGdIIUKOcjTMfuC2DlJUfR0QlpyYft8awUimGqahxzX1QmWh9mzwuYo5Y0bmyrRySJU5g3KJx8OcS1pvSoOQ==", + "version": "0.6.3-dev.364", + "resolved": "https://registry.npmjs.org/@rocket.chat/stylis-logical-props-middleware/-/stylis-logical-props-middleware-0.6.3-dev.364.tgz", + "integrity": "sha512-kW+17VjAmumvhIxrKpLNw0O3S1wjROOKCtPqhzsvIEU5ONqaOV4xzc8z4kX6SmXLaOj/TYhbKRNrivblAP1bOw==", "requires": { - "@rocket.chat/css-supports": "^0.6.3-dev.357+eda6fa37", + "@rocket.chat/css-supports": "^0.6.3-dev.364+c2b02aa4", "tslib": "^2.3.1" } }, @@ -5421,9 +5426,9 @@ } }, "@rocket.chat/fuselage-hooks": { - "version": "0.6.3-dev.358", - "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-hooks/-/fuselage-hooks-0.6.3-dev.358.tgz", - "integrity": "sha512-P5iBg7e0GBi+GPWgqrFJoN1CbraeisofMENz1MzqtXX5krW/ar+gQOUxXhsjMYdRQYy4wiGmqv7Y50xRCNqipw==" + "version": "0.6.3-dev.362", + "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-hooks/-/fuselage-hooks-0.6.3-dev.362.tgz", + "integrity": "sha512-2gmNbRRLFMdbUBLZdOaczRG+5t95JskfzFYIxpkRnUkP8xqUu1a0eUFx1dDjvMHKX/k4NhytCNY/VOKemQC1mg==" }, "@rocket.chat/fuselage-polyfills": { "version": "0.30.1", @@ -5439,9 +5444,9 @@ } }, "@rocket.chat/fuselage-tokens": { - "version": "0.6.3-dev.357", - "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-tokens/-/fuselage-tokens-0.6.3-dev.357.tgz", - "integrity": "sha512-sxI2CLyxDTpFUP8j+BJ8DCFmmxq6x24eiagzNM/ii1HjAyS16hIRCOydYY6h0MG7EtA95BLV7nowRPk1D2dA0Q==" + "version": "0.6.3-dev.362", + "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-tokens/-/fuselage-tokens-0.6.3-dev.362.tgz", + "integrity": "sha512-VsU6bb/NJHSQPn4/9J17nPUYlZe1xzJdTwLwD6Pu583I3GoK27ec3p1ACPQU7qfJbktTAIlMczC+r6B7PJDTdg==" }, "@rocket.chat/fuselage-ui-kit": { "version": "0.30.1", @@ -5466,6 +5471,13 @@ "@rocket.chat/memo": "^0.30.0", "invariant": "^2.2.4", "react-keyed-flatten-children": "^1.3.0" + }, + "dependencies": { + "@rocket.chat/fuselage-tokens": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-tokens/-/fuselage-tokens-0.30.1.tgz", + "integrity": "sha512-bnwuNahkG9+EHYjZ8IpKqjxRAHnWHpQGZWwLaLE0XKt7xP8B/ubEQeV7PLDYI0KPJ5oWJCkk8CSf4FGJ9gSyxg==" + } } }, "@rocket.chat/fuselage-hooks": { @@ -5473,11 +5485,6 @@ "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-hooks/-/fuselage-hooks-0.30.1.tgz", "integrity": "sha512-9mfe8yx9MjgjYGDKctHqDkV5Byb5PuAXDter1sl37w/Y9gk8TB1tohU+bF88YthBYSLnqBaNWZMsO2zpKuTGiQ==" }, - "@rocket.chat/fuselage-tokens": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-tokens/-/fuselage-tokens-0.30.1.tgz", - "integrity": "sha512-bnwuNahkG9+EHYjZ8IpKqjxRAHnWHpQGZWwLaLE0XKt7xP8B/ubEQeV7PLDYI0KPJ5oWJCkk8CSf4FGJ9gSyxg==" - }, "tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", diff --git a/package.json b/package.json index bf08ea48e1e36..a3aeb84d6e056 100644 --- a/package.json +++ b/package.json @@ -174,10 +174,10 @@ "@rocket.chat/apps-engine": "^1.28.1", "@rocket.chat/css-in-js": "^0.30.1", "@rocket.chat/emitter": "^0.30.1", - "@rocket.chat/fuselage": "^0.6.3-dev.357", - "@rocket.chat/fuselage-hooks": "^0.6.3-dev.358", + "@rocket.chat/fuselage": "^0.6.3-dev.364", + "@rocket.chat/fuselage-hooks": "^0.6.3-dev.362", "@rocket.chat/fuselage-polyfills": "^0.30.1", - "@rocket.chat/fuselage-tokens": "^0.6.3-dev.357", + "@rocket.chat/fuselage-tokens": "^0.6.3-dev.362", "@rocket.chat/fuselage-ui-kit": "^0.30.1", "@rocket.chat/icons": "^0.30.1", "@rocket.chat/logo": "^0.30.1", diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 7cdf7ea5cca93..6c0f86946c635 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -3177,6 +3177,7 @@ "Office_Hours": "Office Hours", "Office_hours_enabled": "Office Hours Enabled", "Office_hours_updated": "Office hours updated", + "offline": "offline", "Offline": "Offline", "Offline_DM_Email": "Direct Message Email Subject", "Offline_Email_Subject_Description": "You may use the following placeholders:
        • [Site_Name], [Site_URL], [User] & [Room] for the Application Name, URL, Username & Roomname respectively.
        ", From cf04144df65c9159f01f290b9d37a5021c25d790 Mon Sep 17 00:00:00 2001 From: Guillaume Colson Date: Fri, 3 Dec 2021 14:59:55 +0100 Subject: [PATCH 130/137] [FIX] Add CSP to authorize auto-close of CAS login window (#23215) * [FIX] Add CSP to authorize auto-close of CAS login window * Update cors.js Co-authored-by: pierre-lehnen-rc <55164754+pierre-lehnen-rc@users.noreply.github.com> --- app/cors/server/cors.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/cors/server/cors.js b/app/cors/server/cors.js index 80a1f7a385ab0..f2c63ee441a61 100644 --- a/app/cors/server/cors.js +++ b/app/cors/server/cors.js @@ -31,6 +31,11 @@ WebApp.rawConnectHandlers.use(function(req, res, next) { settings.get('CDN_PREFIX_ALL') ? null : settings.get('CDN_JSCSS_PREFIX'), ].filter(Boolean).join(' '); + const inlineHashes = [ + // Hash for `window.close()`, required by the CAS login popup. + "'sha256-jqxtvDkBbRAl9Hpqv68WdNOieepg8tJSYu1xIy7zT34='", + ].filter(Boolean).join(' '); + res.setHeader( 'Content-Security-Policy', [ @@ -40,7 +45,7 @@ WebApp.rawConnectHandlers.use(function(req, res, next) { 'frame-src *', 'img-src * data:', 'media-src * data:', - `script-src 'self' 'unsafe-eval' ${ cdn_prefixes }`, + `script-src 'self' 'unsafe-eval' ${ inlineHashes } ${ cdn_prefixes }`, `style-src 'self' 'unsafe-inline' ${ cdn_prefixes }`, ].join('; '), ); From eb84220c8435324fb3f208bc632bb27744062ebb Mon Sep 17 00:00:00 2001 From: Tiago Evangelista Pinto Date: Fri, 3 Dec 2021 12:34:37 -0300 Subject: [PATCH 131/137] [NEW] Create new setting to clean local storage at end of chats(#23821) --- app/livechat/server/api/lib/livechat.js | 3 ++- app/livechat/server/config.ts | 9 +++++++++ app/livechat/server/lib/Livechat.js | 1 + app/livechat/server/methods/getInitialData.js | 4 +++- packages/rocketchat-i18n/i18n/en.i18n.json | 1 + 5 files changed, 16 insertions(+), 2 deletions(-) diff --git a/app/livechat/server/api/lib/livechat.js b/app/livechat/server/api/lib/livechat.js index b374e5139a99a..a8abb9115851c 100644 --- a/app/livechat/server/api/lib/livechat.js +++ b/app/livechat/server/api/lib/livechat.js @@ -111,8 +111,9 @@ export async function settings() { forceAcceptDataProcessingConsent: initSettings.Livechat_force_accept_data_processing_consent, showConnecting: initSettings.Livechat_Show_Connecting, agentHiddenInfo: initSettings.Livechat_show_agent_info === false, + clearLocalStorageWhenChatEnded: initSettings.Livechat_clear_local_storage_when_chat_ended, limitTextLength: initSettings.Livechat_enable_message_character_limit - && (initSettings.Livechat_message_character_limit || initSettings.Message_MaxAllowedSize), + && (initSettings.Livechat_message_character_limit || initSettings.Message_MaxAllowedSize), }, theme: { title: initSettings.Livechat_title, diff --git a/app/livechat/server/config.ts b/app/livechat/server/config.ts index 0d37ad8bf9dde..be6ac87f51b98 100644 --- a/app/livechat/server/config.ts +++ b/app/livechat/server/config.ts @@ -56,6 +56,15 @@ Meteor.startup(function() { enableQuery: omnichannelEnabledQuery, }); + this.add('Livechat_clear_local_storage_when_chat_ended', false, { + type: 'boolean', + group: 'Omnichannel', + public: true, + section: 'Livechat', + i18nLabel: 'Clear_livechat_session_when_chat_ended', + enableQuery: omnichannelEnabledQuery, + }); + this.add('Livechat_validate_offline_email', true, { type: 'boolean', group: 'Omnichannel', diff --git a/app/livechat/server/lib/Livechat.js b/app/livechat/server/lib/Livechat.js index 78d54cd5f1e61..7110fadf8310a 100644 --- a/app/livechat/server/lib/Livechat.js +++ b/app/livechat/server/lib/Livechat.js @@ -529,6 +529,7 @@ export const Livechat = { 'Livechat_force_accept_data_processing_consent', 'Livechat_data_processing_consent_text', 'Livechat_show_agent_info', + 'Livechat_clear_local_storage_when_chat_ended', ]).forEach((setting) => { rcSettings[setting._id] = setting.value; }); diff --git a/app/livechat/server/methods/getInitialData.js b/app/livechat/server/methods/getInitialData.js index bac76ce8d49a6..1243a3360ec24 100644 --- a/app/livechat/server/methods/getInitialData.js +++ b/app/livechat/server/methods/getInitialData.js @@ -3,6 +3,7 @@ import _ from 'underscore'; import { LivechatRooms, Users, LivechatDepartment, LivechatTrigger, LivechatVisitors } from '../../../models'; import { Livechat } from '../lib/Livechat'; +import { deprecationWarning } from '../../../api/server/helpers/deprecationWarning'; Meteor.methods({ 'livechat:getInitialData'(visitorToken, departmentId) { @@ -98,6 +99,7 @@ Meteor.methods({ info.allowSwitchingDepartments = initSettings.Livechat_allow_switching_departments; info.online = Users.findOnlineAgents().count() > 0; - return info; + + return deprecationWarning({ endpoint: 'livechat:getInitialData', versionWillBeRemoved: '5.0', response: info }); }, }); diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 6c0f86946c635..f60c4c019224e 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -866,6 +866,7 @@ "clear_cache_now": "Clear Cache Now", "Clear_filters": "Clear filters", "clear_history": "Clear History", + "Clear_livechat_session_when_chat_ended": "Clear guest session when chat ended", "Click_here": "Click here", "Click_here_for_more_details_or_contact_sales_for_a_new_license": "Click here for more details or contact __email__ for a new license.", "Click_here_for_more_info": "Click here for more info", From aec724fd9dc9ec4507776069909055497401b935 Mon Sep 17 00:00:00 2001 From: Aman-Maheshwari <50165440+Aman-Maheshwari@users.noreply.github.com> Date: Fri, 3 Dec 2021 22:40:06 +0530 Subject: [PATCH 132/137] [FIX] No hover effect for items in kebab menu. #23712 --- app/theme/client/imports/components/popover.css | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/theme/client/imports/components/popover.css b/app/theme/client/imports/components/popover.css index 807bdd7c88d21..f1ac7ce254203 100644 --- a/app/theme/client/imports/components/popover.css +++ b/app/theme/client/imports/components/popover.css @@ -42,7 +42,7 @@ max-height: 70%; - padding: var(--popover-padding); + padding: var(--popover-padding) 0; animation: dropdown-show 0.1s cubic-bezier(0.45, 0.05, 0.55, 0.95); @@ -96,7 +96,7 @@ width: 100%; - padding: 4px 0; + padding: 4px 12px; cursor: pointer; @@ -109,6 +109,10 @@ font-size: var(--popover-item-text-size); align-items: center; + &:hover { + background-color: #f7f8fa; + } + &--alert { color: var(--rc-color-error); @@ -179,9 +183,9 @@ } &__divider { - width: 100%; + width: 88%; height: var(--popover-divider-height); - margin: 1rem 0; + margin: 1rem auto; background: var(--popover-divider-color); From 049956cc076e5fec10df4d4d03095ef8fad2e94e Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Fri, 3 Dec 2021 13:17:05 -0600 Subject: [PATCH 133/137] Chore: Create script to add new migrations (#23822) --- .scripts/make-migration.ts | 50 ++++++++++++++++++++++++++ .scripts/migration.template | 9 +++++ package-lock.json | 71 +++++++++++++++++++++++++++++++++++++ package.json | 2 ++ 4 files changed, 132 insertions(+) create mode 100644 .scripts/make-migration.ts create mode 100644 .scripts/migration.template diff --git a/.scripts/make-migration.ts b/.scripts/make-migration.ts new file mode 100644 index 0000000000000..d9df274539af8 --- /dev/null +++ b/.scripts/make-migration.ts @@ -0,0 +1,50 @@ +import { readdirSync, readFileSync, writeFileSync } from 'fs'; + +import { renderFile } from 'template-file'; + +function main(number: string, comment: string): void { + if (!(Number(number) >= 0)) { + console.error(`1st param must be a valid number. ${ number } provided`); + return; + } + + if (comment.trim()) { + comment = `// ${ comment }`; + } + + // check if migration will conflict with current on-branch migrations + const migrationName = `v${ number }`; + const fileList = readdirSync('./server/startup/migrations'); + if (fileList.includes(`${ migrationName }.ts`)) { + console.error('Migration with specified number already exists'); + return; + } + + renderFile('./.scripts/migration.template', { number, comment }) + .then((renderedMigration) => { + // generate new migration file + writeFileSync(`./server/startup/migrations/${ migrationName }.ts`, renderedMigration); + + // get contents of index.ts to append new migration + const indexFile = readFileSync('./server/startup/migrations/index.ts'); + const splittedIndexLines = indexFile.toString().split('\n'); + + // remove end line + xrun import + splittedIndexLines.splice(splittedIndexLines.length - 2, 0, `import './${ migrationName }';`); + const data = splittedIndexLines.join('\n'); + + // append migration import to indexfile + writeFileSync('./server/startup/migrations/index.ts', data); + console.log(`Migration ${ migrationName } created`); + }) + .catch(console.error); +} + +const [, , number, comment = ''] = process.argv; + +if (!number || (comment && !comment.trim())) { + console.error('Usage:\n\tmeteor npm run migration:add [migration comment: optional]\n'); + process.exit(1); +} + +main(number, comment); diff --git a/.scripts/migration.template b/.scripts/migration.template new file mode 100644 index 0000000000000..0b1ba7b550e2c --- /dev/null +++ b/.scripts/migration.template @@ -0,0 +1,9 @@ +import { addMigration } from '../../lib/migrations'; + +{{ comment }} +addMigration({ + version: {{ number }}, + up() { + + }, +}); diff --git a/package-lock.json b/package-lock.json index 7b98464003cb4..cdfc309f4817f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3433,6 +3433,22 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "@blakek/curry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@blakek/curry/-/curry-2.0.2.tgz", + "integrity": "sha512-B/KkDnZqm9Y92LwETU80BaxbQ61bYTR2GaAY41mKisaICwBoC8lcuw7lwQLl52InMhviCTJBO39GJOA8d+BrVw==", + "dev": true + }, + "@blakek/deep": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@blakek/deep/-/deep-2.2.0.tgz", + "integrity": "sha512-aRq/qF1yrlhCWNk2tI4epXNpo+cA8/MrxsR5oIkpKKNYtYOQKjAxRMbgnhASPx+b328MkDN+T706yFKJg8VZkQ==", + "dev": true, + "requires": { + "@blakek/curry": "^2.0.2", + "pathington": "^1.1.7" + } + }, "@bugsnag/browser": { "version": "7.11.0", "resolved": "https://registry.npmjs.org/@bugsnag/browser/-/browser-7.11.0.tgz", @@ -28646,6 +28662,12 @@ "util-ex": "^0.3.10" } }, + "pathington": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/pathington/-/pathington-1.1.7.tgz", + "integrity": "sha512-JxzhUzagDfNIOm4qqwQqP3rWeo7rNNOfIahy4n+3GTEdwXLqw5cJHUR0soSopQtNEv763lzxb6eA2xBllpR8zw==", + "dev": true + }, "pathval": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", @@ -35240,6 +35262,55 @@ } } }, + "template-file": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/template-file/-/template-file-6.0.0.tgz", + "integrity": "sha512-KWHrRYAaK0njUGmn77gqHpb8vr5/Zj9YIWS2B5cstiNWEZLvF63p/LG8rTCgvfpOJwAb1PeiPmtMUjBT7qw2Tw==", + "dev": true, + "requires": { + "@blakek/deep": "^2.2.0", + "glob": "^7.1.6", + "mkdirp": "^1.0.4", + "p-limit": "^4.0.0" + }, + "dependencies": { + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "requires": { + "yocto-queue": "^1.0.0" + } + }, + "yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true + } + } + }, "term-size": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", diff --git a/package.json b/package.json index a3aeb84d6e056..85fd38e285e42 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "ha": "meteor npm run ha:start", "ha:start": "ts-node .scripts/run-ha.ts main", "ha:add": "ts-node .scripts/run-ha.ts instance", + "migration:add": "ts-node-transpile-only --skip-project .scripts/make-migration.ts", "debug": "meteor run --inspect", "debug-brk": "meteor run --inspect-brk", "lint": "meteor npm run stylelint && meteor npm run eslint", @@ -156,6 +157,7 @@ "stylelint": "^13.13.1", "stylelint-order": "^4.1.0", "supertest": "^6.1.6", + "template-file": "^6.0.0", "ts-loader": "^8.3.0", "ts-node": "^10.0.0", "typescript": "^4.3.4", From ef94c08b9e8399603e8365138504b6654d47b704 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Fri, 3 Dec 2021 18:38:04 -0300 Subject: [PATCH 134/137] Fix inactive user creation (#23859) --- app/authentication/server/startup/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/authentication/server/startup/index.js b/app/authentication/server/startup/index.js index ed98b62aaf282..3f3b5acc5f8d6 100644 --- a/app/authentication/server/startup/index.js +++ b/app/authentication/server/startup/index.js @@ -186,7 +186,8 @@ Accounts.onCreateUser(function(options, user = {}) { if (!user.active) { const destinations = []; - Promise.await(Roles.findUsersInRole('admin').toArray()).forEach((adminUser) => { + const usersInRole = Promise.await(Roles.findUsersInRole('admin')); + Promise.await(usersInRole.toArray()).forEach((adminUser) => { if (Array.isArray(adminUser.emails)) { adminUser.emails.forEach((email) => { destinations.push(`${ adminUser.name }<${ email.address }>`); From 4a7fd1fd423786cb8b6bbb67dee3471011b8a856 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrique=20Guimar=C3=A3es=20Ribeiro?= Date: Mon, 6 Dec 2021 17:21:12 -0300 Subject: [PATCH 135/137] [FIX] Wrong button for non trials apps (#23861) --- app/apps/client/orchestrator.js | 3 ++- client/views/admin/apps/helpers.js | 17 +++++++++++++++-- client/views/admin/apps/types.ts | 1 + 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/app/apps/client/orchestrator.js b/app/apps/client/orchestrator.js index 8b73afc4350ab..4c30b31460cf0 100644 --- a/app/apps/client/orchestrator.js +++ b/app/apps/client/orchestrator.js @@ -69,11 +69,12 @@ class AppClientOrchestrator { getAppsFromMarketplace = async () => { const appsOverviews = await APIClient.get('apps', { marketplace: 'true' }); - return appsOverviews.map(({ latest, price, pricingPlans, purchaseType }) => ({ + return appsOverviews.map(({ latest, price, pricingPlans, purchaseType, isEnterpriseOnly }) => ({ ...latest, price, pricingPlans, purchaseType, + isEnterpriseOnly, })); } diff --git a/client/views/admin/apps/helpers.js b/client/views/admin/apps/helpers.js index 19d7dc09ab7a1..5eed93e554e63 100644 --- a/client/views/admin/apps/helpers.js +++ b/client/views/admin/apps/helpers.js @@ -91,6 +91,8 @@ export const appButtonProps = ({ price, purchaseType, subscriptionInfo, + pricingPlans, + isEnterpriseOnly, }) => { const canUpdate = installed && version && marketplaceVersion && semver.lt(version, marketplaceVersion); @@ -114,8 +116,19 @@ export const appButtonProps = ({ }; } - const canTrial = purchaseType === 'subscription' && !subscriptionInfo.status; - if (canTrial) { + const canSubscribe = purchaseType === 'subscription' && !subscriptionInfo.status; + if (canSubscribe) { + const cannotTry = pricingPlans.every( + (currentPricingPlan) => currentPricingPlan.trialDays === 0, + ); + + if (cannotTry || isEnterpriseOnly) { + return { + action: 'purchase', + label: 'Subscribe', + }; + } + return { action: 'purchase', label: 'Trial', diff --git a/client/views/admin/apps/types.ts b/client/views/admin/apps/types.ts index 5f49372dbd39d..db4ae6760b51b 100644 --- a/client/views/admin/apps/types.ts +++ b/client/views/admin/apps/types.ts @@ -17,6 +17,7 @@ export type App = { pricingPlans: unknown[]; iconFileContent: unknown; installed?: boolean; + isEnterpriseOnly?: boolean; bundledIn: { bundleId: string; bundleName: string; From 59e5c2daa9cc6766297e4fe2630474ce4c25a117 Mon Sep 17 00:00:00 2001 From: "lingohub[bot]" <69908207+lingohub[bot]@users.noreply.github.com> Date: Mon, 6 Dec 2021 17:24:27 -0300 Subject: [PATCH 136/137] =?UTF-8?q?i18n:=20Language=20update=20from=20Ling?= =?UTF-8?q?oHub=20=F0=9F=A4=96=20on=202021-12-06Z=20(#23873)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Language update from LingoHub 🤖 Project Name: Rocket.Chat Project Link: https://translate.lingohub.com/rocketchat/dashboard/rocket-dot-chat User: Robot LingoHub Easy language translations with LingoHub 🚀 Co-authored-by: Diego Sampaio --- packages/rocketchat-i18n/i18n/ar.i18n.json | 4 +--- packages/rocketchat-i18n/i18n/cs.i18n.json | 3 +-- packages/rocketchat-i18n/i18n/de-AT.i18n.json | 3 +-- packages/rocketchat-i18n/i18n/de.i18n.json | 3 +-- packages/rocketchat-i18n/i18n/el.i18n.json | 3 +-- packages/rocketchat-i18n/i18n/en.i18n.json | 2 +- packages/rocketchat-i18n/i18n/es.i18n.json | 5 ++--- packages/rocketchat-i18n/i18n/fa.i18n.json | 10 ++-------- packages/rocketchat-i18n/i18n/fi.i18n.json | 3 +-- packages/rocketchat-i18n/i18n/fr.i18n.json | 7 ++++++- packages/rocketchat-i18n/i18n/he.i18n.json | 3 +-- packages/rocketchat-i18n/i18n/hu.i18n.json | 3 +-- packages/rocketchat-i18n/i18n/id.i18n.json | 3 +-- packages/rocketchat-i18n/i18n/it.i18n.json | 3 +-- packages/rocketchat-i18n/i18n/ka-GE.i18n.json | 3 +-- packages/rocketchat-i18n/i18n/km.i18n.json | 3 +-- packages/rocketchat-i18n/i18n/ku.i18n.json | 3 +-- packages/rocketchat-i18n/i18n/lo.i18n.json | 4 +--- packages/rocketchat-i18n/i18n/lv.i18n.json | 5 +---- packages/rocketchat-i18n/i18n/mn.i18n.json | 3 +-- packages/rocketchat-i18n/i18n/ms-MY.i18n.json | 5 +---- packages/rocketchat-i18n/i18n/nl.i18n.json | 19 ++++++++++++------- packages/rocketchat-i18n/i18n/ro.i18n.json | 4 +--- packages/rocketchat-i18n/i18n/sr.i18n.json | 4 +--- packages/rocketchat-i18n/i18n/sv.i18n.json | 3 +-- packages/rocketchat-i18n/i18n/ta-IN.i18n.json | 4 +--- packages/rocketchat-i18n/i18n/tr.i18n.json | 5 ++--- packages/rocketchat-i18n/i18n/ug.i18n.json | 3 +-- packages/rocketchat-i18n/i18n/uk.i18n.json | 3 +-- packages/rocketchat-i18n/i18n/zh-HK.i18n.json | 3 +-- packages/rocketchat-i18n/i18n/zh.i18n.json | 2 +- 31 files changed, 50 insertions(+), 81 deletions(-) diff --git a/packages/rocketchat-i18n/i18n/ar.i18n.json b/packages/rocketchat-i18n/i18n/ar.i18n.json index 958898b741a7f..4b43c727b0f54 100644 --- a/packages/rocketchat-i18n/i18n/ar.i18n.json +++ b/packages/rocketchat-i18n/i18n/ar.i18n.json @@ -2234,8 +2234,6 @@ "RetentionPolicy_MaxAge_Groups": "الحد الأقصى لعمر الرسالة في المجموعات الخاصة", "RetentionPolicy_Precision": "الدقة الموقت", "RetentionPolicy_Precision_Description": "كم مرة يجب أن يتم تشغيل جهاز توقيت التقليم. يؤدي تعيين هذا إلى قيمة أكثر دقة إلى جعل القنوات ذات الموقتات السريعة للاحتفاظ تعمل بشكل أفضل ، ولكنها قد تكلف طاقة معالجة إضافية في المجتمعات الكبيرة.", - "RetentionPolicy_RoomWarning_FilesOnly": null, - "RetentionPolicy_RoomWarning_UnpinnedFilesOnly": null, "RetentionPolicyRoom_Enabled": "تقليم الرسائل القديمة تلقائيا", "RetentionPolicyRoom_ExcludePinned": "استبعاد الرسائل المثبتة", "RetentionPolicyRoom_FilesOnly": "ملفات التقليم فقط ، الاحتفاظ بالرسائل", @@ -2956,4 +2954,4 @@ "Your_push_was_sent_to_s_devices": "وقد أرسلت دفعك إلى أجهزة٪ الصورة", "Your_server_link": "رابط الخادم الخاص بك", "Your_workspace_is_ready": "مساحة العمل الخاصة بك جاهزة لاستخدام 🎉" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/cs.i18n.json b/packages/rocketchat-i18n/i18n/cs.i18n.json index b8e39f7a2af1f..8a28be9d0b30d 100644 --- a/packages/rocketchat-i18n/i18n/cs.i18n.json +++ b/packages/rocketchat-i18n/i18n/cs.i18n.json @@ -3334,7 +3334,6 @@ "Show_Setup_Wizard": "Zobrazit průvodce nastavením", "Show_the_keyboard_shortcut_list": "Zobrazit klávesové zkratky", "Showing_archived_results": "

        Zobrazeno %s archivovaných výsledků

        ", - "Showing_online_users": null, "Showing_results": "

        Zobrazeno %s výsledků

        ", "Sidebar": "Postranní panel", "Sign_in_to_start_talking": "Pro konverzaci se přihlašte", @@ -4060,4 +4059,4 @@ "Your_server_link": "Odkaz na Váš server", "Your_temporary_password_is_password": "Vaše dočasné heslo je [password].", "Your_workspace_is_ready": "Váš prostředí je připraveno k použití 🎉" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/de-AT.i18n.json b/packages/rocketchat-i18n/i18n/de-AT.i18n.json index 39085f874fec9..0b7bbf0a29f22 100644 --- a/packages/rocketchat-i18n/i18n/de-AT.i18n.json +++ b/packages/rocketchat-i18n/i18n/de-AT.i18n.json @@ -2305,7 +2305,6 @@ "Show_Setup_Wizard": "Setup-Assistent anzeigen", "Show_the_keyboard_shortcut_list": "Zeigen Sie die Tastenkombination an", "Showing_archived_results": "

        Anzeigen von %s archivierten Ergebnissen

        ", - "Showing_online_users": null, "Showing_results": "

        %s Ergebnisse

        ", "Sidebar": "Seitenleiste", "Sidebar_list_mode": "Sidebar-Kanallistenmodus", @@ -2822,4 +2821,4 @@ "Your_push_was_sent_to_s_devices": "Die Push-Nachricht wurde an %s Geräte gesendet.", "Your_server_link": "Ihre Serververbindung", "Your_workspace_is_ready": "Ihr Arbeitsbereich ist einsatzbereit 🎉" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/de.i18n.json b/packages/rocketchat-i18n/i18n/de.i18n.json index 1712ba826a809..5cf5ef583cc81 100644 --- a/packages/rocketchat-i18n/i18n/de.i18n.json +++ b/packages/rocketchat-i18n/i18n/de.i18n.json @@ -3449,7 +3449,6 @@ "Show_Setup_Wizard": "Setup-Assistent anzeigen", "Show_the_keyboard_shortcut_list": "Zeige die Liste der Keyboard-Shortcuts", "Showing_archived_results": "

        Aneigen von %s archivierte Räume

        ", - "Showing_online_users": null, "Showing_results": "

        %s Ergebnisse

        ", "Sidebar": "Seitenleiste", "Sidebar_list_mode": "Seitenleiste Kanallisten-Modus", @@ -4164,4 +4163,4 @@ "Your_temporary_password_is_password": "Ihr temporäres Passwort lautet [password].", "Your_TOTP_has_been_reset": "Dein Zwei-Faktor-TOTP wurde zurückgesetzt.", "Your_workspace_is_ready": "Ihr Arbeitsbereich ist einsatzbereit 🎉" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/el.i18n.json b/packages/rocketchat-i18n/i18n/el.i18n.json index 6181f923c0b94..5d49e660e1bd6 100644 --- a/packages/rocketchat-i18n/i18n/el.i18n.json +++ b/packages/rocketchat-i18n/i18n/el.i18n.json @@ -2310,7 +2310,6 @@ "Show_Setup_Wizard": "Εμφάνιση του οδηγού εγκατάστασης", "Show_the_keyboard_shortcut_list": "Εμφάνιση της λίστας συντομεύσεων πληκτρολογίου", "Showing_archived_results": "

        Εμφάνιση αρχειοθετημένα αποτελέσματα %s

        ", - "Showing_online_users": null, "Showing_results": "

        Εμφανιζονται %s αποτελεσματα

        ", "Sidebar": "Πλευρική γραμμή", "Sidebar_list_mode": "Λειτουργία λίστας καναλιών πλευρικής γραμμής", @@ -2826,4 +2825,4 @@ "Your_push_was_sent_to_s_devices": "ώθηση σας στάλθηκε σε συσκευές %s", "Your_server_link": "Σύνδεσμος διακομιστή σας", "Your_workspace_is_ready": "Ο χώρος εργασίας σας είναι έτοιμος για χρήση 🎉" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index f60c4c019224e..44668c0ba2953 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -2313,7 +2313,7 @@ "Invite": "Invite", "Invites": "Invites", "Invite_Link": "Invite Link", - "Invite_removed": "Invite removed successfully", + "Invite_removed": "Invite removed successfully", "Invite_user_to_join_channel": "Invite one user to join this channel", "Invite_user_to_join_channel_all_from": "Invite all users from [#channel] to join this channel", "Invite_user_to_join_channel_all_to": "Invite all users from this channel to join [#channel]", diff --git a/packages/rocketchat-i18n/i18n/es.i18n.json b/packages/rocketchat-i18n/i18n/es.i18n.json index 9627c0b0d6e26..478a7cf7ae303 100644 --- a/packages/rocketchat-i18n/i18n/es.i18n.json +++ b/packages/rocketchat-i18n/i18n/es.i18n.json @@ -1846,7 +1846,6 @@ "Favorite": "Favorito", "Favorite_Rooms": "Habilitar salas favoritas", "Favorites": "Favoritos", - "Feature_depends_on_selected_call_provider_to_be_enabled_from_administration_settings": "Esta función depende del proveedor de llamadas seleccionado anteriormente que se habilitará desde la configuración de administración.", "Feature_Depends_on_Livechat_Visitor_navigation_as_a_message_to_be_enabled": "Esta función depende de \"Enviar el historial de navegación de visitantes como un mensaje\" para que se habilite.", "Feature_Limiting": "Limitación de funciones", "Features": "Características", @@ -4418,7 +4417,7 @@ "User__username__removed_from__room_name__moderators": "El usuario __username__ fue retirado de los moderadores de __room_name__", "User__username__removed_from__room_name__owners": "El usuario __username__ fue removido de los propietarios de __room_name__", "User__username__unmuted_in_room__roomName__": "Usuario __username__ sin silenciar en la sala __roomName__", - "User_added": "Usuario __user_added__ añadido.", + "User_added": "Usuario añadido.", "User_added_by": "El usuario __user_added__ ha sido añadido por __user_by__.", "User_added_successfully": "Usuario añadido correctamente", "User_and_group_mentions_only": "Sólo menciones de usuario y grupo", @@ -4759,4 +4758,4 @@ "Your_temporary_password_is_password": "Su contraseña temporal es [password].", "Your_TOTP_has_been_reset": "Su TOTP de dos factores ha sido restablecido.", "Your_workspace_is_ready": "Su espacio de trabajo está listo para usar 🎉" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/fa.i18n.json b/packages/rocketchat-i18n/i18n/fa.i18n.json index 3fd78b586a0b9..7618e6f52b393 100644 --- a/packages/rocketchat-i18n/i18n/fa.i18n.json +++ b/packages/rocketchat-i18n/i18n/fa.i18n.json @@ -380,8 +380,6 @@ "Apps_Marketplace_Login_Required_Description": "خرید برنامه از Rocket.Chat Marketplace نیاز به ثبت فضای کاری شما و ورود به سیستم دارد.", "Apps_Marketplace_Login_Required_Title": "نیازمند ورود به بازار", "Apps_Marketplace_Modify_App_Subscription": "تصحیح کردن اشتراک", - "Apps_Marketplace_pricingPlan_yearly": null, - "Apps_Marketplace_pricingPlan_yearly_perUser": null, "Apps_Marketplace_Uninstall_App_Prompt": "آیا واقعاً می خواهید این برنامه را حذف کنید؟", "Apps_Marketplace_Uninstall_Subscribed_App_Anyway": "در هر صورت حذف کنید", "Apps_Marketplace_Uninstall_Subscribed_App_Prompt": "این برنامه اشتراک دارد و حذف آن لغو نخواهد شد. اگر می خواهید این کار را انجام دهید ، لطفاً قبل از حذف اشتراک ، اشتراک خود را اصلاح کنید.", @@ -2444,8 +2442,6 @@ "RetentionPolicy_MaxAge_Groups": "حداکثر سن پیام در گروه های خصوصی", "RetentionPolicy_Precision": "تایمر دقیق", "RetentionPolicy_Precision_Description": "هر چند وقت یکبار تایمر بره باید اجرا شود تنظیم این به یک مقدار دقیق تر باعث می شود کانال های با تایمر نگهداری سریع کار بهتر، اما ممکن است پردازش قدرت اضافی در جوامع بزرگ هزینه.", - "RetentionPolicy_RoomWarning_FilesOnly": null, - "RetentionPolicy_RoomWarning_UnpinnedFilesOnly": null, "RetentionPolicyRoom_Enabled": "پیام های قدیمی را به طور خودکار خرد کنید", "RetentionPolicyRoom_ExcludePinned": "پیام های پین شده را حذف کنید", "RetentionPolicyRoom_FilesOnly": "فقط پرونده ها را ببندید، پیام ها را نگه دارید", @@ -2740,7 +2736,7 @@ "Sunday": "یک شنبه", "Support": "حمایت کردن", "Survey": "نظرسنجی", - "Survey_instructions": "رای هر سوال با توجه به جلب رضایت شما، 1 معنی که شما ناراضی هستند به طور کامل و 5 این معنی که شما به طور کامل راضی هستند.", + "Survey_instructions": "به هر سوال بر اساس رضایتتان نمره دهید. ۱ به معنای عدم رضایت کامل و ۵ به معنای رضایت کامل است.", "Symbols": "علامت", "Sync_in_progress": "هماهنگ سازی در حال انجام است", "Sync_success": "موفقیت همگام سازی", @@ -2829,8 +2825,6 @@ "This_email_has_already_been_used_and_has_not_been_verified__Please_change_your_password": "این ایمیل قبلا استفاده شده است و تأیید نشده است. لطفا رمز عبور خود را تغییر دهید.", "This_is_a_desktop_notification": "این یک اعلان دسکتاپ است", "This_is_a_push_test_messsage": "این messsage آزمون فشار است", - "This_room_has_been_archived_by__username_": null, - "This_room_has_been_unarchived_by__username_": null, "Thursday": "پنج شنبه", "Time_in_seconds": "زمان در ثانیه", "Title": "عنوان", @@ -3164,4 +3158,4 @@ "Your_push_was_sent_to_s_devices": "فشار خود را به دستگاه %s را ارسال شد", "Your_server_link": "لینک سرور شما", "Your_workspace_is_ready": "فضای کاری شما آماده استفاده است" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/fi.i18n.json b/packages/rocketchat-i18n/i18n/fi.i18n.json index f816082b7fb79..de01a02b1d6da 100644 --- a/packages/rocketchat-i18n/i18n/fi.i18n.json +++ b/packages/rocketchat-i18n/i18n/fi.i18n.json @@ -2304,7 +2304,6 @@ "Show_Setup_Wizard": "Näytä ohjattu asennustoiminto", "Show_the_keyboard_shortcut_list": "Näytä pikanäppäinten luettelo", "Showing_archived_results": "

        Näytetään %s arkistoitua tulosta

        ", - "Showing_online_users": null, "Showing_results": "

        Näytetään %s tulosta

        ", "Sidebar": "sivupalkki", "Sidebar_list_mode": "Sivupalkin kanavaluettelotila", @@ -2820,4 +2819,4 @@ "Your_push_was_sent_to_s_devices": "Push-viestisi lähetettiin %s laitteeseen", "Your_server_link": "Palvelimesi linkki", "Your_workspace_is_ready": "Työtila on valmis käyttämään 🎉" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/fr.i18n.json b/packages/rocketchat-i18n/i18n/fr.i18n.json index cad2e6136c124..235f71822e377 100644 --- a/packages/rocketchat-i18n/i18n/fr.i18n.json +++ b/packages/rocketchat-i18n/i18n/fr.i18n.json @@ -866,6 +866,7 @@ "clear_cache_now": "Vider le cache maintenant", "Clear_filters": "Effacer les filtres", "clear_history": "Effacer l'historique", + "Clear_livechat_session_when_chat_ended": "Effacer la session invité à la fin du chat", "Click_here": "Cliquez ici", "Click_here_for_more_details_or_contact_sales_for_a_new_license": "Cliquez ici pour plus de détails ou contactez __email__ pour une nouvelle licence.", "Click_here_for_more_info": "Cliquez ici pour plus d'informations", @@ -1818,6 +1819,7 @@ "Exit_Full_Screen": "Quitter le mode plein écran", "Expand": "Développer", "Experimental_Feature_Alert": "Ceci est une fonctionnalité expérimentale ! Veuillez noter qu'il peut changer, casser ou même être supprimé à l'avenir sans préavis.", + "Expired": "Expiré", "Expiration": "Expiration", "Expiration_(Days)": "Expiration (jours)", "Export_as_file": "Exporter en tant que fichier", @@ -1847,7 +1849,7 @@ "Favorite": "Favori", "Favorite_Rooms": "Activer les salons favoris", "Favorites": "Favoris", - "Feature_depends_on_selected_call_provider_to_be_enabled_from_administration_settings": "Cette fonction dépend du fournisseur d'appel sélectionné ci-dessus à activer à partir des paramètres d'administration.", + "Feature_depends_on_selected_call_provider_to_be_enabled_from_administration_settings": "Cette fonction dépend du fournisseur d'appel sélectionné ci-dessus à activer à partir des paramètres d'administration.
        Pour **Jitsi**, assurez-vous que Jitsi est activé sous Admin -> Conférence vidéo -> Jitsi -> Activé.
        Pour **WebRTC**, assurez-vous que WebRTC est activé sous Admin -> WebRTC -> Activé.", "Feature_Depends_on_Livechat_Visitor_navigation_as_a_message_to_be_enabled": "Cette fonctionnalité dépend de l'activation de \"Envoyer l'historique de navigation des visiteurs sous forme de message\".", "Feature_Limiting": "Limitation des fonctionnalités", "Features": "Fonctionnalités", @@ -2309,7 +2311,9 @@ "Invitation_Subject": "Sujet de l'invitation", "Invitation_Subject_Default": "Vous avez été invité à [Site_Name]", "Invite": "Inviter", + "Invites": "Invitations", "Invite_Link": "Lien d'invitation", + "Invite_removed": "Invitation supprimée avec succès", "Invite_user_to_join_channel": "Inviter un utilisateur à rejoindre le canal", "Invite_user_to_join_channel_all_from": "Inviter tous les utilisateurs de [#channel] à rejoindre ce canal", "Invite_user_to_join_channel_all_to": "Inviter tous les utilisateurs de ce canal à rejoindre [#channel]", @@ -3174,6 +3178,7 @@ "Office_Hours": "Heures de bureau", "Office_hours_enabled": "Heures de bureau activées", "Office_hours_updated": "Heures de bureau modifiées", + "offline": "hors ligne", "Offline": "Hors ligne", "Offline_DM_Email": "Objet de l'e-mail du message privé", "Offline_Email_Subject_Description": "Vous pouvez utiliser les espaces réservés suivants:
        • [Site_Name], [Site_URL], [User] & [Room] pour le nom de l'application, l'URL, le nom d'utilisateur et le nom du salon, respectivement.
        ", diff --git a/packages/rocketchat-i18n/i18n/he.i18n.json b/packages/rocketchat-i18n/i18n/he.i18n.json index e81fe86c3a3ee..cf40e07cc7e68 100644 --- a/packages/rocketchat-i18n/i18n/he.i18n.json +++ b/packages/rocketchat-i18n/i18n/he.i18n.json @@ -1239,7 +1239,6 @@ "Show_only_online": "הצג רק מחוברים", "Show_preregistration_form": "צג טופס הרשמה מראש", "Showing_archived_results": "

        מציג %s תוצאות בארכיון

        ", - "Showing_online_users": null, "Showing_results": "

        מוצגות %s תוצאות

        ", "since_creation": "מאז %s", "Site_Name": "שם האתר", @@ -1559,4 +1558,4 @@ "Your_password_is_wrong": "הסיסמה שלך שגויה!", "Your_push_was_sent_to_s_devices": "הודעת ה-push נשלח בהצלחה ל-%s מכשירים", "Your_question": "השאלה שלך" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/hu.i18n.json b/packages/rocketchat-i18n/i18n/hu.i18n.json index 418a550eefaf7..cc8d5a6547028 100644 --- a/packages/rocketchat-i18n/i18n/hu.i18n.json +++ b/packages/rocketchat-i18n/i18n/hu.i18n.json @@ -3169,7 +3169,6 @@ "Show_the_keyboard_shortcut_list": "Mutassa be a billentyűparancsok listáját", "Show_video": "Videó megjelenítése", "Showing_archived_results": "

        A következő %s archivált eredmények

        ", - "Showing_online_users": null, "Showing_results": "

        %s eredmény megjelenítve

        ", "Sidebar": "Oldalsáv", "Sidebar_list_mode": "Oldalsáv csatornalista mód", @@ -3876,4 +3875,4 @@ "Your_temporary_password_is_password": "Az ideiglenes jelszavad: [password]", "Your_TOTP_has_been_reset": "A kétfaktoros TOTP-d visszaállításra került.", "Your_workspace_is_ready": "A munkaterület készen áll a 🎉 használatára" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/id.i18n.json b/packages/rocketchat-i18n/i18n/id.i18n.json index a6187ba783e0e..5ddd5548e912e 100644 --- a/packages/rocketchat-i18n/i18n/id.i18n.json +++ b/packages/rocketchat-i18n/i18n/id.i18n.json @@ -2311,7 +2311,6 @@ "Show_Setup_Wizard": "Tampilkan Wizard Pengaturan", "Show_the_keyboard_shortcut_list": "Tampilkan daftar jalan pintas keyboard", "Showing_archived_results": "

        Menampilkan%s hasil diarsipkan

        ", - "Showing_online_users": null, "Showing_results": "

        Menampilkan %s hasil

        ", "Sidebar": "Sidebar", "Sidebar_list_mode": "Mode Daftar Saluran Sidebar", @@ -2827,4 +2826,4 @@ "Your_push_was_sent_to_s_devices": "push dikirim ke%s perangkat", "Your_server_link": "Tautan server Anda", "Your_workspace_is_ready": "Ruang kerja Anda siap digunakan 🎉" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/it.i18n.json b/packages/rocketchat-i18n/i18n/it.i18n.json index 1fb3c11791cde..76a5ac4309770 100644 --- a/packages/rocketchat-i18n/i18n/it.i18n.json +++ b/packages/rocketchat-i18n/i18n/it.i18n.json @@ -2367,7 +2367,6 @@ "Show_Setup_Wizard": "Mostra procedura guidata di installazione", "Show_the_keyboard_shortcut_list": "Mostra l'elenco delle scorciatoie per la tastiera", "Showing_archived_results": "

        Mostra %s risultati archiviati

        ", - "Showing_online_users": null, "Showing_results": "

        Visualizzati %s risultati

        ", "Sidebar": "Sidebar", "Sidebar_list_mode": "Modalità Elenco canali laterale", @@ -2905,4 +2904,4 @@ "Your_push_was_sent_to_s_devices": "La tua richiesta è stata inviata ai %s dispositivi.", "Your_server_link": "Il tuo collegamento al server", "Your_workspace_is_ready": "Il tuo spazio di lavoro è pronto per l'uso 🎉" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/ka-GE.i18n.json b/packages/rocketchat-i18n/i18n/ka-GE.i18n.json index 403028e1f582d..5a789166c236f 100644 --- a/packages/rocketchat-i18n/i18n/ka-GE.i18n.json +++ b/packages/rocketchat-i18n/i18n/ka-GE.i18n.json @@ -2888,7 +2888,6 @@ "Room_archivation_state_false": "აქტიური", "Room_archivation_state_true": "დაარქივებულია", "Room_archived": "ოთახი დაარქივებულია", - "room_changed_privacy": null, "room_changed_topic": "ოთახის თემა შეიცვალა: __room_topic__ __user_by__", "Room_default_change_to_private_will_be_default_no_more": "ეს არის დეფაულტ არხი და პირად ჯგუფად გადაკეთების შემთხვევაში აღარ იქნება დეფაულტ არხი.გსურთ გაგრძელება?", "Room_description_changed_successfully": "ოთახის აღწერა წარმატებით შეიცვალა", @@ -3748,4 +3747,4 @@ "Your_server_link": "თქვენი სერვერის მისამართი", "Your_temporary_password_is_password": "თქვენი დროებითი პაროლია არის [password]", "Your_workspace_is_ready": "თქვენი სამუშაო გარემო მზად არის სამუშაოდ 🎉" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/km.i18n.json b/packages/rocketchat-i18n/i18n/km.i18n.json index 8435fc2afe794..4ef92febf87c8 100644 --- a/packages/rocketchat-i18n/i18n/km.i18n.json +++ b/packages/rocketchat-i18n/i18n/km.i18n.json @@ -2626,7 +2626,6 @@ "Show_Setup_Wizard": "បង្ហាញអ្នកជំនួយការដំឡើង", "Show_the_keyboard_shortcut_list": "បង្ហាញបញ្ជីផ្លូវកាត់ក្តារចុច", "Showing_archived_results": "

        បង្ហាញពីលទ្ធផលទុកក្នុងប័ណ្ណសារ %s បាន

        ", - "Showing_online_users": null, "Showing_results": "

        កំពុង​បង្ហាញ %s លទ្ធផល

        ", "Sidebar": "របារចំហៀង", "Sidebar_list_mode": "របៀបបញ្ជីឆានែលរបារចំហៀង", @@ -3179,4 +3178,4 @@ "Your_push_was_sent_to_s_devices": "ការជំរុញរបស់អ្នកត្រូវបានបញ្ជូនទៅកាន់ឧបករណ៍ %s បាន", "Your_server_link": "តំណភ្ជាប់ម៉ាស៊ីនមេរបស់អ្នក", "Your_workspace_is_ready": "កន្លែងធ្វើការរបស់អ្នករួចរាល់ដើម្បីប្រើ🎉" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/ku.i18n.json b/packages/rocketchat-i18n/i18n/ku.i18n.json index 663f9dd27c72c..907a67b1bd418 100644 --- a/packages/rocketchat-i18n/i18n/ku.i18n.json +++ b/packages/rocketchat-i18n/i18n/ku.i18n.json @@ -2296,7 +2296,6 @@ "Show_Setup_Wizard": "Vebijêrk Setup", "Show_the_keyboard_shortcut_list": "Lîsteya kurteya klavyeyê nîşan bide", "Showing_archived_results": "

        Rûpela results trendê %s

        ", - "Showing_online_users": null, "Showing_results": "

        نیشاندانی %s ئەنجام

        ", "Sidebar": "Sidebar", "Sidebar_list_mode": "Mîhengên Channel Lîsteya Sidebar", @@ -2812,4 +2811,4 @@ "Your_push_was_sent_to_s_devices": "push xwe ji bo cîhazên %s hate şandin", "Your_server_link": "Girêdana serverê", "Your_workspace_is_ready": "Karên te yên amadekar e amade ye" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/lo.i18n.json b/packages/rocketchat-i18n/i18n/lo.i18n.json index a12e2ad656845..fa12bc0d68512 100644 --- a/packages/rocketchat-i18n/i18n/lo.i18n.json +++ b/packages/rocketchat-i18n/i18n/lo.i18n.json @@ -2337,7 +2337,6 @@ "Show_Setup_Wizard": "ສະແດງຕົວຊ່ວຍສ້າງການຕັ້ງຄ່າ", "Show_the_keyboard_shortcut_list": "ສະແດງລາຍະການທາງລັດແປ້ນພິມ", "Showing_archived_results": "

        ສະແດງໃຫ້ເຫັນຜົນໄດ້ຮັບທີ່ເກັບ %s

        ", - "Showing_online_users": null, "Showing_results": "

        ສະແດງໃຫ້ເຫັນຜົນໄດ້ຮັບ %s

        ", "Sidebar": "Sidebar", "Sidebar_list_mode": "ໂຫມດບັນທັດຂອງແຖບ Sidebar", @@ -2717,7 +2716,6 @@ "Users_added": "ຜູ້ໃຊ້ໄດ້ຖືກເພີ່ມ", "Users_in_role": "ຜູ້ຊົມໃຊ້ໃນພາລະບົດບາດ", "UTF8_Names_Slugify": "UTF8 Names Slugify", - "Videocall_enabled": null, "Validate_email_address": "ຢືນຢັນທີ່ຢູ່ອີເມວ", "Verification": "ການຢັ້ງຢືນ", "Verification_Description": "ທ່ານອາດຈະນໍາໃຊ້ບ່ອນວາງສະຖານດັ່ງຕໍ່ໄປນີ້:
        • [Verification_Url] ສໍາລັບ URL ການຢືນຢັນ.
        • [ຊື່], [fname], [lname] ສໍາລັບຊື່ເຕັມ, ຊື່ຫຼືນາມສະກຸນຂອງຜູ້ໃຊ້.
        • [ອີເມວ] ສໍາລັບອີເມວຂອງຜູ້ໃຊ້.
        • [Site_Name] ແລະ [Site_URL] ສໍາລັບຊື່ແອັບຯແລະ URL ຕາມລໍາດັບ.
        ", @@ -2853,4 +2851,4 @@ "Your_push_was_sent_to_s_devices": "ການຊຸກຍູ້ຂອງທ່ານໄດ້ຖືກສົ່ງໄປອຸປະກອນ %s", "Your_server_link": "ເຊື່ອມຕໍ່ເຊີຟເວີຂອງທ່ານ", "Your_workspace_is_ready": "ພື້ນທີ່ເຮັດວຽກຂອງທ່ານແມ່ນພ້ອມທີ່ຈະໃຊ້🎉" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/lv.i18n.json b/packages/rocketchat-i18n/i18n/lv.i18n.json index f01b99b21e3e3..9c911304e0fa8 100644 --- a/packages/rocketchat-i18n/i18n/lv.i18n.json +++ b/packages/rocketchat-i18n/i18n/lv.i18n.json @@ -2159,7 +2159,6 @@ "Room_archivation_state_false": "Aktīvs", "Room_archivation_state_true": "Arhivēts", "Room_archived": "Istaba arhivēta", - "room_changed_privacy": null, "Room_default_change_to_private_will_be_default_no_more": "Šis ir noklusējuma kanāls, mainot to uz privātu grupu, tas vairs nebūs noklusējuma kanāls. Vai vēlaties turpināt?", "Room_description_changed_successfully": "Istabas apraksts ir veiksmīgi mainīts", "Room_has_been_archived": "Istaba ir arhivēta", @@ -2598,7 +2597,6 @@ "Use_url_for_avatar": "Izmantot URL kā avataru", "Use_User_Preferences_or_Global_Settings": "Izmantot lietotāja preferences vai globālos iestatījumus", "User": "Lietotājs", - "User__username__removed_from__room_name__leaders": null, "User_added": "Lietotājs pievienots", "User_added_successfully": "Lietotājs pievienots veiksmīgi", "User_and_group_mentions_only": "Lietotājs un grupa tikai pieminējumi", @@ -2773,7 +2771,6 @@ "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Jūs varat izmantot webhoaks, lai viegli integrētu livechat ar savu CRM.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Jūs nevarat atstāt livechat istabu. Lūdzu, izmantojiet aizvēršanas pogu.", "You_have_been_muted": "Jums ir liegts rakstīt un nevar runāt šajā istabā", - "You_have_n_codes_remaining": null, "You_have_not_verified_your_email": "Jūs neesat apstiprinājis savu e-pastu.", "You_have_successfully_unsubscribed": "Jūs esat veiksmīgi anulējis abonomentu no mūsu Sūtšanas saraksta.", "You_have_to_set_an_API_token_first_in_order_to_use_the_integration": "Vispirms ir jāiestata API žetons, lai izmantotu integrāciju.", @@ -2800,4 +2797,4 @@ "Your_push_was_sent_to_s_devices": "Jūsu push tika nosūtīts uz %s ierīcēm", "Your_server_link": "Jūsu servera saite", "Your_workspace_is_ready": "Jūsu darbastacija ir gatava lietošanai 🎉" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/mn.i18n.json b/packages/rocketchat-i18n/i18n/mn.i18n.json index c53483bee2373..7ca0202276ebd 100644 --- a/packages/rocketchat-i18n/i18n/mn.i18n.json +++ b/packages/rocketchat-i18n/i18n/mn.i18n.json @@ -2776,7 +2776,6 @@ "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Та вэбсайтаа CRech-тай livechat-тай амархан холбох боломжтой.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Та livechat өрөө орхиж болохгүй. Ойрхон товчлуурыг ашиглана уу.", "You_have_been_muted": "Та чимээгүй болж, энэ өрөөнд ярьж чадахгүй байна", - "You_have_n_codes_remaining": null, "You_have_not_verified_your_email": "Та өөрийн имэйлийг баталгаажуулаагүй байна.", "You_have_successfully_unsubscribed": "Та манай Майдансын жагсаалтаас амжилттай цуцалсан.", "You_have_to_set_an_API_token_first_in_order_to_use_the_integration": "Та интеграцыг ашиглахын тулд эхлээд API жетоныг тохируулах хэрэгтэй.", @@ -2803,4 +2802,4 @@ "Your_push_was_sent_to_s_devices": "Таны түлхэлт %s төхөөрөмж рүү илгээгдсэн", "Your_server_link": "Таны серверийн холбоос", "Your_workspace_is_ready": "Таны ажлын талбарыг ашиглахад бэлэн байна" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/ms-MY.i18n.json b/packages/rocketchat-i18n/i18n/ms-MY.i18n.json index 746bd620fb56c..5fc9e487a1607 100644 --- a/packages/rocketchat-i18n/i18n/ms-MY.i18n.json +++ b/packages/rocketchat-i18n/i18n/ms-MY.i18n.json @@ -1,7 +1,6 @@ { "403": "Larangan", "500": "Ralat Pelayan Dalaman", - "__username__was_set__role__by__user_by_": null, "@username": "@pengguna", "@username_message": "@username ", "#channel": "#channel", @@ -1022,7 +1021,6 @@ "error-application-not-found": "Permohonan tidak dijumpai", "error-archived-duplicate-name": "Ada satu saluran yang diarkibkan dengan nama '__room_name__'", "error-avatar-invalid-url": "avatar URL tidak sah: __url__", - "error-avatar-url-handling": null, "error-cant-invite-for-direct-room": "tidak boleh menjemput pengguna ke bilik terus", "error-channels-setdefault-is-same": "Tetapan lalai saluran adalah sama dengan apa yang akan ditukar kepada.", "error-channels-setdefault-missing-default-param": "Diperlukan 'default' bodyParam", @@ -2309,7 +2307,6 @@ "Show_Setup_Wizard": "Tunjukkan Penyihir Persediaan", "Show_the_keyboard_shortcut_list": "Tunjukkan senarai pintasan papan kekunci", "Showing_archived_results": "

        Menunjukkan hasil yang diarkibkan %s

        ", - "Showing_online_users": null, "Showing_results": "

        Menunjukan %s keputusan

        ", "Sidebar": "Sidebar", "Sidebar_list_mode": "Mod Senarai Saluran Sidebar", @@ -2822,4 +2819,4 @@ "Your_push_was_sent_to_s_devices": "push anda telah dihantar ke peranti %s", "Your_server_link": "Pautan pelayan anda", "Your_workspace_is_ready": "Ruang kerja anda sedia untuk menggunakan 🎉" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/nl.i18n.json b/packages/rocketchat-i18n/i18n/nl.i18n.json index 19acdbcb70c60..dd2a2fc60343a 100644 --- a/packages/rocketchat-i18n/i18n/nl.i18n.json +++ b/packages/rocketchat-i18n/i18n/nl.i18n.json @@ -866,6 +866,7 @@ "clear_cache_now": "Wis nu de cache", "Clear_filters": "Filters wissen", "clear_history": "Geschiedenis wissen", + "Clear_livechat_session_when_chat_ended": "Gastsessie wissen wanneer chat beëindigd is", "Click_here": "Klik hier", "Click_here_for_more_details_or_contact_sales_for_a_new_license": "Klik hier voor meer details of neem contact op met __email__ voor een nieuwe licentie.", "Click_here_for_more_info": "Klik hier voor meer info", @@ -1818,6 +1819,7 @@ "Exit_Full_Screen": "Volledig scherm verlaten", "Expand": "Uitbreiden", "Experimental_Feature_Alert": "Dit is een expirementele functie! Houd er rekening mee dat het in de toekomst zonder voorafgaande waarschuwing kan veranderen, breken of zelfs worden verwijderd.", + "Expired": "Verlopen", "Expiration": "Vervaldatum", "Expiration_(Days)": "Vervaldatum (dagen)", "Export_as_file": "Exporteren als bestand", @@ -1847,7 +1849,7 @@ "Favorite": "Favoriete", "Favorite_Rooms": "Schakel favoriete kamers in", "Favorites": "Favorieten", - "Feature_depends_on_selected_call_provider_to_be_enabled_from_administration_settings": "Deze functie is afhankelijk van de hierboven geselecteerde oproepprovider die moet worden ingeschakeld via de beheerinstellingen.", + "Feature_depends_on_selected_call_provider_to_be_enabled_from_administration_settings": "Deze functie is afhankelijk van de hierboven geselecteerde oproepprovider die moet worden ingeschakeld via de beheerinstellingen.
        Voor **Jitsi**, zorg ervoor dat Jitsi ingeschakeld is onder Beheer -> Videoconferentie ->Jitsi -> Ingeschakeld.
        Voor **WebRTC**, zorg ervoor dat WebRTC ingeschakeld is onder Beheer -> WebRTC -> Ingeschakeld.", "Feature_Depends_on_Livechat_Visitor_navigation_as_a_message_to_be_enabled": "Deze functie is afhankelijk van het feit of 'Navigatiegeschiedenis van bezoeker als bericht verzenden' is ingeschakeld.", "Feature_Limiting": "Functiebeperking", "Features": "Functies", @@ -2309,7 +2311,9 @@ "Invitation_Subject": "Uitnodiging onderwerp", "Invitation_Subject_Default": "Je bent uitgenodigd voor [Site_Name]", "Invite": "Nodig uit", + "Invites": "Uitnodigingen", "Invite_Link": "Uitnodigingslink", + "Invite_removed": "Uitnodiging succesvol verwijderd", "Invite_user_to_join_channel": "Nodig een gebruiker uit om lid te worden van dit kanaal", "Invite_user_to_join_channel_all_from": "Nodig alle gebruikers van [#kanaal] uit om lid te worden van dit kanaal", "Invite_user_to_join_channel_all_to": "Alle gebruikers van dit kanaal uitnodigen om lid te worden van [#channel]", @@ -2721,7 +2725,7 @@ "Log_Trace_Methods": "Traceer methode aanroepen", "Log_Trace_Methods_Filter": "Traceermethode filter", "Log_Trace_Methods_Filter_Description": "De tekst hier wordt geëvalueerd als RegExp (`new RegExp ('text)`). Houd het leeg om het spoor van elk gesprek te tonen.", - "Log_Trace_Subscriptions": "Traceer abonnementsoproepen", + "Log_Trace_Subscriptions": "Abonnementsgesprekken traceren", "Log_Trace_Subscriptions_Filter": "Traceer abonnementsfilter", "Log_Trace_Subscriptions_Filter_Description": "De tekst hier wordt geëvalueerd als RegExp (`new RegExp ('text)`). Houd het leeg om het spoor van elk gesprek te tonen.", "Log_View_Limit": "Limiet logboekweergave", @@ -3005,7 +3009,7 @@ "MongoDB_Deprecated": "MongoDB verouderd", "MongoDB_version_s_is_deprecated_please_upgrade_your_installation": "MongoDB-versie %s is verouderd, upgrade uw installatie.", "Monitor_added": "Monitor toegevoegd", - "Monitor_history_for_changes_on": "Monitor geschiedenis voor wijzigingen op", + "Monitor_history_for_changes_on": "Geschiedenis controleren voor wijzigingen aan", "Monitor_removed": "Monitor verwijderd", "Monitors": "Monitoren", "Monthly_Active_Users": "Maandelijks actieve gebruikers", @@ -3174,6 +3178,7 @@ "Office_Hours": "Kantoortijden", "Office_hours_enabled": "Kantooruren ingeschakeld", "Office_hours_updated": "Kantooruren bijgewerkt", + "offline": "offline", "Offline": "Offline", "Offline_DM_Email": "Onderwerp e-mail privébericht", "Offline_Email_Subject_Description": "U kunt de volgende tijdelijke aanduidingen gebruiken:
        • [Site_Name], [Site_URL], [User] en [Room] voor respectievelijk de app-naam, URL, gebruikersnaam en roomname.
        ", @@ -3237,7 +3242,7 @@ "or": "of", "Or_talk_as_anonymous": "Of praat als anoniem", "Order": "Volgorde", - "Organization_Email": "Organisatie e-mail", + "Organization_Email": "E-mail organisatie", "Organization_Info": "Organisatie info", "Organization_Name": "Organisatie naam", "Organization_Type": "Organisatie type", @@ -3305,7 +3310,7 @@ "PiwikAnalytics_cookieDomain": "Alle subdomeinen", "PiwikAnalytics_cookieDomain_Description": "Volg bezoekers over alle subdomeinen", "PiwikAnalytics_domains": "Verberg uitgaande links", - "PiwikAnalytics_domains_Description": "Verberg in het rapport 'Outlinks' klikken naar bekende alias-URL's. Voer één domein per regel in en gebruik geen scheidingstekens.", + "PiwikAnalytics_domains_Description": "Verberg klikken naar bekende alias-URL's in het rapport 'Outlinks'. Voer één domein per regel in en gebruik geen scheidingstekens.", "PiwikAnalytics_prependDomain": "Voor domein vooraf toe", "PiwikAnalytics_prependDomain_Description": "Zet het sitedomein voor de paginatitel bij het tracken", "PiwikAnalytics_siteId_Description": "De site-ID die moet worden gebruikt om deze site te identificeren. Voorbeeld: 17", @@ -4160,7 +4165,7 @@ "theme-color-rc-color-announcement-background": "Achtergrondkleur van aankondiging", "theme-color-rc-color-announcement-text-hover": "Tekstkleur voor hover van aankondiging", "theme-color-rc-color-announcement-background-hover": "Hover van achtergrondkleur van aankondiging", - "theme-color-rc-color-button-primary": "Knop Primair", + "theme-color-rc-color-button-primary": "Primaire knop", "theme-color-rc-color-button-primary-light": "Knop Primair licht", "theme-color-rc-color-content": "Inhoud", "theme-color-rc-color-error": "Fout", @@ -4330,7 +4335,7 @@ "UI_Click_Direct_Message_Description": "Sla het openen van het profieltabblad over en ga rechtstreeks naar het gesprek", "UI_DisplayRoles": "Rollen tonen", "UI_Group_Channels_By_Type": "Groepeer kanalen op type", - "UI_Merge_Channels_Groups": "Voeg privégroepen samen met kanalen", + "UI_Merge_Channels_Groups": "Privégroepen met kanalen samenvoegen", "UI_Show_top_navbar_embedded_layout": "Toon bovenste navigatiebalk in geïntegreerde lay-out", "UI_Unread_Counter_Style": "Ongelezen tellerstijl", "UI_Use_Name_Avatar": "Gebruik de initialen van de volledige naam om een standaardavatar te genereren", diff --git a/packages/rocketchat-i18n/i18n/ro.i18n.json b/packages/rocketchat-i18n/i18n/ro.i18n.json index b62744fc559ff..de0f58fb2bcdf 100644 --- a/packages/rocketchat-i18n/i18n/ro.i18n.json +++ b/packages/rocketchat-i18n/i18n/ro.i18n.json @@ -2302,7 +2302,6 @@ "Show_Setup_Wizard": "Afișați asistentul de configurare", "Show_the_keyboard_shortcut_list": "Afișați lista de comenzi rapide de la tastatură", "Showing_archived_results": "

        Se arată %s rezultate arhivate

        ", - "Showing_online_users": null, "Showing_results": "

        Se afișează %s rezultate

        ", "Sidebar": "Bara laterală", "Sidebar_list_mode": "Modul listei de canale din bara laterală", @@ -2781,7 +2780,6 @@ "Yes_unarchive_it": "Da, dezarhivați-o!", "yesterday": "ieri", "You": "Tu", - "you_are_in_preview_mode_of": null, "You_are_logged_in_as": "Sunteți autentificat ca ", "You_are_not_authorized_to_view_this_page": "Nu sunteți autorizat pentru a vizualiza această pagină.", "You_can_change_a_different_avatar_too": "Puteți înlocui avatarul folosit pentru a posta din această integrare.", @@ -2817,4 +2815,4 @@ "Your_push_was_sent_to_s_devices": "Mesajul Push a fost trimis la %s dispozitive", "Your_server_link": "Linkul dvs. de server", "Your_workspace_is_ready": "Spațiul dvs. de lucru este gata de utilizare 🎉" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/sr.i18n.json b/packages/rocketchat-i18n/i18n/sr.i18n.json index 391f91bc4ffe9..605c58f8db201 100644 --- a/packages/rocketchat-i18n/i18n/sr.i18n.json +++ b/packages/rocketchat-i18n/i18n/sr.i18n.json @@ -1,7 +1,6 @@ { "403": "Забрањен", "500": "Интерна грешка сервера", - "__username__was_set__role__by__user_by_": null, "@username": "@корисничко име", "@username_message": "@ корисничко име ", "#channel": "#канал", @@ -2087,7 +2086,6 @@ "Show_Setup_Wizard": "Прикажи чаробњак за подешавање", "Show_the_keyboard_shortcut_list": "Покажите листу пречица на тастатури", "Showing_archived_results": "

        Показујући %s архивиране резултате

        ", - "Showing_online_users": null, "Showing_results": "

        Приказујем %s резултата

        ", "Sidebar": "Сидебар", "Sidebar_list_mode": "Режим листе канала бочне траке", @@ -2586,4 +2584,4 @@ "Your_push_was_sent_to_s_devices": "Ваш притиском је послат на %s уређајима", "Your_server_link": "Веза са сервером", "Your_workspace_is_ready": "Ваш радни простор је спреман за кориштење 🎉" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/sv.i18n.json b/packages/rocketchat-i18n/i18n/sv.i18n.json index aaa696bfc544a..909ffa328f65e 100644 --- a/packages/rocketchat-i18n/i18n/sv.i18n.json +++ b/packages/rocketchat-i18n/i18n/sv.i18n.json @@ -2389,7 +2389,6 @@ "Show_Setup_Wizard": "Visa installationsguiden", "Show_the_keyboard_shortcut_list": "Visa genvägslistan för tangentbordet", "Showing_archived_results": "

        Visar %s arkiverade resultat

        ", - "Showing_online_users": null, "Showing_results": "

        Visar %s resultat

        ", "Sidebar": "Sidebar", "Sidebar_list_mode": "Sidpanel Kanallista läge", @@ -2920,4 +2919,4 @@ "Your_push_was_sent_to_s_devices": "Din push skickades till %s enheter", "Your_server_link": "Din serverlänk", "Your_workspace_is_ready": "Din arbetsyta är redo att använda 🎉" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/ta-IN.i18n.json b/packages/rocketchat-i18n/i18n/ta-IN.i18n.json index 293eb0975800a..81117509ab6ff 100644 --- a/packages/rocketchat-i18n/i18n/ta-IN.i18n.json +++ b/packages/rocketchat-i18n/i18n/ta-IN.i18n.json @@ -2301,7 +2301,6 @@ "Show_Setup_Wizard": "அமைவு வழிகாட்டி காட்டு", "Show_the_keyboard_shortcut_list": "விசைப்பலகை குறுக்குவழி பட்டியலைக் காட்டு", "Showing_archived_results": "

        %s ஐக் காட்டுகிறது காப்பகப் முடிவுகள்

        ", - "Showing_online_users": null, "Showing_results": "

        %s ஐக் காட்டுகிறது முடிவுகள்

        ", "Sidebar": "பக்கப்பட்டி", "Sidebar_list_mode": "பக்கப்பட்டி சேனல் பட்டியல் முறை", @@ -2789,7 +2788,6 @@ "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "நீங்கள் எளிதாக உங்கள் CRM உடன் livechat ஒருங்கிணைக்க webhooks பயன்படுத்த முடியும்.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "நீங்கள் ஒரு livechat அறையில் விட்டு போக முடியாது. தயவு செய்து, நெருங்கிய பொத்தானை பயன்படுத்த.", "You_have_been_muted": "நீங்கள் ஒலியடக்கப்பட்டுள்ளன இந்த அறையில் பேச முடியாது வேண்டும்", - "You_have_n_codes_remaining": null, "You_have_not_verified_your_email": "நீங்கள் உங்கள் மின்னஞ்சல் சரிபார்க்கப்படவில்லை.", "You_have_successfully_unsubscribed": "நீங்கள் வெற்றிகரமாக எங்கள் Mailling பட்டியல் விலகியுள்ளீர்கள்.", "You_have_to_set_an_API_token_first_in_order_to_use_the_integration": "ஒருங்கிணைப்புகளைப் பயன்படுத்த நீங்கள் முதலில் ஒரு API டோக்கனை அமைக்க வேண்டும்.", @@ -2816,4 +2814,4 @@ "Your_push_was_sent_to_s_devices": "உங்கள் மிகுதி% கள் சாதனங்கள் அனுப்பப்பட்டது", "Your_server_link": "உங்கள் சர்வர் இணைப்பு", "Your_workspace_is_ready": "உங்கள் பணியிடம் use பயன்படுத்த தயாராக உள்ளது" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/tr.i18n.json b/packages/rocketchat-i18n/i18n/tr.i18n.json index 8a1bad1788bcf..eb5a7c75710c3 100644 --- a/packages/rocketchat-i18n/i18n/tr.i18n.json +++ b/packages/rocketchat-i18n/i18n/tr.i18n.json @@ -2728,7 +2728,6 @@ "Show_Setup_Wizard": "Kurulum Sihirbazını Göster", "Show_the_keyboard_shortcut_list": "Klavye kısayol listesini göster", "Showing_archived_results": "

        %s arşivlenmiş sonuçlar gösteriliyor

        ", - "Showing_online_users": null, "Showing_results": "

        %s kayıt bulundu

        ", "Sidebar": "Kenar çubuğu", "Sidebar_list_mode": "Kenar Çubuğu Kanal Listesi Modu", @@ -2848,7 +2847,7 @@ "Sunday": "Pazar", "Support": "Yardım", "Survey": "Anket", - "Survey_instructions": "Tamamen memnun anlam tamamen tatminsiz olan, yani sizi tatmin göre 1 her soruyu oranı ve 5.", + "Survey_instructions": "Her soruya memnuniyetinize göre cevap verin. Hiç memnun değilseniz 1, çok memnunsanız 5 verin.", "Symbols": "Semboller", "Sync": "Eşitle", "Sync / Import": "Eşitle / İçe Aktar", @@ -3333,4 +3332,4 @@ "Your_question": "Sorunuz", "Your_server_link": "Sunucu bağlantınız", "Your_workspace_is_ready": "Çalışma alanınız kullanılmaya hazır 🎉" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/ug.i18n.json b/packages/rocketchat-i18n/i18n/ug.i18n.json index dd77c6c4ac3cb..d40e3901cb75c 100644 --- a/packages/rocketchat-i18n/i18n/ug.i18n.json +++ b/packages/rocketchat-i18n/i18n/ug.i18n.json @@ -979,7 +979,6 @@ "Show_only_online": "توردا بار ئەزانى كۆرسىتىش", "Show_preregistration_form": "كىرىشتىن بۇرۇنقى ھۆججەتنى كۆرسىتىش", "Showing_archived_results": "

        ئاللىبۇرۇن تۈرگە ئايرىپ ساقلاندى
        %s كۆرسىتىش

        ", - "Showing_online_users": null, "Showing_results": "تال نەتىجە %sكۆرسىتىش

        ", "since_creation": "دىن باشلانغان %s", "Site_Name": "تور نامى", @@ -1241,4 +1240,4 @@ "Your_mail_was_sent_to_s": "يوللاندى %s سىزنىڭ ئىلخىتىڭىز ئاللىبۇرۇن", "Your_password_is_wrong": "پارول خاتا !", "Your_push_was_sent_to_s_devices": "ئۈسكىنىگە يوللاندى %s سىزنىڭ ئىتتىرگىنىڭىز" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/uk.i18n.json b/packages/rocketchat-i18n/i18n/uk.i18n.json index 57b9b48377669..0a94894dcf779 100644 --- a/packages/rocketchat-i18n/i18n/uk.i18n.json +++ b/packages/rocketchat-i18n/i18n/uk.i18n.json @@ -2851,7 +2851,6 @@ "Show_Setup_Wizard": "Показати майстер налаштування", "Show_the_keyboard_shortcut_list": "Показати список комбінацій клавіш", "Showing_archived_results": "

        Відображення %s заархівовані результати

        ", - "Showing_online_users": null, "Showing_results": "

        Показано результатів %s

        ", "Sidebar": "Бічна панель", "Sidebar_list_mode": "Режим списку каналів бічної панелі", @@ -3419,4 +3418,4 @@ "Your_server_link": "Посилання на Ваш сервер", "Your_temporary_password_is_password": "Ваш тимчасовий пароль [password].", "Your_workspace_is_ready": "Ваш робочий простір готовий до використання 🎉" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/zh-HK.i18n.json b/packages/rocketchat-i18n/i18n/zh-HK.i18n.json index 5e1669a48b8b9..1bc32d7ce84c3 100644 --- a/packages/rocketchat-i18n/i18n/zh-HK.i18n.json +++ b/packages/rocketchat-i18n/i18n/zh-HK.i18n.json @@ -2324,7 +2324,6 @@ "Show_Setup_Wizard": "显示安装向导", "Show_the_keyboard_shortcut_list": "显示键盘快捷键列表", "Showing_archived_results": "

        显示%s已存档结果

        ", - "Showing_online_users": null, "Showing_results": "

        显示%s条结果

        ", "Sidebar": "侧边栏", "Sidebar_list_mode": "边栏频道列表模式", @@ -2838,4 +2837,4 @@ "Your_push_was_sent_to_s_devices": "您的推送已发送到%s设备", "Your_server_link": "您的服务器链接", "Your_workspace_is_ready": "您的工作区已准备好使用🎉" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/zh.i18n.json b/packages/rocketchat-i18n/i18n/zh.i18n.json index 68c6bf878fb11..1172b63a913e4 100644 --- a/packages/rocketchat-i18n/i18n/zh.i18n.json +++ b/packages/rocketchat-i18n/i18n/zh.i18n.json @@ -3621,7 +3621,7 @@ "Sunday": "星期日", "Support": "支持", "Survey": "调查", - "Survey_instructions": "根据问题填写您的满意度,1 代表完全不满意,5 代表非常满意。", + "Survey_instructions": "请根据您的满意程度给每个问题打分,1 代表非常不满意,5 代表非常满意。", "Symbols": "符号", "Sync": "同步", "Sync / Import": "同步 / 导入", From abc59e1b9d3dc194ac743df8400b1e172970b040 Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Mon, 6 Dec 2021 18:06:39 -0300 Subject: [PATCH 137/137] Regression: Missing padding in popover with custom template (#23877) --- app/theme/client/imports/components/popover.css | 6 +++++- app/ui-utils/client/lib/popover.html | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/theme/client/imports/components/popover.css b/app/theme/client/imports/components/popover.css index f1ac7ce254203..5ce63aee851e5 100644 --- a/app/theme/client/imports/components/popover.css +++ b/app/theme/client/imports/components/popover.css @@ -42,7 +42,7 @@ max-height: 70%; - padding: var(--popover-padding) 0; + padding: var(--popover-padding); animation: dropdown-show 0.1s cubic-bezier(0.45, 0.05, 0.55, 0.95); @@ -53,6 +53,10 @@ border-radius: var(--popover-radius); background-color: var(--popover-background); box-shadow: 0 0 2px 0 rgba(47, 52, 61, 0.08), 0 0 12px 0 rgba(47, 52, 61, 0.12); + + &--templateless { + padding: var(--popover-padding) 0; + } } &__column { diff --git a/app/ui-utils/client/lib/popover.html b/app/ui-utils/client/lib/popover.html index 588d9365be686..4376562cdb30a 100644 --- a/app/ui-utils/client/lib/popover.html +++ b/app/ui-utils/client/lib/popover.html @@ -1,6 +1,6 @@